[{"content":"","date":"June 7, 2026","externalUrl":null,"permalink":"/tags/analog-signals/","section":"Tags","summary":"","title":"Analog-Signals","type":"tags"},{"content":"","date":"June 7, 2026","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"June 7, 2026","externalUrl":null,"permalink":"/tags/filters/","section":"Tags","summary":"","title":"Filters","type":"tags"},{"content":"The site is intentionally small, static, and readable — because nobody needs a blog that ships more JavaScript than actual substance.\n","date":"June 7, 2026","externalUrl":null,"permalink":"/","section":"Hyperborealis Blog","summary":"","title":"Hyperborealis Blog","type":"page"},{"content":"","date":"June 7, 2026","externalUrl":null,"permalink":"/tags/plc/","section":"Tags","summary":"","title":"Plc","type":"tags"},{"content":"","date":"June 7, 2026","externalUrl":null,"permalink":"/categories/plc-programming/","section":"Categories","summary":"","title":"PLC Programming","type":"categories"},{"content":" The Problem Analog PLC signals are rarely clean.\nPressure, temperature, force, and position signals often contain electrical noise, quantization noise, or mechanical vibration. A simple first-order low-pass filter is often enough to make the signal usable.\nThe hard part is not the filter equation. The hard part is choosing the coefficient without guessing.\nThis article assumes:\nthe PLC task cycle time is constant the filter runs once per task cycle the input value is already scaled to engineering units the relevant signal content is below the Nyquist frequency the filter is used for signal conditioning, not for safety-critical decisions A Simple First-Order Low-Pass Filter A common PLC low-pass filter is:\n$$ a = \\frac{T_s}{T_f + 0.5T_s} $$$$ y_f[k] = y_f[k-1] + a\\left(y[k] - y_f[k-1]\\right) $$ Symbol Description \\(T_s\\) PLC cycle time \\(T_f\\) Filter time \\(y_f[k-1]\\) Filter value last cycle \\(y_f[k]\\) Filtered value \\(y[k]\\) Input value \\(a\\) Filter coefficient The same equation can also be written as:\n$$ y_f[k] = (1-a)y_f[k-1] + ay[k] $$That form makes the meaning of \\(a\\) easier to see. The new filtered value is a weighted combination of the previous filtered value and the current input value.\nFor \\(a = 0.01\\), the filtered value moves 1% of the remaining distance from the previous filtered value toward the current input value every cycle. It does not simply add 1% of the input. It adds 1% of the current error between input and filtered value.\nUseful practical limits are:\n\\(0 \u003c a \u003c 1\\): normal smoothing behavior \\(a = 1\\): no filtering \\(1 \u003c a \u003c 2\\): mathematically stable, but usually not useful because the response can alternate after a step \\(a \\le 0\\) or \\(a \\ge 2\\): invalid or unstable for this use In PLC code, I usually clamp or reject coefficients outside \\(0 \u003c a \\le 1\\).\nWhy Task Cycle Time Matters The filter depends on the task cycle time. The same coefficient behaves differently in a 1 ms task and a 10 ms task.\nThe ratio \\(T_f / T_s\\) determines how many PLC cycles the filter has to react. If the filter time is close to the cycle time, the discrete approximation becomes coarse. If the filter time is much larger than the cycle time, the behavior is easier to predict.\nFor this reason, \\(T_f\\) should normally be much larger than \\(T_s\\). That rule is useful, but it is not very convenient when selecting a filter for a real signal. A cutoff frequency is often easier to reason about.\nThe cutoff frequency is the frequency where the filter magnitude has dropped by 3 dB. That is about 70.7% of the input amplitude.\nThe following chart shows the magnitude response for different values of \\(T_f\\), with \\(T_s = 10\\,\\text{ms}\\).\nMagnitude response for one low-pass filter stage Small coefficients give stronger smoothing and lower cutoff frequencies. They also add more delay. There is no free filter hiding in the control cabinet.\nChoosing The Coefficient From Cutoff Frequency For practical work, it is useful to choose the desired cutoff frequency and calculate the coefficient from it.\nThe following Python function calculates \\(a\\) for one or more identical filter stages. The argument stage_count is the number of filters connected in series. For example, use stage_count=2 when two identical first-order filters are chained and the combined response should reach -3 dB at the requested cutoff frequency.\nfrom __future__ import annotations import numpy as np def coefficient_from_cutoff( cutoff_frequency_hz: np.ndarray | float, sample_frequency_hz: float, stage_count: int = 1, ) -\u0026gt; np.ndarray | float: \u0026#34;\u0026#34;\u0026#34;Return the low-pass coefficient for a target -3 dB cutoff frequency.\u0026#34;\u0026#34;\u0026#34; if sample_frequency_hz \u0026lt;= 0.0: raise ValueError(\u0026#34;sample_frequency_hz must be positive.\u0026#34;) if stage_count \u0026lt;= 0: raise ValueError(\u0026#34;stage_count must be positive.\u0026#34;) cutoff = np.asarray(cutoff_frequency_hz, dtype=float) if np.any(cutoff \u0026lt; 0.0) or np.any(cutoff \u0026gt; sample_frequency_hz / 2.0): raise ValueError(\u0026#34;cutoff_frequency_hz must be in the range 0 \u0026lt;= fc \u0026lt;= fs/2.\u0026#34;) beta = 10.0 ** (-3.0 / (10.0 * stage_count)) omega_c = 2.0 * np.pi * cutoff / sample_frequency_hz distance = 1.0 - np.cos(omega_c) radicand = beta**2 * distance**2 + 2.0 * beta * distance * (1.0 - beta) coefficient = (-beta * distance + np.sqrt(radicand)) / (1.0 - beta) if np.isscalar(cutoff_frequency_hz): return float(coefficient) return coefficient Example for a PLC task running at 100 Hz with a desired cutoff frequency of 2 Hz:\ncoefficient = coefficient_from_cutoff( cutoff_frequency_hz=2.0, sample_frequency_hz=100.0, stage_count=1, ) The result can be used directly as the coefficient in the PLC filter.\nOne Stage Versus Two Stages Two identical first-order filters in series create a steeper roll-off than one filter. This can be useful when high-frequency noise should be reduced more strongly.\nThe trade-off is more delay. For control loops, that delay can reduce stability margin. For HMI values or slow diagnostics, it is often acceptable.\nMagnitude response for two low-pass filter stages When two filters are chained, each stage must be less aggressive if the combined response should have the same -3 dB cutoff frequency. That is why the Python function includes stage_count.\nStructured Text Implementation The filter itself is small. The important details are initialization and coefficient validation.\nOn startup or reset, initialize the filtered value to the current input. Otherwise the filter may start from zero and create an artificial transient.\nFUNCTION_BLOCK AnalogLowPassFilter VAR signalFiltered : LREAL; initialized : BOOL; coefficientValid : BOOL; END_VAR METHOD update VAR_INPUT signalRaw : LREAL; coefficient : LREAL; reset : BOOL; END_VAR coefficientValid := (coefficient \u0026gt; 0.0) AND (coefficient \u0026lt;= 1.0); IF NOT coefficientValid THEN RETURN; END_IF IF reset OR NOT initialized THEN signalFiltered := signalRaw; initialized := TRUE; RETURN; END_IF signalFiltered := signalFiltered + coefficient * (signalRaw - signalFiltered); END_METHOD Expose the filtered value through a property or through the project’s existing signal interface. Expose coefficientValid as diagnostics if the coefficient comes from a recipe or HMI. Avoid writing the filter state directly from outside the function block.\nPROPERTY SignalFiltered : LREAL Getter implementation:\nSignalFiltered := signalFiltered; PROPERTY CoefficientValid : BOOL Getter implementation:\nCoefficientValid := coefficientValid; For a two-stage filter, instantiate two filters and call them in order. The output of the first stage becomes the input of the second stage. Both stages should use the coefficient calculated with stage_count=2.\nLimitations A low-pass filter is not a repair tool for a bad signal chain.\nIt can reduce noise, but it cannot fix:\nbad shielding poor grounding loose terminals incorrect analog input configuration aliasing from high-frequency noise broken or drifting sensors Filtering also adds delay. That matters in closed-loop control. A pressure value shown on an HMI can tolerate more delay than a feedback signal used by a fast controller.\nInvalid sensor states should be handled explicitly. If the analog terminal reports an error, the filter should usually hold, reset, or mark its output invalid. Silently filtering invalid data is just hiding a fault with extra math.\nConclusion A first-order low-pass filter is useful for analog PLC signals because it is cheap, deterministic, and easy to implement.\nThe coefficient should not be guessed. Choose it from the task cycle time and the desired cutoff frequency. Use one stage for simple smoothing. Use two stages only when the stronger attenuation is worth the added delay.\nAs usual in PLC work, the formula is the easy part. The engineering decision is deciding how much delay the machine can tolerate.\n","date":"June 7, 2026","externalUrl":null,"permalink":"/posts/p011/","section":"Posts","summary":"","title":"PLC Programming: Choosing a Low-Pass Filter for Analog Signals","type":"posts"},{"content":"","date":"June 7, 2026","externalUrl":null,"permalink":"/tags/signal-processing/","section":"Tags","summary":"","title":"Signal-Processing","type":"tags"},{"content":"","date":"June 7, 2026","externalUrl":null,"permalink":"/tags/structured-text/","section":"Tags","summary":"","title":"Structured-Text","type":"tags"},{"content":"","date":"June 7, 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"June 6, 2026","externalUrl":null,"permalink":"/tags/architecture/","section":"Tags","summary":"","title":"Architecture","type":"tags"},{"content":"","date":"June 6, 2026","externalUrl":null,"permalink":"/tags/initialization/","section":"Tags","summary":"","title":"Initialization","type":"tags"},{"content":"","date":"June 6, 2026","externalUrl":null,"permalink":"/tags/twincat/","section":"Tags","summary":"","title":"Twincat","type":"tags"},{"content":" The Problem Many PLC projects initialize by accident.\nA function block sets a first-cycle flag. Another one does some work in FB_init. A service registers itself when it happens to run. A hardware wrapper assumes that a configuration block has already copied its parameters. The machine starts because the current call order happens to make it start.\nThat is not architecture. That is luck with a scan cycle.\nThe problem usually appears later:\nafter an online change during simulation when a library is reused in another project when a service is called before it is registered when hardware needs more time to become available when commissioning exposes a startup race that never happened on the developer\u0026rsquo;s desk Startup should be explicit. A PLC runs cyclically, but a project still has a lifecycle.\nCyclic Code Is Not Startup Code Normal cyclic code should update the machine state. It should read inputs, update controllers, handle commands, write outputs, and expose diagnostics.\nStartup code has a different job. It should prepare the system so cyclic code can run safely.\nThat usually means:\nloading or validating configuration initializing infrastructure services connecting references and interfaces checking hardware wrappers validating parameter ranges confirming required dependencies exist reporting startup faults Mixing this into normal cyclic logic makes the program harder to reason about. A first-cycle flag looks harmless until every function block has one and the actual startup order becomes a treasure map drawn by committee.\nThe better model is simple:\nStartup is a controlled state, not a side effect of cyclic execution.\nFB_init Is Limited FB_init is not bad. It exists for a reason.\nIt is useful for local setup that belongs to the function block itself. Simple initial values, local bookkeeping, and idempotent internal preparation can fit there.\nIt is less suitable for project-level wiring.\nFB_init runs automatically. It is also called again during an online change. That matters. If complex dependency registration is hidden inside FB_init, the behavior after an online change can differ from the behavior after a cold start. Very exciting, if your definition of exciting includes debugging a machine that only fails after lunch.\nThere is another practical issue: FB_init does not make the project startup order obvious. When reading the application, it is harder to see which service is initialized first, which module depends on it, and what happens if the dependency is missing.\nA useful rule is:\nUse FB_init for local, simple, repeatable setup. Use an explicit startup phase for project dependencies.\nUse an Explicit Init Phase For project-level initialization, expose an init() method on function blocks that need setup. Call those methods from a dedicated startup sequence.\nThe important detail is timing. The explicit initialization phase runs after the function block instances already exist. That makes it possible to connect references, pass interfaces, validate configuration, and report failures in one visible place.\nA small initialization method can be enough.\nMETHOD init : BOOL VAR_INPUT service : IService; END_VAR IF service \u0026lt;\u0026gt; 0 THEN _service := service; _initialized := TRUE; _hasError := FALSE; init := TRUE; ELSE _initialized := FALSE; _hasError := TRUE; init := FALSE; END_IF END_METHOD This example is intentionally small. The point is not the exact interface. The point is that dependency validation happens where the dependency is assigned.\nThe cyclic part should not silently run before initialization.\nMETHOD update IF NOT _initialized THEN _hasError := TRUE; RETURN; END_IF // Normal cyclic logic runs here. END_METHOD Returning early is not enough by itself. The missing initialization must be observable through a diagnostic flag, error code, state, event, or HMI message. Silent failure is not graceful.\nPut Startup in One Place A project-level startup runner does not need to be complicated. It can be a small state machine owned by the application.\n{attribute \u0026#39;qualified_only\u0026#39;} {attribute \u0026#39;strict\u0026#39;} TYPE StartupState : ( IDLE, INIT_SERVICES, INIT_HARDWARE, INIT_MODULES, RUNNING, ERROR ) DINT; END_TYPE The application then controls the startup order explicitly.\nCASE state OF StartupState.IDLE: state := StartupState.INIT_SERVICES; StartupState.INIT_SERVICES: IF logger.init() THEN state := StartupState.INIT_HARDWARE; ELSE state := StartupState.ERROR; END_IF StartupState.INIT_HARDWARE: IF pressureSensor.init() THEN state := StartupState.INIT_MODULES; ELSE state := StartupState.ERROR; END_IF StartupState.INIT_MODULES: IF controller.init(logger, pressureSensor) THEN state := StartupState.RUNNING; ELSE state := StartupState.ERROR; END_IF StartupState.RUNNING: controller.update(); StartupState.ERROR: // Report the startup fault. ELSE state := StartupState.ERROR; END_CASE This is not a framework. It is a visible startup sequence. That is the point.\nFor a small project, this can live directly in the top-level application block. For a larger project, it is usually cleaner to put it in a dedicated startup function block. MAIN should still stay boring: instantiate top-level objects and call the runner.\nValidate Dependencies Initialization is not only about calling methods in order. It is also the best place to reject an invalid project state before the machine enters normal operation.\nUseful checks include:\ninterface variables are not 0 REFERENCE TO variables pass __ISVALIDREF() configuration values are inside valid ranges required infrastructure services are registered hardware wrappers have usable input data module limits match the configured machine variant Do not spread these checks randomly through the codebase. A module should validate the dependencies it owns. The startup runner should decide whether the system may continue.\nFor references, check the reference before using it.\nIF __ISVALIDREF(configuration) THEN speedMax := configuration.speedMax; ELSE _hasError := TRUE; END_IF For interface variables, check against 0.\nIF alarmService \u0026lt;\u0026gt; 0 THEN alarmService.raise(\u0026#39;Startup failed\u0026#39;); END_IF These checks are small, but they prevent a common failure mode: a function block assumes the world is ready because the compiler accepted the code.\nReport Initialization Faults A startup sequence should fail loudly enough to be diagnosed.\nAt minimum, expose:\nthe current startup state whether startup has completed whether startup has failed the failed step or module an error code or short error text For small systems, a few properties may be enough.\nPROPERTY HasError : BOOL HasError := _hasError; END_PROPERTY For larger systems, use a consistent diagnostic structure. The important part is consistency. If every module reports errors differently, the HMI becomes a museum of local opinions.\nStartup errors should also be visible during simulation and testing. A test setup should be able to prove that a missing dependency prevents the project from entering StartupState.RUNNING.\nHardware May Be Late Not every dependency is ready in the first scan.\nSome hardware needs several cycles before useful data is available. Fieldbus devices may still be changing state. External services may need time to connect. An OPC UA, MQTT, database, or ADS service may not be ready just because the PLC task has started.\nThat does not invalidate the startup sequence. It means the startup sequence may need waiting states with timeouts.\nFor example:\nwait for a hardware wrapper to report operational data wait for a service to become connected fail after a defined timeout allow a manual reset after the fault is corrected Online Changes Need Testing Online change is part of normal TwinCAT development. It is also one reason initialization logic needs discipline.\nAfter an online change, some initialization behavior may run again. Some variables may keep their values. Others may not. References and registrations should not rely on folklore.\nTest the project lifecycle explicitly:\ncold start warm restart online change simulation startup missing hardware or service invalid configuration The startup runner should make these cases easier to test because the project has a visible state. Without that, the startup model is whatever happened last time. A bold testing strategy, in the same way jumping from a roof is a bold elevator strategy.\nConclusion TwinCAT projects should not start by accident.\nFB_init is useful for local setup, but it is not a project architecture. First-cycle flags can solve small problems, but they do not create a clear lifecycle.\nUse an explicit startup phase. Call init() methods in a known order. Validate dependencies. Expose startup faults. Enter cyclic operation only when the system is ready.\nThe result is controlled startup behavior. It also forces the programmer to design the startup process explicitly.\n","date":"June 6, 2026","externalUrl":null,"permalink":"/posts/p010/","section":"Posts","summary":"","title":"TwinCAT Initialization: Stop Letting Startup Happen by Accident","type":"posts"},{"content":"","date":"June 6, 2026","externalUrl":null,"permalink":"/tags/interfaces/","section":"Tags","summary":"","title":"Interfaces","type":"tags"},{"content":" The Problem PLC programs often start close to the hardware. That is natural. Inputs are mapped. Outputs are mapped. Drives expose status words. Analog terminals return raw values. Fieldbus devices have vendor-specific diagnostics.\nThe problem starts when those details spread into the application logic.\nA pressure controller should not need to know that the pressure signal comes from a 12-bit analog terminal. A winch controller should not need to know the exact bit layout of a drive status word. A machine sequence should not care whether a valve is connected through local I/O, EtherCAT, CANopen, or a simulation block.\nWhen hardware details become part of the application architecture, every hardware change becomes a software change in too many places. Very efficient, if the goal is to make a terminal replacement feel like archaeology.\nThe better rule is simple:\nHardware details belong at the boundary. Application modules should depend on contracts.\nHardware Is a Boundary Hardware-facing code has an important job. It translates physical, electrical, and vendor-specific details into something the rest of the program can use safely.\nThat boundary code may deal with:\nraw analog ranges scaling and engineering units fieldbus status words terminal diagnostics vendor-specific fault codes simulation values stale data hardware states such as OP and PRE_OP Those details are real. They should not be ignored. But they should be contained.\nInside the application, the code should work with stable meanings:\npressure in bar speed as a relative command valve open or closed drive operational or pre-operational sensor value available only when the sensor is OP The boundary converts from hardware representation to application representation. That is where raw types, exact terminal ranges, and protocol layouts belong.\nContracts Describe Behavior A contract defines what a module provides, not how it is implemented.\nIn TwinCAT, a small interface is often enough. If the project uses operational states instead of diagnostic booleans, define that state explicitly.\n{attribute \u0026#39;qualified_only\u0026#39;} {attribute \u0026#39;strict\u0026#39;} TYPE OperationState : ( PRE_OP, OP ) DINT; END_TYPE INTERFACE ISensor PROPERTY Value : LREAL PROPERTY OpState : OperationState END_INTERFACE This contract says that the object can provide a value and an operational state. A sensor in OperationState.OP has a valid value. A sensor in OperationState.PRE_OP does not. The contract does not say whether the value comes from an analog input, an EtherCAT terminal, a calculation, or a test object.\nA valve contract can be just as small.\nINTERFACE IValve METHOD open METHOD close PROPERTY OpState : OperationState END_INTERFACE The controller using the valve does not need to know whether open() writes a digital output, sends a fieldbus command, or changes a simulated state. It only needs the agreed behavior.\nThat agreement is the architecture.\nA Hardware-Coupled Design Consider a pressure relief function. In a weak design, the controller directly knows the raw input range and the output mapping.\nFUNCTION_BLOCK PressureRelief VAR_INPUT pressureRaw : DINT; END_VAR VAR valveOutput AT %Q* : BOOL; END_VAR VAR CONSTANT PRESSURE_RAW_LIMIT : DINT := 27648; END_VAR IF pressureRaw \u0026gt; PRESSURE_RAW_LIMIT THEN valveOutput := TRUE; ELSE valveOutput := FALSE; END_IF This is small, but the coupling is already visible.\nThe block depends on:\nthe raw input scale the physical output mapping the chosen terminal representation the absence of an operational state the assumption that TRUE means valve open If the pressure signal changes to another terminal type, the controller changes. If the valve becomes a fieldbus valve, the controller changes. If simulation is needed, the controller changes. If diagnostics are added, the controller changes again, because apparently the controller volunteered to become the entire system.\nA Contract-Based Design The same behavior can be expressed through contracts.\nFUNCTION_BLOCK PressureRelief VAR sensor : ISensor; valve : IValve; initialized : BOOL; END_VAR VAR CONSTANT PRESSURE_LIMIT : LREAL := 120.0; END_VAR The dependencies are injected explicitly during initialization.\nMETHOD init : BOOL VAR_INPUT sensorInput : ISensor; valveInput : IValve; END_VAR IF sensorInput = 0 OR valveInput = 0 THEN initialized := FALSE; init := FALSE; RETURN; END_IF sensor := sensorInput; valve := valveInput; initialized := TRUE; init := TRUE; The cyclic logic now depends on meaning instead of hardware.\nMETHOD update IF NOT initialized THEN RETURN; END_IF IF sensor \u0026lt;\u0026gt; 0 AND_THEN valve \u0026lt;\u0026gt; 0 THEN IF valve.OpState = OperationState.OP THEN IF sensor.OpState = OperationState.OP AND_THEN sensor.Value \u0026gt; PRESSURE_LIMIT THEN valve.open(); ELSE valve.close(); END_IF END_IF END_IF This code says what the machine should do:\nIf the valve can be controlled, and the sensor is operational, open the relief valve when pressure is too high.\nIt does not say how the pressure is measured. It does not say how the valve is wired. Those decisions are outside this module.\nThat separation is the point.\nImplementation Blocks Adapt Hardware Contract-based design does not remove hardware code. It gives hardware code a proper place to live.\nA real analog pressure sensor can implement ISensor.\nFUNCTION_BLOCK AnalogPressureSensor IMPLEMENTS ISensor VAR valueActual : LREAL; opStateActual : OperationState := OperationState.PRE_OP; END_VAR VAR CONSTANT RAW_MIN : LREAL := 0.0; RAW_MAX : LREAL := 32767.0; PRESSURE_MIN : LREAL := 0.0; PRESSURE_MAX : LREAL := 250.0; END_VAR Its update method handles the boundary conversion. The linear scaling is kept in a helper function so the sensor block shows intent instead of algebra.\nMETHOD update VAR_INPUT valueRaw : DINT; opStateTerminal : OperationState; END_VAR opStateActual := opStateTerminal; IF opStateActual = OperationState.OP THEN valueActual := transformLinear( valueInput := TO_LREAL(valueRaw), valueInputMin := RAW_MIN, valueInputMax := RAW_MAX, valueOutputMin := PRESSURE_MIN, valueOutputMax := PRESSURE_MAX, limitOutput := TRUE); ELSE valueActual := 0.0; END_IF The properties expose the contract.\nPROPERTY Value : LREAL Getter:\nValue := valueActual; PROPERTY OpState : OperationState Getter:\nOpState := opStateActual; A simulated sensor can implement the same contract without any I/O.\nFUNCTION_BLOCK SimulatedPressureSensor IMPLEMENTS ISensor VAR valueActual : LREAL; opStateActual : OperationState := OperationState.OP; END_VAR METHOD setValue VAR_INPUT valueInput : LREAL; END_VAR valueActual := valueInput; The pressure relief controller can use either implementation. It does not change.\nTesting Becomes a Consequence When application logic depends on contracts, testing becomes much easier.\nThe controller can be tested with fake objects:\na fake sensor that returns selected pressure values a fake valve that stores whether open() or close() was called No analog terminal is required. No real valve is required. No fieldbus device is required.\nThe test checks the machine rule:\nAbove the pressure limit, the valve shall open.\nThat is better than testing a raw number from one specific terminal. The raw number is a hardware detail. The pressure limit is the application behavior.\nThis does not make hardware tests unnecessary. It only means the business logic can be tested before the cabinet exists. A strange idea, apparently, but useful.\nContracts Need Semantics An interface only defines syntax. It does not define engineering meaning.\nThis interface is not enough by itself:\nINTERFACE ISensor PROPERTY Value : LREAL PROPERTY OpState : OperationState END_INTERFACE The team must also define the semantics:\nValue: Engineering value in bar. Updated before the controller update method is called. Meaningful only when OpState is OP. OpState: OP when the value is fresh, scaled, and based on healthy input data. PRE_OP when the signal is missing, stale, invalid, or not ready for use. Without that agreement, two blocks can implement the same interface with different meanings. The compiler will accept it. The machine may not.\nGood contracts need both parts:\na technical shape an engineering convention The interface gives the shape. Documentation, naming, tests, and reviews enforce the convention.\nWhere This Pattern Helps Contract-based modules are useful when implementations may change while behavior stays stable.\nTypical examples are:\nanalog sensors digital actuators proportional valves drives axes winches HMI commands alarm services logging services communication adapters simulation models The pattern is also useful when a framework must support machine variants. One vessel may use one drive vendor. Another may use a different inverter setup. One project may use real I/O. Another may run in simulation. If the application depends on contracts, those variants can be adapted at the boundary.\nLimitations Contracts are useful, but they are not free.\nBad abstractions are worse than no abstractions. If an interface hides important device behavior, the application may lose information it actually needs. Diagnostics, limits, operational state, and timing behavior must remain visible.\nContracts also do not remove real-time concerns. Task cycle times, update order, stale data, timeouts, and fieldbus behavior still matter. A clean interface cannot fix an impossible timing requirement.\nFunctional safety is separate. A normal PLC interface can make control logic cleaner, but it does not replace a safety PLC, safety-rated components, risk assessment, or certified safety design.\nConclusion Hardware details are unavoidable in PLC programming. They are not the problem. The problem is letting those details define the application architecture.\nKeep raw values, terminal ranges, fieldbus words, and vendor-specific behavior at the system boundary. Translate them into stable contracts with clear engineering meaning.\nApplication modules should communicate through behavior:\nthis sensor provides an operational pressure value this valve can open and close this drive can accept a relative speed command this service can publish a diagnostic message That makes modules easier to test, easier to simulate, easier to replace, and easier to reuse.\nThe practical rule is simple:\nDesign application modules around contracts, not hardware.\n","date":"June 6, 2026","externalUrl":null,"permalink":"/posts/p009/","section":"Posts","summary":"","title":"PLC Architecture: Design Modules Around Contracts, Not Hardware","type":"posts"},{"content":" The Problem Infrastructure APIs often look simple from the outside.\nA function block calls a logging API. An alarm is raised. An MQTT message is published. A message appears on the HMI.\nBehind that small API call, something must do the actual work. There is usually one service instance that owns the connection to the outside world. It may talk to Windows, ADS, MQTT, an HMI, a database, or another task.\nThe awkward part is access to that service instance. Passing a reference through every layer of the PLC project works, but it pollutes the application structure. Suddenly every machine module knows about the logger, the alarm service, or the MQTT publisher. A fine way to turn infrastructure into everybody\u0026rsquo;s problem.\nBeckhoff APIs often avoid this visible dependency. The call site does not show where the service instance lives. This article shows one way to implement a similar pattern in a TwinCAT library by using a hidden global reference.\nThis is still global state. The point is not to pretend otherwise. The point is to make the dependency small, controlled, and limited to infrastructure-style services.\nDefine the API Interface Assume the library is called msglib. First define the interface that application code uses to send a message.\nINTERFACE IMessage METHOD send VAR_INPUT message : STRING(255); END_VAR END_INTERFACE The application should depend on this small API. It should not need to know whether the message is written to a log file, shown on an HMI, or sent with MQTT.\nDefine the Publisher Interface The actual transport is handled by a publisher. It has a separate interface.\nINTERFACE IPublisher METHOD publish VAR_INPUT data : STRING(255); END_VAR END_INTERFACE Keeping IMessage and IPublisher separate avoids mixing the public API with the implementation detail. The message API can stay stable even if the publisher changes.\nStore the Hidden Reference Create a global variable list named RefBase inside the library. The GVL contains the publisher interface reference.\nVAR_GLOBAL {attribute \u0026#39;hide\u0026#39;} messenger : IPublisher; END_VAR The {attribute 'hide'} pragma hides the variable from normal external access. That keeps the application project from using RefBase.messenger directly.\nThere is a trade-off. The reference is also harder to inspect online.\nRegister the Publisher Now implement a publisher and register it during an explicit initialization phase.\nFUNCTION_BLOCK Publisher IMPLEMENTS IPublisher VAR initialized : BOOL; END_VAR METHOD init IF NOT initialized THEN RefBase.messenger := THIS^; initialized := TRUE; END_IF END_METHOD METHOD publish VAR_INPUT data : STRING(255); END_VAR // Send the data to the real transport here. END_METHOD The application should create exactly one active publisher instance for this pattern. If multiple publishers register themselves, the last one wins. That may be intentional in a test setup, but it is usually a bug in a production machine.\nRegistration can also be done in FB_init, but that needs care. FB_init runs automatically and is called again during online change. It also does not give the application an obvious place to control initialization order. An explicit init() method is usually easier to reason about and easier to test.\nImplement the Message API The API function block uses the hidden publisher reference.\nFUNCTION_BLOCK Message IMPLEMENTS IMessage METHOD send VAR_INPUT message : STRING(255); END_VAR IF RefBase.messenger \u0026lt;\u0026gt; 0 THEN RefBase.messenger.publish(message); END_IF END_METHOD The validity check is important. An interface variable can be 0. If the message API is called before the publisher is registered, the code must not blindly call through the reference.\nThis example silently drops the message when no publisher is available. That keeps the code small, but it is not enough for most production systems. A real implementation should make the fault observable with a diagnostic flag, counter, event, or error state.\nUse It from the Application The application owns the concrete publisher instance and calls init() during startup.\nPROGRAM MAIN VAR publisher : Publisher; messageApi : Message; initialized : BOOL; END_VAR IF NOT initialized THEN publisher.init(); messageApi.send(\u0026#39;Machine started\u0026#39;); initialized := TRUE; END_IF After initialization, any function block that receives an IMessage reference, or owns a Message instance, can send messages without knowing about the publisher instance.\nLimitations This pattern is useful, but it is not free.\nConsider these limitations before using it:\nIt is still global state, only hidden behind a small API. Initialization order matters. The publisher must be registered before messages are sent. Online-change behavior must be tested. Only one active publisher should normally register itself. Missing references need diagnostics. Silently dropping messages can hide real faults. Hidden references are harder to debug than explicit dependencies. Use this pattern for infrastructure services that are naturally shared across the project: logging, alarms, events, diagnostics, or message publishing. Do not use it to share random machine state between unrelated function blocks. That is not architecture. That is a future incident report with syntax highlighting.\nConclusion A hidden global reference can make a TwinCAT API easier to use when many parts of the application need access to one shared service, such as logging, alarms, events, or MQTT publishing.\nThe benefit is a cleaner application interface. Function blocks can call a small API without carrying a publisher reference through every layer of the project.\nThe cost is hidden coupling. The publisher must be registered before the API is used, online-change behavior must be tested, and missing references must be handled explicitly. The {attribute 'hide'} pragma hides the variable from normal use, but it does not remove the dependency.\nUse this pattern for infrastructure-style services with one well-defined implementation instance. Do not use it as a general escape hatch for sharing arbitrary state across the PLC project.\n","date":"June 6, 2026","externalUrl":null,"permalink":"/posts/p008/","section":"Posts","summary":"","title":"TwinCAT: Implementing an API with a Hidden Global Reference","type":"posts"},{"content":"","date":"June 5, 2026","externalUrl":null,"permalink":"/tags/analog-input/","section":"Tags","summary":"","title":"Analog-Input","type":"tags"},{"content":"","date":"June 5, 2026","externalUrl":null,"permalink":"/tags/normalization/","section":"Tags","summary":"","title":"Normalization","type":"tags"},{"content":"In PLC programs, raw analog values often travel much farther than they should.\nAn analog input may deliver a value from 0 to 1023. The value is then passed into a PID controller, an HMI bar graph, a valve command, or a drive command. Before long, the number 1023 appears everywhere.\nvaluePid := LIMIT(0.0, valuePid, 1023.0); valueBarGraph := 100.0 * valuePid / 1023.0; The problem is not the calculation itself. The problem is the convention hidden inside it.\nThe code assumes that 1023 is the maximum value of the signal. If that assumption is used in one place, it is manageable. If it is spread over ten thousand lines, the range of one input terminal has become part of the architecture. An impressive achievement, if the goal was to make a hardware detail everybody\u0026rsquo;s problem.\nThe better approach is to normalize the value once, close to the boundary of the system.\nThe Boundary Should Know the Raw Range Normalization means converting one range into another common range.\nFor control signals, a range from 0.0 to 1.0 is often practical. It can be interpreted as a relative command, or as a percentage divided by 100.\nThe raw ADC range stays near the input handling code. The rest of the application receives a normalized value.\nInstead of this:\nvalueBarGraph := 100.0 * valuePid / 1023.0; the application can work with this:\nvalueBarGraph := 100.0 * valueNormalized; The important change is not saving one division. The important change is that the rest of the code no longer needs to know whether the input came from a 10-bit ADC, a 12-bit ADC, an EtherCAT terminal, a simulation value, or a fieldbus device with a vendor-specific range.\nA Linear Transform The general transform from one range to another is a linear equation.\noutput = (outputMax - outputMin) / (inputMax - inputMin) * (input - inputMin) + outputMin In Structured Text, this can be implemented as a small function.\nFUNCTION transformLinear : LREAL VAR_INPUT valueInput : LREAL; valueInputMin : LREAL; valueInputMax : LREAL; valueOutputMin : LREAL; valueOutputMax : LREAL; limitOutput : BOOL; END_VAR IF ABS(valueInputMax - valueInputMin) \u0026gt; 1E-9 THEN transformLinear := (valueOutputMax - valueOutputMin) / (valueInputMax - valueInputMin) * (valueInput - valueInputMin) + valueOutputMin; IF limitOutput THEN IF valueOutputMax \u0026gt;= valueOutputMin THEN transformLinear := LIMIT( valueOutputMin, transformLinear, valueOutputMax); ELSE transformLinear := LIMIT( valueOutputMax, transformLinear, valueOutputMin); END_IF END_IF ELSE transformLinear := valueOutputMin; END_IF The function also handles reversed output ranges and avoids division by zero. Returning the minimum output value for an invalid input range is a design choice. In production code, the caller may also need an error state or diagnostic flag. Quietly returning a number is not always enough.\nA Potentiometer Example Assume a potentiometer is connected to an analog input. The raw value is converted into a relative value from 0.0 to 1.0.\nFUNCTION_BLOCK Potentiometer VAR valueRelative : LREAL; END_VAR VAR CONSTANT VALUE_RAW_MIN : LREAL := 0.0; VALUE_RAW_MAX : LREAL := 1023.0; END_VAR The update method performs the conversion.\nMETHOD update VAR_INPUT valueRaw : DINT; END_VAR valueRelative := transformLinear( valueInput := TO_LREAL(valueRaw), valueInputMin := VALUE_RAW_MIN, valueInputMax := VALUE_RAW_MAX, valueOutputMin := 0.0, valueOutputMax := 1.0, limitOutput := TRUE); The public property returns the normalized value.\nPROPERTY Value : LREAL Getter:\nValue := valueRelative; From this point on, the rest of the application does not use 1023. It uses potentiometer.Value.\nThat value has a simple contract:\n0.0 = minimum 1.0 = maximum Use the Normalized Contract A variable frequency drive can expose an operation method with relative values.\nINTERFACE IDrive METHOD executeOperation : BOOL VAR_INPUT speedRelative : LREAL; torqueRelative : LREAL; END_VAR Both inputs use the same convention:\n0.0 = no command 1.0 = maximum configured command Now the potentiometer can be connected to different meanings without changing its own implementation.\nCASE choice OF 1: // Fixed speed, variable torque. drive.executeOperation( speedRelative := 0.1, torqueRelative := potentiometer.Value); 2: // Variable speed, full torque. drive.executeOperation( speedRelative := potentiometer.Value, torqueRelative := 1.0); ELSE drive.executeOperation( speedRelative := 0.0, torqueRelative := 0.0); END_CASE This works because both sides agree on the normalized range. The potentiometer does not need to know the maximum speed of the drive. The drive does not need to know the ADC range of the potentiometer.\nThe conversion back to an engineering value happens inside the drive or close to the drive.\nspeedSetpoint := speedRelative * speedMax; torqueSetpoint := torqueRelative * torqueMax; The raw range and the physical range stay at the boundaries. The application logic works with the relative command.\nWhere Normalization Helps Normalized values are useful when a signal represents a relative command or relative feedback.\nTypical examples are analog inputs, valve positions, drive speed commands, torque limits, PID outputs, HMI sliders, and simulation inputs.\nPID controllers are a good example. If the output is normalized to 0.0..1.0, it can be connected directly to a relative valve position, torque command, or speed command. The controller does not need to know whether the final actuator is a proportional valve, a VFD, or a simulated load.\nThe same idea also helps testing. A test can set a fake potentiometer to 0.25 without knowing the raw ADC value. The test checks behavior against the application contract, not against one hardware terminal.\nLimits Normalization is not a reason to delete engineering units from the program.\nPressure, temperature, length, speed, and force should often be represented in engineering units inside the application. A pressure controller should usually reason about bar or pascals, not only about 0.0..1.0.\nNormalized values are best for relative commands and generic interfaces. Engineering values are better when the physical meaning matters.\nThe rule is simple:\nNormalize hardware-specific ranges at the boundary. Keep physical values in engineering units when the physics matters.\nAlso document the convention. If speedRelative = 1.0 means speedMax, then speedMax must be visible and well defined. If a signal can exceed the normal range, decide whether to clamp it, report it, or use it for diagnostics.\nConclusion Normalization prevents hardware ranges from leaking through the whole PLC project.\nThe ADC limit belongs near the analog input. The maximum speed belongs near the drive. The application logic between them should depend on a clear contract, not on scattered constants.\nFor many PLC systems, 0.0..1.0 is a useful range for relative signals. It makes interfaces simpler, tests easier, and code less dependent on one piece of hardware.\nThe practical rule is boring, which is usually a good sign:\nConvert once at the boundary. Use the normalized value in the application. Convert back only where the physical output requires it.\n","date":"June 5, 2026","externalUrl":null,"permalink":"/posts/p007/","section":"Posts","summary":"","title":"Normalize PLC Values at the System Boundary","type":"posts"},{"content":"Testing PLC code is difficult when every function block creates its own world internally.\nThis often happens in older IEC 61131-3 projects. A hydraulic power unit function block instantiates its valve blocks directly. A winch function block instantiates its brake, clutch, sensors, and valves directly. The structure looks convenient at first because fewer variables need to be linked at the program level.\nThe cost appears later.\nIf the winch state machine depends on internally created brakes, clutches, sensors, and valves, then testing the winch means testing the whole physical system around it. The test requires hardware or a large simulation of everything inside the block. The logic cannot be tested in isolation because the dependencies are hidden inside the implementation.\nThat is the problem dependency injection solves.\nThe idea is simple:\nA function block should not create every object it depends on. Some dependencies should be passed into it from the outside.\nIn TwinCAT, this becomes useful when combined with interfaces.\nStart With the Dependency Assume a controller needs a pressure value. The controller should not care whether the value comes from a real analog input, a fieldbus device, a simulation model, or a test object.\nIt only needs a sensor contract.\nINTERFACE ISensor The interface exposes one property:\nPROPERTY Value : LREAL The real sensor implements the interface.\nFUNCTION_BLOCK PressureSensor IMPLEMENTS ISensor VAR valueActual : LREAL; END_VAR The cyclic method can read hardware, apply scaling, or process diagnostics. The caller does not need to know those details.\nMETHOD update valueActual := 123.4; The property getter returns the measured value.\nValue := valueActual; Now a variable of type ISensor can refer to any function block that implements this interface.\nVAR pressureSensor : PressureSensor; sensor : ISensor; pressure : LREAL; END_VAR sensor := pressureSensor; IF sensor \u0026lt;\u0026gt; 0 THEN pressure := sensor.Value; END_IF This is already useful. The controller can depend on ISensor instead of PressureSensor. But the real benefit appears when testing starts.\nReplace the Real Sensor For a test, create a fake sensor that implements the same interface.\nFUNCTION_BLOCK SensorFake IMPLEMENTS ISensor VAR valueActual : LREAL; END_VAR The fake exposes the same Value property.\nValue := valueActual; It also provides a method to set test values.\nMETHOD setValue VAR_INPUT value : LREAL; END_VAR valueActual := value; The production code reads ISensor.Value. It does not know whether the instance is a real sensor or a fake sensor.\nThat is the useful abstraction. The controller depends on behavior, not on the concrete implementation.\nThe same pattern works for actuators. A valve can be represented by an interface.\nINTERFACE IValve For example:\nMETHOD open METHOD close PROPERTY IsOpen : BOOL A real valve writes outputs. A fake valve stores the requested state in memory. The controller can use both through the same interface.\nInject the Dependency The next step is to pass the required objects into the controller.\nThe controller stores interface references internally.\nFUNCTION_BLOCK PressureController VAR sensor : ISensor; valve : IValve; initialized : BOOL; END_VAR Use an explicit init() method to connect the dependencies.\nMETHOD init : BOOL VAR_INPUT sensorInput : ISensor; valveInput : IValve; END_VAR IF sensorInput = 0 OR valveInput = 0 THEN initialized := FALSE; init := FALSE; RETURN; END_IF sensor := sensorInput; valve := valveInput; initialized := TRUE; init := TRUE; The cyclic logic uses only the interfaces.\nMETHOD update VAR CONSTANT PRESSURE_LIMIT : LREAL := 100.0; END_VAR IF NOT initialized THEN RETURN; END_IF IF sensor \u0026lt;\u0026gt; 0 AND_THEN valve \u0026lt;\u0026gt; 0 THEN IF sensor.Value \u0026gt; PRESSURE_LIMIT THEN valve.open(); ELSE valve.close(); END_IF END_IF In the real application, pass the real objects.\nVAR pressureSensor : PressureSensor; pressureValve : PressureValve; controller : PressureController; initialized : BOOL; END_VAR IF NOT initialized THEN initialized := controller.init( sensorInput := pressureSensor, valveInput := pressureValve); END_IF pressureSensor.update(); controller.update(); In a test application, pass fake objects.\nVAR sensorFake : SensorFake; valveFake : ValveFake; controller : PressureController; initialized : BOOL; END_VAR IF NOT initialized THEN initialized := controller.init( sensorInput := sensorFake, valveInput := valveFake); END_IF sensorFake.setValue(120.0); controller.update(); The controller code does not change. Only the objects passed into it change.\nThat is dependency injection in a PLC context. Not magic. Not a framework. Just a controlled way to provide dependencies from the outside instead of hiding them inside the block.\nWhat This Changes Dependency injection makes testing practical because the tested block can be separated from real hardware.\nYou can feed sensor values directly into a fake sensor. You can observe whether a fake valve was opened or closed. You can test transitions in a state machine without connecting a real hydraulic unit.\nThis changes the design pressure on the code. Function blocks must become smaller. Their interfaces must be clear. Dependencies must be visible. Large blocks that instantiate half the machine internally become harder to justify. Tragic, I know. The monster block had such character.\nThere are also practical limits.\nAll instances are still statically allocated. Dependency injection in TwinCAT does not mean dynamic object creation. It means that the application creates the instances and then passes interface references to the blocks that need them.\nInterface references must be checked before use.\nIF sensor \u0026lt;\u0026gt; 0 THEN pressure := sensor.Value; END_IF Initialization order also matters. The top-level application must create and initialize the objects in a defined order. Do not hide complex wiring in FB_init() unless there is a very specific reason. FB_init() is called automatically and also runs during online changes. For most application logic, an explicit init() method is easier to control and easier to test.\nThe interface does not remove the need for a protocol. If IValve.open() is called, the expected behavior must still be defined. Is it a command? A request? Is feedback required? How are errors reported? The compiler checks that the method exists. It does not understand your hydraulic concept. Rude, but consistent.\nConclusion Dependency injection is valuable in PLC programming because it makes dependencies visible.\nA controller that depends directly on a concrete sensor and a concrete valve is difficult to test without those objects. A controller that depends on ISensor and IValve can use real devices in the machine and fake devices in a test.\nThat makes testing before commissioning more realistic. It also improves the structure of the PLC application because function blocks become smaller and their contracts become clearer.\nThe practical rule is simple:\nCreate objects outside the block. Pass required behavior in through interfaces. Test the block with fake implementations.\n","date":"June 5, 2026","externalUrl":null,"permalink":"/posts/p006/","section":"Posts","summary":"","title":"Dependency Injection in PLC Programming Makes Testing Possible","type":"posts"},{"content":"","date":"June 5, 2026","externalUrl":null,"permalink":"/tags/oop/","section":"Tags","summary":"","title":"Oop","type":"tags"},{"content":"","date":"June 5, 2026","externalUrl":null,"permalink":"/tags/testing/","section":"Tags","summary":"","title":"Testing","type":"tags"},{"content":"","date":"June 5, 2026","externalUrl":null,"permalink":"/tags/bitbucket/","section":"Tags","summary":"","title":"Bitbucket","type":"tags"},{"content":"SSH key authentication is simple when there is only one account.\nCreate a key pair. Add the public key to GitHub or Bitbucket. Clone the repository. Done.\nThe problem starts when one machine needs access to more than one account on the same service.\nFor example:\na private GitHub account a company GitHub account a customer GitHub organization one or more Bitbucket workspaces All GitHub repositories still use the same real host:\ngithub.com If all keys are thrown into ~/.ssh, SSH may try the wrong key first. Authentication then depends on key order, agent state, and other details that are easy to forget. Very modern: security by drawer full of mystery files.\nA cleaner solution is to use SSH host aliases and one small config file per account.\nThe Basic Idea SSH does not have to connect directly to the host name written in the Git URL. The Host entry in ~/.ssh/config can define an alias.\nThis means github.com-private can point to the real host github.com, but use a specific private key.\nThe Git remote then uses the alias:\ngit@github.com-private:ralph/project.git instead of:\ngit@github.com:ralph/project.git The alias selects the account-specific SSH key.\nDirectory Layout Keep each account in its own directory.\nExample:\n~/.ssh/ ├── config ├── github-private/ │ ├── config │ ├── key │ ├── key.pub │ └── readme.md └── github-company/ ├── config ├── key ├── key.pub └── readme.md The top-level config file only includes the account-specific config files.\nInclude ~/.ssh/github-private/config Include ~/.ssh/github-company/config This keeps the main file short. It also makes it obvious which key belongs to which account.\nThe readme.md file is optional, but useful. Write down what the key is for:\nGitHub private account for Ralph. Public key added to https://github.com/settings/keys. Created: 2026-06-05. Future you will appreciate this. Future you is usually the person asking why a random file called id_rsa_old2_final exists.\nCreate the Keys Create one key pair per account.\nFor a private GitHub account:\nssh-keygen -t ed25519 -f ~/.ssh/github-private/key -C \u0026#34;github-private\u0026#34; For a company GitHub account:\nssh-keygen -t ed25519 -f ~/.ssh/github-company/key -C \u0026#34;github-company\u0026#34; Add each .pub file to the matching account in GitHub or Bitbucket.\nFor GitHub, this is usually:\nSettings → SSH and GPG keys → New SSH key Do not add the same key to multiple personal accounts. Use separate keys. It makes access control and cleanup much easier.\nConfigure the Private Account Create this file:\n~/.ssh/github-private/config Content:\nHost github.com-private HostName github.com User git IdentityFile ~/.ssh/github-private/key IdentitiesOnly yes The important parts are:\nHost github.com-private defines the alias. HostName github.com defines the real server. User git is the SSH user used by GitHub. IdentityFile selects the private key. IdentitiesOnly yes prevents SSH from trying every key in the agent. Configure the Company Account Create this file:\n~/.ssh/github-company/config Content:\nHost github.com-company HostName github.com User git IdentityFile ~/.ssh/github-company/key IdentitiesOnly yes The two configs point to the same real server, but they use different aliases and different keys.\nFile Permissions on Linux and macOS OpenSSH is strict about private key permissions.\nUse:\nchmod 700 ~/.ssh chmod 700 ~/.ssh/github-private ~/.ssh/github-company chmod 600 ~/.ssh/config chmod 600 ~/.ssh/github-private/config ~/.ssh/github-company/config chmod 600 ~/.ssh/github-private/key ~/.ssh/github-company/key chmod 644 ~/.ssh/github-private/key.pub ~/.ssh/github-company/key.pub The private key must not be readable by other users.\nOn Windows, the same layout works with the built-in OpenSSH client and PowerShell. The home directory is available as ~, for example:\ncd ~ The SSH directory is normally:\nC:\\Users\\\u0026lt;user\u0026gt;\\.ssh Windows file permissions are different, so chmod is only relevant when using a Unix-like environment such as WSL or Git Bash.\nTest the Configuration Test the private account:\nssh -T git@github.com-private Or, because User git is already configured:\nssh -T github.com-private For GitHub, a successful login looks like this:\nHi \u0026lt;username\u0026gt;! You\u0026#39;ve successfully authenticated, but GitHub does not provide shell access. For detailed debugging, use:\nssh -Tv github.com-private Look for lines showing which config file and identity file are used.\nThen test the company account:\nssh -T github.com-company If the wrong account responds, check these items first:\nIs the correct public key added to the correct GitHub account? Does the alias in the Git remote match the Host entry? Is IdentitiesOnly yes set? Is the private key path correct? Are the file permissions acceptable to OpenSSH? Use the Alias in Git Remotes When cloning, replace the real host name with the alias.\nPrivate account:\ngit clone git@github.com-private:ralph/project.git Company account:\ngit clone git@github.com-company:company/important-project.git For an existing repository, update the remote URL.\nCheck the current URL:\ngit remote -v Set the new URL:\ngit remote set-url origin git@github.com-company:company/important-project.git After that, normal Git commands use the configured key:\ngit fetch git pull git push Bitbucket Works the Same Way The same pattern works for Bitbucket.\nExample:\nHost bitbucket.org-company HostName bitbucket.org User git IdentityFile ~/.ssh/bitbucket-company/key IdentitiesOnly yes Clone with:\ngit clone git@bitbucket.org-company:company/project.git The exact repository path depends on the service and workspace, but the SSH principle is the same.\nWhy This Is Cleaner This pattern has several advantages:\neach account has one dedicated directory each account has one dedicated key pair the main SSH config stays small Git remotes explicitly show which account is used old keys can be removed without guessing their purpose more accounts can be added without turning ~/.ssh into a junk drawer It also avoids relying on SSH agent key order. That matters when a developer has many keys loaded, which is common on machines used for private work, company work, and customer projects.\nLimitations This does not replace proper access management.\nRemove unused keys from GitHub or Bitbucket. Rotate keys when a machine is lost. Do not share private keys between users. Do not copy one private key across many machines unless there is a deliberate reason.\nFor company environments, follow the organization policy. Some teams prefer managed developer devices, SSO, short-lived credentials, or hardware-backed keys. SSH config aliases do not replace those controls.\nConclusion Multiple GitHub or Bitbucket accounts are easier to manage when each account has its own SSH key and its own config file.\nThe useful trick is the Host alias:\nHost github.com-company HostName github.com User git IdentityFile ~/.ssh/github-company/key IdentitiesOnly yes Then use the alias in the Git remote:\ngit@github.com-company:company/project.git This keeps authentication explicit. It also makes the setup easier to understand months later, when nobody remembers which key was created for which account. Which, obviously, would never happen to us.\n","date":"June 5, 2026","externalUrl":null,"permalink":"/posts/p005/","section":"Posts","summary":"","title":"Configure SSH Cleanly for Multiple GitHub Accounts","type":"posts"},{"content":"","date":"June 5, 2026","externalUrl":null,"permalink":"/tags/developer-tools/","section":"Tags","summary":"","title":"Developer-Tools","type":"tags"},{"content":"","date":"June 5, 2026","externalUrl":null,"permalink":"/tags/git/","section":"Tags","summary":"","title":"Git","type":"tags"},{"content":"","date":"June 5, 2026","externalUrl":null,"permalink":"/tags/github/","section":"Tags","summary":"","title":"Github","type":"tags"},{"content":"","date":"June 5, 2026","externalUrl":null,"permalink":"/categories/software-engineering/","section":"Categories","summary":"","title":"Software Engineering","type":"categories"},{"content":"","date":"June 5, 2026","externalUrl":null,"permalink":"/tags/ssh/","section":"Tags","summary":"","title":"Ssh","type":"tags"},{"content":"TwinCAT and CODESYS support object-oriented extensions for IEC 61131-3 Structured Text. That does not mean every PLC program should be written like a desktop application.\nThe useful question is smaller:\nWhen should a PLC programmer use inheritance, and when should a PLC programmer use interfaces?\nThe short answer is this:\nUse inheritance for small, stable hierarchies. Use interfaces for behavior that must work across different devices.\nFunction Blocks as Classes In TwinCAT, a function block can act like a class. It has internal state. It can expose methods. It can expose properties. The cyclic body or an explicit update() method can process its behavior.\nA simple light could expose one property:\nFUNCTION_BLOCK Light VAR _on : BOOL; END_VAR The property On controls the internal state or a mapped output.\nPROPERTY On : BOOL Getter:\nOn := _on; Setter:\n_on := On; Usage stays simple:\nVAR light : Light; END_VAR light.On := TRUE; So far, this is not revolutionary. It is only a function block with a cleaner public API. The interesting part starts when the system contains different kinds of lights.\nSome lights are only on or off. Some are dimmable. Some have RGB channels. Some may be virtual devices used for simulation or testing.\nPutting every variant into one large Light function block is possible. It is also how small examples become large maintenance problems. The block grows flags, unused variables, special cases, and customer-specific branches. Very elegant, if the goal is job security through confusion.\nInheritance Inheritance lets one function block extend another function block.\nA dimmable light can extend a basic light:\nFUNCTION_BLOCK LightDimmable EXTENDS Light VAR dimmingValue : LREAL; END_VAR The derived block inherits the public members of Light and adds its own property.\nPROPERTY DimmingValue : LREAL Usage:\nVAR light : LightDimmable; dimmingValue : LREAL; END_VAR light.On := TRUE; light.DimmingValue := 0.3; dimmingValue := light.DimmingValue; This works well when the relationship is stable:\nA dimmable light is a light.\nIt also allows a reference to the base type to refer to an instance of the derived type.\nVAR lightSimple : Light; lightDimmable : LightDimmable; lightReference : REFERENCE TO Light; END_VAR lightReference REF= lightDimmable; IF __ISVALIDREF(lightReference) THEN lightReference.On := TRUE; END_IF The reverse does not work. A reference to LightDimmable cannot point to a plain Light, because a plain Light has no DimmingValue property.\nThat is correct. It is also the first important limitation.\nThe base class starts to define the shape of the hierarchy. If more variants appear, the hierarchy can become awkward:\ndimmable light RGB light dimmable RGB light simulated light diagnostic light vendor-specific light At that point, inheritance is no longer solving the problem. It is organizing the problem into a tree and hoping the real world behaves like a tree. It often does not.\nWhere Inheritance Fits Inheritance is useful when the hierarchy is shallow and stable.\nGood candidates are small families with clear relationships:\nbasic indicator and specialized indicator base simulation block and one test implementation common diagnostic base with a few narrow extensions The practical rule is:\nIf the hierarchy needs a diagram to understand, it is probably already too deep.\nFor PLC frameworks, inheritance should usually stay one level deep. It should not become the main tool for sharing behavior across unrelated devices.\nInterfaces An interface defines what a function block must provide. It does not define how the function block works internally.\nFor a switchable device, the interface could be:\nINTERFACE ISwitchable With one property:\nPROPERTY On : BOOL A light can implement it:\nFUNCTION_BLOCK Light IMPLEMENTS ISwitchable VAR on : BOOL; END_VAR A ventilator can implement the same interface:\nFUNCTION_BLOCK Ventilator IMPLEMENTS ISwitchable VAR on : BOOL; END_VAR A ventilator is not a light. But both can be switched on and off. That is the point.\nThe interface describes behavior the caller may use. It avoids forcing unrelated devices into the same inheritance tree.\nInterface References An interface variable can refer to any instance that implements the interface.\nVAR device : ISwitchable; light : Light; ventilator : Ventilator; END_VAR device := light; IF device \u0026lt;\u0026gt; 0 THEN device.On := TRUE; END_IF device := ventilator; IF device \u0026lt;\u0026gt; 0 THEN device.On := FALSE; END_IF The caller does not need to know whether the device is a light, fan, pump, or simulated object. It only needs the contract:\nThis object has an On property with the agreed meaning.\nThat last part matters. The compiler checks that the property exists. It does not check the semantic convention. A troll implementation could switch the output off when On is set to TRUE. The compiler will not care. Your machine might.\nInterfaces define syntax. The engineering team must still define the protocol.\nKeep Interfaces Small Interfaces should be focused. One interface should describe one capability.\nExamples:\nINTERFACE ISwitchable INTERFACE IDimmable EXTENDS ISwitchable INTERFACE IRequestOperation INTERFACE IStartStopOperation Do not create one large interface for every possible device feature. That only recreates the oversized function block problem with different syntax.\nA useful interface answers one question:\nWhat does the caller need from this object?\nIf a caller only needs to switch devices on and off, use ISwitchable. Do not force it to know about dimming, diagnostics, simulation mode, motor currents, RGB colors, and whatever the next project meeting invents.\nOptional Capabilities Interfaces also help when some devices support additional behavior.\nAssume a room contains several switchable devices. Some are dimmable. Others are not.\nThe basic operation can use ISwitchable:\nVAR CONSTANT LIGHT_COUNT : DINT := 8; END_VAR VAR lights : ARRAY[0..LIGHT_COUNT - 1] OF ISwitchable; N : DINT; value : BOOL; END_VAR FOR N := 0 TO LIGHT_COUNT - 1 DO IF lights[N] \u0026lt;\u0026gt; 0 THEN lights[N].On := value; END_IF END_FOR To apply dimming only where supported, query the additional interface:\nVAR dimmable : IDimmable; dimmingValue : LREAL; END_VAR FOR N := 0 TO LIGHT_COUNT - 1 DO IF lights[N] \u0026lt;\u0026gt; 0 THEN IF __QUERYINTERFACE(lights[N], dimmable) THEN dimmable.DimmingValue := dimmingValue; END_IF END_IF END_FOR The caller does not need a separate array for every light type. It can operate on common behavior and query optional behavior only when needed.\nThis is useful in automation frameworks because real systems rarely contain perfectly uniform devices. A project may contain standard hardware, optional hardware, simulated hardware, and customer-specific devices in the same control concept.\nInterfaces as Communication Protocols An interface is more than a list of methods and properties. In a PLC framework, it becomes part of the communication protocol between function blocks.\nFor example, there are at least two different ways to put a device into operation.\nOne interface may describe commands:\nINTERFACE IStartStopOperation METHOD start METHOD stop Another may describe cyclic intent:\nINTERFACE IRequestOperation METHOD requestOperation Both can be valid. They do not mean the same thing.\nstart() is an event. It says something should happen now. requestOperation() can mean that operation is requested as long as the method is called cyclically. That difference is important for watchdogs, communication loss, and state-machine behavior.\nThe interface alone does not explain all of this. The convention must be documented and used consistently.\nPractical Guidelines Use inheritance when:\nthe relationship is a clear is-a relationship the hierarchy is shallow the base behavior is stable derived blocks should really inherit the base API Use interfaces when:\ndifferent devices share a behavior but not an implementation a caller should depend on capability, not concrete type optional features must be queried at runtime a framework needs replaceable components simulation and real hardware should use the same protocol Avoid both when a plain function block or composition is simpler. OOP is a tool, not a certificate of adulthood.\nLimitations There are several practical limits.\nFirst, interface references must be checked before use.\nIF device \u0026lt;\u0026gt; 0 THEN device.On := TRUE; END_IF Second, a REFERENCE TO must be checked with __ISVALIDREF() before access.\nIF __ISVALIDREF(lightReference) THEN lightReference.On := TRUE; END_IF Third, OOP does not remove PLC constraints. Code still runs in scan cycles. It must stay deterministic. It must be easy to debug online. It must not hide timing behavior behind nice-looking abstractions.\nFourth, interfaces do not replace documentation. They define the callable surface. They do not fully define the expected behavior, timing, error handling, or reset semantics.\nConclusion Inheritance and interfaces are both useful in TwinCAT Structured Text, but they solve different problems.\nInheritance shares structure and behavior inside a small hierarchy. It is useful when the relationship is stable and obvious.\nInterfaces define contracts. They are better when different objects must provide the same capability without sharing the same base class.\nFor PLC frameworks, interfaces usually scale better. They let the application depend on behavior instead of concrete implementation. They also make simulation, testing, and customer-specific variation easier to manage.\nThe best design usually starts before coding:\nDefine the protocol first. Then implement the devices behind it.\n","date":"June 5, 2026","externalUrl":null,"permalink":"/posts/p004/","section":"Posts","summary":"","title":"TwinCAT OOP: When to Use Inheritance and Interfaces","type":"posts"},{"content":"","date":"June 5, 2026","externalUrl":null,"permalink":"/tags/automation/","section":"Tags","summary":"","title":"Automation","type":"tags"},{"content":" In automation, INT and REAL are still common default types. That made sense when PLCs had limited memory and slower CPUs.\nModern PLCs are different. Many systems now run on 32-bit or 64-bit CPUs with enough memory that the old defaults deserve another look. In many applications, DINT and LREAL are better internal types.\nFloating Point The difference between LREAL and REAL matters in scaling, filtering, integration, and coordinate transformations.\nA 32-bit REAL has 24 bits of significand precision, including the implicit leading bit. Around \\(1.0\\), the distance to the next representable value is \\(2^{-23}\\). This distance is one ULP1.\nA 64-bit LREAL has 53 bits of significand precision. Around \\(1.0\\), one ULP is \\(2^{-52}\\). That gives LREAL \\(2^{29}\\) times finer spacing around \\(1.0\\) than REAL.\nBefore addition or subtraction, floating-point values are aligned to the same exponent. A normalized binary value like \\(100.0_2\\) is represented as \\(1.0_2 \\times 2^2\\). If the operands have very different magnitudes, the smaller operand may no longer affect the stored result after rounding.\nFor example, with a 32-bit REAL and the usual round-to-nearest mode:\n\\[ \\begin{align*} 1.0 + 2^{-24} \u0026\\,=\\, 1.0 \\end{align*} \\]The value \\(2^{-24}\\) is half an ULP at \\(1.0\\). It is too small to change the stored REAL value. With LREAL, the same addition is still far above the rounding limit.\nSubtraction has a different problem. If two operands are almost equal, the leading digits cancel. The result may be mathematically correct, but it can contain few meaningful bits. This is called cancellation.\nA transformation like\n\\[ \\begin{align*} \\frac{a}{b-c} \\end{align*} \\]can become numerically fragile when \\(b-c\\) is close to zero. LREAL does not fix a bad formula, but it gives more precision before rounding becomes visible.\nThe exact intermediate precision depends on the CPU, compiler, and runtime. The important point is simpler: once a value is stored as REAL, it is rounded to 32-bit precision. If the application performs physical scaling or intermediate calculations, LREAL is usually the safer internal type.\nInteger The argument for DINT is less about numerical precision and more about practical range.\nIn IEC 61131-3, INT is commonly a signed 16-bit integer. That gives a range from \\(-32768\\) to \\(32767\\). This is enough for many field values, but it is small for counters, indexes, intermediate calculations, scaled values, and accumulated totals.\nDINT is a signed 32-bit integer. It can represent signed and unsigned 16-bit field values without changing the internal type. It also covers common 24-bit ADC values without forcing a redesign of the calculation chain.\nThis does not mean every variable should be a DINT. Hardware mappings, protocol data, and packed structures should use the exact type required by the interface. But inside the application logic, DINT is often the more robust default.\nConclusion INT and REAL are still valid types, but they should not be automatic defaults in modern PLC projects.\nUse DINT when a value is a general-purpose integer. It avoids tight 16-bit limits and gives enough range for most counters, indexes, and intermediate calculations.\nUse LREAL when a value represents a physical quantity, a scaling result, or an intermediate calculation. The extra precision is cheap on modern hardware and can prevent avoidable rounding errors.\nKeep narrow types at the system boundary. Use the type required by the fieldbus, hardware register, protocol, or memory layout. Then convert to a more robust internal type for the application logic.\nThe practical rule is simple:\nUse exact types at the boundary. Use robust types inside the application.\nUnit in the Last Place\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"June 5, 2026","externalUrl":null,"permalink":"/posts/p003/","section":"Posts","summary":"","title":"Automation: Using DINT and LREAL as fundamental types","type":"posts"},{"content":"","date":"June 5, 2026","externalUrl":null,"permalink":"/tags/st/","section":"Tags","summary":"","title":"ST","type":"tags"},{"content":"Traditional automation for fishing vessels has long relied on basic joystick panels to operate deck equipment like winches. But what happens when multiple users try to take control from different panels? The conventional solution—manual selector switches on the bridge—puts extra pressure on the skipper, who’s already juggling navigation and supervising the catch.\nAt Setpoint, we solved this with intelligent control allocation. Our system automatically manages access to deck devices without burdening the skipper or requiring hard-coded project logic. Any panel can take control from another without interrupting ongoing automatic operations—perfect for situations like a snagged trawl net or a fish pump that needs manual override.\nZero Project-Specific Code. Fully Configurable.\nA decade ago, each control panel was bloated with complex ladder code and cryptic acronyms. Debugging was painful, error-prone, and costly. We replaced that chaos with a fully configurable logic layer—no custom code required per project.\nUsers can now operate winches, cranes, and remote panels dynamically. When a deckhand selects a different winch, the system safely deactivates the previous one. It’s all handled through configuration, not rewriting logic.\nIntelligent Inverter Sharing Space-saving through inverter sharing became common in electrical winch setups. But when one winch needs four inverters, and two were already in use elsewhere, how do you manage it? Our logic dynamically allocates inverters based on demand. No switches. No manual intervention. Just seamless operation—automated and transparent to the user\nRobust Communication with MQTT Integrating MQTT into PLCs posed a challenge, given the pointer-heavy libraries and outdated string handling in IEC61131-3. But the payoff is big: MQTT enabled us to decouple the HMI from specific project logic, making testing easier and dramatically cutting engineering costs.\nSmarter Trawling Automation We’ve developed an automatic trawl controller that maintains net depth and prevents closure during vessel turns. Whether you’re running three winches or switching to a single-trawl system, our controller handles transitions smoothly. Tension equalization keeps the net open, and automatic payout engages when manual control hits predefined limits.\nEven the sounder winch is smarter—controlled by tension, not just torque—reducing the skipper’s workload.\nTackling Real-World Challenges Every vessel works differently. Processes, safety protocols, power availability, and subsystems vary. Blackouts from underpowered AC systems or overheating inverters aren’t uncommon. Our system is designed to handle unexpected failures—cooling loss, brake issues, or sudden stops—while always prioritizing gear safety and crew security.\nThese aren’t just technical decisions—they’re part of a broader conversation with vessel owners about balancing risk, safety, and efficiency.\ni## Development Philosophy: Concepts First\nToo often in automation, the focus is on code instead of concepts. That’s backward. Real solutions come from understanding system requirements, regulations, and how components must interact.\nWe embraced composition over inheritance to keep the system flexible and future-proof. Interfaces are designed before code is written, allowing for better collaboration and easier revisions. If something doesn’t work, it can be replaced cleanly—no legacy bugs carried forward.\nWhy TwinCAT? We chose TwinCAT for good reason:\nUltra-low jitter with 10ms cycle times Rich library support and wide PLC compatibility Strong support for composition in programming That said, it’s not perfect. Visual Studio can be unstable, especially with references open. And FB_Init() limitations make reference handling tricky due to legacy compatibility. But the benefits far outweigh the quirks.\nWhat is fishing about? Trawling is about the position the net towards the fish. The opening has to be optimal at all times and at the right depth. For that purpose and package with sensor electronics, connected to the doors and net. A well equipped fishing vessel knows about the position of the doors in depth, pitch and roll and is knowing how much the load has to be expected. A ultrasonic sensor in the center monitors the opening of the net. The skipper is maintaining the depth and opening with the length of the wires and the speed of the vessel.\nThe trawl doors keep the net open as the vessel moves, while a sensor package with an echo sounder relays real-time net opening data back to the bridge. To simplify control, the sensor package is physically linked to the net and held in place by the sounder winch, which maintains constant tension to ensure it stays properly positioned relative to the gear.\nThe length measurement of the wires is without any sensor, since the conditions on fishing vessels are rough. The measurement is dependent on a well working spooling.\nConclusion Modern fishing demands more than just powerful equipment—it requires intelligent, adaptable systems that work with the crew, not against them. With the Wave Framework, we’ve built a control architecture that simplifies complex operations, reduces human error, and adapts to real-world challenges on deck. By prioritizing concept-driven design, configuration over custom code, and robust communication through technologies like MQTT, we’re setting a new standard for automation at sea. The result is a smarter, safer, and more efficient vessel—ready for whatever the ocean brings.\n","date":"June 4, 2026","externalUrl":null,"permalink":"/posts/p002/","section":"Posts","summary":"","title":"Automation for Fishing Vessels: Wave Framework with TwinCAT","type":"posts"},{"content":" The Problem PLC programs often use state machines to control machine behavior. That is usually a good choice. A state machine makes the current operating mode visible and keeps transitions explicit.\nThe difficult part starts when one state machine must control another one.\nAssume PLC 1 decides that a unit should run, and PLC 2 controls that unit. The simple solution is to send a start command from PLC 1 to PLC 2. Later, PLC 1 sends a stop command.\nThat works in a demo. It is weaker in a real machine.\nA start command is an event. It says that something happened at one point in time. It does not say that the sender is still alive. It does not say that the communication link is still healthy. It does not say that running is still wanted.\nFor continuous operation, the receiver usually needs more than an event. It needs to know the current intent of the sender.\nStart/Stop Commands Are Fragile One-shot commands are easy to understand, but they create hidden protocol requirements.\nConsider this sequence:\nPLC 1 sends start. PLC 2 receives start and enters RUNNING. PLC 1 loses power. PLC 2 keeps running because no stop command arrives. PLC 2 cannot distinguish these cases:\nPLC 1 still wants the unit to run. PLC 1 is stopped and cannot send anything. The same problem appears when a network switch fails, an ADS connection is interrupted, or a task stops executing. The receiver may continue based on an old event.\nYou can make a start/stop protocol robust. But then it is no longer just start and stop. You need extra parts:\nacknowledgement bits command sequence numbers state feedback communication watchdogs timeout handling reset behavior error diagnostics Those parts are not wrong. In some systems they are required. But they are also work, and they are easy to implement inconsistently.\nThe core issue is simple:\nA command describes an event. A request describes an active intent.\nFor continuous operation, active intent is often the better abstraction.\nA Better Pattern: Cyclic Run Request Instead of sending start once, the controlling state machine repeatedly sends a run request while it wants the controlled state machine to run.\nThe message is no longer:\nStart now.\nIt becomes:\nKeep running while this request continues to arrive.\nProposal The controlled state machine uses a timeout. Every valid run request refreshes it. If requests stop arriving, the controlled state machine leaves RUNNING and moves to STOPPING or IDLE.\nThis is a watchdog pattern. With a one-shot start, the default after a communication failure may be “continue running”. With a cyclic run request, the default becomes “stop after the timeout”.\nThat is usually the safer operational behavior. It is not the same as functional safety, but it is a useful control-layer default.\nHow the Timeout Works In TwinCAT, a TOF timer is a practical way to implement this pattern.\nTOF is an off-delay timer. Its output Q stays TRUE for the configured time PT after the input IN becomes FALSE.\nThis is useful because the run request is normally only present for one PLC scan. The receiver sets an internal runRequested flag when a request arrives. The cyclic update method calls the timer and clears the flag again.\nAs long as new requests arrive before the off-delay expires, Q remains TRUE. If requests stop, Q becomes FALSE after the timeout.\nImportant rule:\nCall the timer every scan cycle.\nDo not call timers only inside selected states unless you have a clear reason. A skipped timer call means skipped timer behavior. That can make timeout diagnostics misleading.\nTwinCAT Implementation The example below shows the core pattern. Real code should add diagnostics, reset handling, and application-specific stop behavior.\nFirst define an explicit enum for the state machine.\n{attribute \u0026#39;qualified_only\u0026#39;} {attribute \u0026#39;strict\u0026#39;} TYPE MachineState : ( IDLE, PREPARING, RUNNING, STOPPING, ERROR ) DINT; END_TYPE The controller stores the current state, the one-scan request flag, and the off-delay timer.\nFUNCTION_BLOCK MachineController VAR state : MachineState := MachineState.IDLE; runRequested : BOOL; runWatchdog : TOF; END_VAR VAR CONSTANT RUN_REQUEST_TIMEOUT : TIME := T#100MS; END_VAR The controlling state machine calls requestRun() cyclically while operation is wanted.\nMETHOD requestRun runRequested := TRUE; The controlled state machine calls update() once per PLC scan.\nMETHOD update runWatchdog(IN := runRequested, PT := RUN_REQUEST_TIMEOUT); runRequested := FALSE; CASE state OF MachineState.IDLE: IF runWatchdog.Q THEN state := MachineState.PREPARING; END_IF MachineState.PREPARING: state := MachineState.RUNNING; MachineState.RUNNING: IF NOT runWatchdog.Q THEN state := MachineState.STOPPING; END_IF MachineState.STOPPING: state := MachineState.IDLE; MachineState.ERROR: ; ELSE state := MachineState.ERROR; END_CASE The timer is called first. Then runRequested is cleared. If another function block wants this machine to keep running, it must call requestRun() again before the next update() call.\nThis makes the request edge-independent. The receiver does not care about a rising edge. It cares whether the request continues to be refreshed.\nIn a larger application, the caller might look like this:\nIF productionState = ProductionState.RUNNING THEN machineController.requestRun(); END_IF machineController.update(); Call order matters. The request should be made before update() in the same scan if both blocks run in the same task. If the request comes from another PLC or task, size the timeout accordingly.\nChoosing the Timeout The timeout must be longer than the worst-case interval between valid requests.\nDo not base it only on the nominal task cycle. Real systems have jitter, communication delays, task priority effects, and online changes. A short timeout can create false stops. A long timeout delays the reaction to a lost request.\nExample for local logic in the same PLC task:\nParameter Value Sender task cycle 10 ms Receiver task cycle 10 ms Expected request interval 10 ms Practical timeout 30 ms to 100 ms For communication between PLCs, use more margin. The correct value depends on the communication mechanism, task cycle times, network load, and required stopping behavior.\nAs a rule of thumb, start with this question:\nWhat is the longest acceptable time the receiver may continue running after the sender stops requesting operation?\nThen check whether the communication path can reliably refresh the request within that time. If not, the architecture has a conflict. Increasing the timeout may hide nuisance stops, but it also increases running time after a failure.\nThat trade-off should be explicit.\nFailure Modes This pattern improves several common failure modes, but it does not remove the need for proper machine design.\nFailure Expected behavior Controlling PLC stops Request expires and receiver stops operationally Communication link fails Request expires and receiver stops operationally Sender task has expected jitter Timeout should tolerate it Sender task hangs Request expires if no new request is produced Receiver logic has an internal error Must be handled by separate error logic The last row matter. A watchdog on the request does not fix bad receiver code. The receiver still needs internal diagnostics, error states, and deterministic stop behavior.\nFor example, STOPPING should not be a decorative state. It should actively bring the controlled unit into a defined condition. Depending on the machine, that may mean ramping down motion, closing valves, disabling outputs, or handing control to a lower-level drive function.\nConclusion One-shot start and stop commands are simple, but they are often the wrong abstraction for continuous operation between state machines.\nA start command says that an event happened. A cyclic run request says that operation is still wanted now.\nFor PLC state machines, that difference matters. A cyclic request with timeout supervision gives the receiver a clear rule:\nRun while the request is refreshed. Stop when the request expires.\nThe pattern is small, testable, and easy to debug online. It still needs correct timeout selection, deterministic receiver logic, and proper diagnostics. It also does not replace functional safety.\nUsed in the right place, it reduces protocol complexity and gives the system a better default behavior when communication or the controlling state machine fails.\n","date":"June 3, 2026","externalUrl":null,"permalink":"/posts/p001/","section":"Posts","summary":"","title":"State Machine Communication: Prefer Requests Over Start/Stop Commands","type":"posts"},{"content":"","date":"June 3, 2026","externalUrl":null,"permalink":"/tags/state-machine/","section":"Tags","summary":"","title":"State-Machine","type":"tags"},{"content":"\nSenior automation and software engineer with extensive experience in PLC development, industrial control systems, winch control and electronics development. Solid expertise in TwinCAT, CODESYS, system architecture and commissioning of advanced automation systems.\n","date":"May 20, 2026","externalUrl":null,"permalink":"/about/","section":"Hyperborealis Blog","summary":"","title":"About","type":"page"},{"content":"","date":"May 20, 2026","externalUrl":null,"permalink":"/notes/","section":"Notes","summary":"","title":"Notes","type":"notes"},{"content":"","date":"May 20, 2026","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"May 20, 2026","externalUrl":null,"permalink":"/projects/","section":"Projects","summary":"","title":"Projects","type":"projects"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]