<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Plc on Marius Gjerd</title><link>https://mariusgjerd.github.io/tags/plc/</link><description>Recent content in Plc on Marius Gjerd</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Sun, 26 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://mariusgjerd.github.io/tags/plc/index.xml" rel="self" type="application/rss+xml"/><item><title>What 4-20mA actually means (and why your zero is at 4)</title><link>https://mariusgjerd.github.io/posts/what-4-20ma-actually-means/</link><pubDate>Sun, 26 Jul 2026 00:00:00 +0000</pubDate><author>Marius Gjerd</author><guid>https://mariusgjerd.github.io/posts/what-4-20ma-actually-means/</guid><category>420ma</category><category>sensors</category><category>plc</category><category>scada</category><category>dataquality</category><category>datalifecycle</category><description>Most industrial sensor data still travels as a current between 4 and 20 milliamps. What the signal is, how it becomes a number in your database, and the ways it can quietly be wrong.</description><content:encoded><![CDATA[<p>The temperature in your database was, at some point, a current in a wire. Not as a metaphor. Actual milliamps through copper. Almost everything your code knows about a physical process traveled as an electrical current through a cable before it became a number, and the rules for that journey were written in the 1950s.</p>
<p>The standard is called 4-20mA. It is old, it is analog, and it still carries most of the world&rsquo;s industrial sensor data. I have pulled the cables for it as an electrician and consumed the numbers from it as a developer, and the two jobs taught me very different things about the same signal. This post is what I wish the developer side knew.</p>
<aside class="terms-box">
  <p class="terms-title">Words in this post, in plain language</p>
  <ul>
<li><strong>Transmitter:</strong> the measurement converter mounted on or near the sensor. It turns the sensor&rsquo;s raw signal (the resistance of a PT100, for example) into a standardized 4-20 mA current.</li>
<li><strong>PLC (programmable logic controller):</strong> the industrial computer that runs the control logic of a plant. It reads inputs, executes its program and sets outputs, over and over, in real time. In this post we care about its analog inputs.</li>
<li><strong>Span:</strong> the measuring range the transmitter is configured with, meaning which physical values 4 and 20 mA correspond to. For example 0-200°C.</li>
<li><strong>Frequency drive:</strong> power electronics that control the speed of a motor by varying the frequency and voltage it is fed. Found on everything from fans to pumps, and it returns in the noise section below.</li>
</ul>

</aside>

<h2 id="why-your-zero-is-at-4">Why your zero is at 4</h2>
<p>A 4-20mA sensor maps its measuring range onto a current. 4 mA means the bottom of the range, 20 mA means the top. If the sensor measures 0-200°C, then 4 mA is 0 degrees and 20 mA is 200 degrees. Whatever sits between is your value, scaled linearly.</p>
<p>The first question everyone asks: why does zero sit at 4 and not at 0? The answer is the smartest part of the whole standard. 0 mA is reserved for failure. A healthy loop always carries at least 4 mA, so if the current drops to zero, something is wrong. Broken wire, dead transmitter, loop lost its power. The fault detection is built into the physics itself, which means a dead sensor cannot pretend to be a cold pipe.</p>
<p>There is a convention that makes this precise, NAMUR NE43. Normal measurement information lives between 3.8 and 20.5 mA. Anything below 3.6 mA or above 21.0 mA is a failure signal. Transmitters are configured to drive the current high or low when the sensor itself fails (this is called burnout direction), and your code needs to know which way your transmitters are set. A sensor failing upwards looks exactly like a process that is overheating, if you insist on treating it as a measurement.</p>
<p>So read the sensor documentation and know what to expect. A value below 3.6 mA is not a very cold pipe. It is a broken wire telling you about itself, in the only language it has.</p>
<figure class="diagram">
  <svg viewBox="0 0 720 240" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The 4-20 mA scale with every zone named. Below 3.6 mA and above 21 mA: fault signal. 3.6 to 3.8 and 20.5 to 21: dead band. 3.8 to 4 and 20 to 20.5: NAMUR NE43 margin. 4 to 20 mA: measurement range, mapped linearly to engineering units, here 0 to 200 degrees Celsius.">
  <style>
    .lbl { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 13px; fill: var(--content, #333); }
    .sub { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 12px; fill: var(--secondary, #666); }
    .leg { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 11px; fill: var(--content, #333); }
    .fault-lbl { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 12px; font-weight: 600; fill: #d64545; }
    .sig-lbl { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 13px; font-weight: 600; fill: #ffffff; }
    .tick { stroke: var(--secondary, #666); stroke-width: 1; }
    .map { stroke: var(--border, #bbb); stroke-width: 1; stroke-dasharray: 4 4; }
  </style>

  <!-- mA bar: x = 40 + mA * (640/22) -->
  <rect x="40" y="60" width="104.7" height="28" fill="#d64545" rx="4"/>
  <rect x="144.7" y="60" width="5.8" height="28" fill="var(--border, #c8c8c8)"/>
  <rect x="636.3" y="60" width="14.6" height="28" fill="var(--border, #c8c8c8)"/>
  <rect x="150.5" y="60" width="5.8" height="28" fill="#2f6fdd" opacity="0.35"/>
  <rect x="621.8" y="60" width="14.5" height="28" fill="#2f6fdd" opacity="0.35"/>
  <rect x="156.4" y="60" width="465.4" height="28" fill="#2f6fdd"/>
  <rect x="650.9" y="60" width="29.1" height="28" fill="#d64545" rx="4"/>

  <text class="sig-lbl" x="389" y="79" text-anchor="middle">measurement range</text>
  <text class="fault-lbl" x="92" y="52" text-anchor="middle">fault: fail low</text>
  <text class="fault-lbl" x="655" y="52" text-anchor="end">fault: fail high</text>

  <!-- ticks and numbers -->
  <line class="tick" x1="40" y1="88" x2="40" y2="96"/>
  <text class="sub" x="40" y="110" text-anchor="middle">0 mA</text>
  <line class="tick" x1="144.7" y1="88" x2="144.7" y2="96"/>
  <text class="sub" x="138" y="110" text-anchor="middle">3.6</text>
  <line class="tick" x1="156.4" y1="88" x2="156.4" y2="96"/>
  <text class="sub" x="164" y="110" text-anchor="middle">4</text>
  <line class="tick" x1="389" y1="88" x2="389" y2="96"/>
  <text class="sub" x="389" y="110" text-anchor="middle">12</text>
  <line class="tick" x1="621.8" y1="88" x2="621.8" y2="96"/>
  <text class="sub" x="617" y="110" text-anchor="middle">20</text>
  <line class="tick" x1="650.9" y1="88" x2="650.9" y2="96"/>
  <text class="sub" x="655" y="110" text-anchor="middle">21</text>

  <!-- mapping to engineering units -->
  <line class="map" x1="156.4" y1="96" x2="156.4" y2="146"/>
  <line class="map" x1="389" y1="96" x2="389" y2="146"/>
  <line class="map" x1="621.8" y1="96" x2="621.8" y2="146"/>
  <line x1="156.4" y1="146" x2="621.8" y2="146" stroke="var(--content, #333)" stroke-width="1.5"/>
  <line class="tick" x1="156.4" y1="142" x2="156.4" y2="150"/>
  <line class="tick" x1="389" y1="142" x2="389" y2="150"/>
  <line class="tick" x1="621.8" y1="142" x2="621.8" y2="150"/>
  <text class="lbl" x="156.4" y="168" text-anchor="middle">0 &#176;C</text>
  <text class="lbl" x="389" y="168" text-anchor="middle">100 &#176;C</text>
  <text class="lbl" x="621.8" y="168" text-anchor="middle">200 &#176;C</text>
  <text class="sub" x="389" y="190" text-anchor="middle">what your code sees, after the span (here 0-200 &#176;C) is applied</text>

  <!-- legend: every zone named with its range -->
  <rect x="40" y="212" width="12" height="12" fill="#2f6fdd" rx="2"/>
  <text class="leg" x="58" y="222">measurement range (4-20 mA)</text>
  <rect x="222" y="212" width="12" height="12" fill="#2f6fdd" opacity="0.35" rx="2"/>
  <text class="leg" x="240" y="222">NE43 margin (3.8-4/20-20.5)</text>
  <rect x="404" y="212" width="12" height="12" fill="var(--border, #c8c8c8)" rx="2"/>
  <text class="leg" x="422" y="222">dead band (3.6-3.8/20.5-21)</text>
  <rect x="586" y="212" width="12" height="12" fill="#d64545" rx="2"/>
  <text class="leg" x="604" y="222">fault (&#60;3.6/&#62;21)</text>
</svg>
<figcaption>The whole scale in one picture: fault zones outside 3.6 and 21 mA, the NE43 margins in faint blue, dead bands in gray between them, and the linear mapping to engineering units underneath.</figcaption></figure>
<h2 id="why-current-and-not-voltage">Why current and not voltage</h2>
<p>Two reasons. First, current is the same everywhere in the loop. A voltage signal drops along the cable, and the longer the cable, the more you lose. Current does not have that problem. The loop can run through half a building and the milliamps arriving at the panel are the milliamps the transmitter set.</p>
<p>Second, a current loop shrugs off electrical noise far better than a voltage signal does. Industrial buildings are electrically loud places. More on that below, because I have a story.</p>
<p>The wiring itself is almost elegant. A typical loop is just two wires, and those same two wires carry both the power supply (usually 24 VDC) and the signal. The transmitter sits in the loop and regulates how much current it lets through. On the receiving end, the analog input card often just measures the current across a 250 ohm resistor, which turns 4-20 mA into 1-5 V that the card can read. Two wires, hundreds of meters, powered and talking at the same time.</p>
<figure class="diagram">
  <svg viewBox="0 0 720 280" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Multi-wire schematic of a 4-20 mA current loop with three separated components: a 24 VDC power supply on the left, a two-wire transmitter at the top center, and a PLC analog input on the right. The supply plus terminal feeds the transmitter plus terminal, the transmitter minus terminal feeds the analog input plus terminal, and the return wire runs from the analog input minus terminal back to the supply minus terminal. The same current flows in the whole loop.">
  <style>
    .wire { stroke: var(--content, #333); stroke-width: 2; fill: none; }
    .box { fill: var(--entry, #f6f6f6); stroke: var(--content, #333); stroke-width: 1.5; }
    .lbl { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 14px; fill: var(--content, #333); }
    .sub { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 12px; fill: var(--secondary, #666); }
    .pol { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 13px; font-weight: 700; fill: var(--content, #333); }
    .sig { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 13px; font-weight: 600; fill: #2f6fdd; }
    .term { fill: var(--content, #333); }
    .arrow { fill: #2f6fdd; }
  </style>

  <!-- transmitter, top center, separated from both -->
  <rect class="box" x="290" y="30" width="140" height="70" rx="6"/>
  <text class="lbl" x="360" y="59" text-anchor="middle">transmitter</text>
  <text class="sub" x="360" y="78" text-anchor="middle">+ sensor (2-wire)</text>
  <circle class="term" cx="320" cy="100" r="3.5"/>
  <text class="pol" x="306" y="118">+</text>
  <circle class="term" cx="400" cy="100" r="3.5"/>
  <text class="pol" x="406" y="118">&#8722;</text>

  <!-- power supply, left -->
  <rect class="box" x="40" y="110" width="140" height="110" rx="6"/>
  <text class="lbl" x="110" y="158" text-anchor="middle">24 VDC</text>
  <text class="sub" x="110" y="177" text-anchor="middle">power supply</text>
  <circle class="term" cx="180" cy="140" r="3.5"/>
  <text class="pol" x="166" y="132">+</text>
  <circle class="term" cx="180" cy="190" r="3.5"/>
  <text class="pol" x="166" y="184">&#8722;</text>

  <!-- PLC analog input, right -->
  <rect class="box" x="540" y="110" width="140" height="110" rx="6"/>
  <text class="lbl" x="610" y="150" text-anchor="middle">PLC analog input</text>
  <text class="sub" x="610" y="168" text-anchor="middle">250 &#937; &#8594; 1-5 V</text>
  <circle class="term" cx="540" cy="140" r="3.5"/>
  <text class="pol" x="548" y="132">AI+</text>
  <circle class="term" cx="540" cy="190" r="3.5"/>
  <text class="pol" x="548" y="184">AI&#8722;</text>

  <!-- wire: supply + to transmitter +, right-angle routing -->
  <polyline class="wire" points="180,140 320,140 320,100"/>
  <polygon class="arrow" points="240,134 256,140 240,146"/>
  <text class="sig" x="248" y="126" text-anchor="middle">4-20 mA</text>

  <!-- wire: transmitter - to PLC AI+ -->
  <polyline class="wire" points="400,100 400,140 540,140"/>
  <polygon class="arrow" points="462,134 478,140 462,146"/>

  <!-- return wire: PLC AI- to supply - -->
  <line class="wire" x1="540" y1="190" x2="180" y2="190"/>
  <polygon class="arrow" points="368,184 352,190 368,196"/>
  <text class="sub" x="360" y="210" text-anchor="middle">return</text>

  <!-- documentation -->
  <text class="sub" x="360" y="246" text-anchor="middle">cable: 2 x 0.75 mm&#178; shielded twisted pair, shield grounded at one end</text>
  <text class="sig" x="360" y="268" text-anchor="middle">series loop: the same 4-20 mA flows through every component</text>
</svg>
<figcaption>One loop, three components in series. The transmitter regulates the current, and the same milliamps flow through every point in the loop.</figcaption></figure>
<h2 id="from-current-to-a-number">From current to a number</h2>
<p>The current becomes a number through the transmitter&rsquo;s span. The span is the configured measuring range, and the transmitter maps it onto 4-20 mA linearly. With a span of 0-200°C, 12 mA means 100 degrees. Simple.</p>
<p>Here is the danger: the span lives in the transmitter, not in your code. If someone reconfigures the range to 0-150°C and does not tell anyone downstream, every value in your pipeline is scaled wrong from that moment on. Silently. The numbers still look plausible, no error, no gap, just a temperature that is no longer the temperature. If a measurement setup changes, the configuration and the documentation have to move together, because the person consuming the data three systems away has no other way to know.</p>
<figure class="diagram">
  <svg viewBox="0 0 720 250" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The same 12 mA current read against two different spans. With the transmitter's actual span of 0 to 150 degrees, 12 mA means 75 degrees. With the span your code assumes, 0 to 200 degrees, the same current computes to 100 degrees. The transmitter always wins.">
  <style>
    .lbl { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 13px; fill: var(--content, #333); }
    .sub { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 12px; fill: var(--secondary, #666); }
    .ok { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 14px; font-weight: 700; fill: #2f6fdd; }
    .bad { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 14px; font-weight: 700; fill: #d64545; }
    .track { fill: var(--border, #d8d8d8); opacity: 0.55; }
    .dash { stroke: var(--content, #333); stroke-width: 1.5; stroke-dasharray: 5 4; }
  </style>

  <!-- 12 mA line: x = 120 + (12-4)/16 * 500 = 370 -->
  <text class="lbl" x="370" y="26" text-anchor="middle" font-weight="700">the same 12 mA</text>
  <line class="dash" x1="370" y1="34" x2="370" y2="196"/>

  <!-- row 1: the transmitter's actual span -->
  <text class="lbl" x="120" y="52">span in the transmitter: 0-150 &#176;C</text>
  <rect class="track" x="120" y="60" width="500" height="22" rx="4"/>
  <circle cx="370" cy="71" r="6" fill="#2f6fdd"/>
  <text class="ok" x="384" y="76">= 75 &#176;C, what it sends</text>
  <text class="sub" x="120" y="100">4 mA = 0 &#176;C</text>
  <text class="sub" x="620" y="100" text-anchor="end">20 mA = 150 &#176;C</text>

  <!-- row 2: the span your code assumes -->
  <text class="lbl" x="120" y="140">span your code assumes: 0-200 &#176;C</text>
  <rect class="track" x="120" y="148" width="500" height="22" rx="4"/>
  <circle cx="370" cy="159" r="6" fill="#d64545"/>
  <text class="bad" x="384" y="164">= 100 &#176;C, what you compute</text>
  <text class="sub" x="120" y="188">4 mA = 0 &#176;C</text>
  <text class="sub" x="620" y="188" text-anchor="end">20 mA = 200 &#176;C</text>

  <text class="sub" x="370" y="226" text-anchor="middle">same current, two different temperatures. The transmitter always wins, silently.</text>
</svg>
<figcaption>The span problem in one picture. The current is correct, the assumption is not, and nothing in the data tells you.</figcaption></figure>
<p>(Some loops also run HART, a digital signal on top of the same 4-20 mA wires, which can carry diagnostics and configuration. Worth knowing it exists.)</p>
<p>One honest note about the last step. Inside the PLC, the analog card converts the current into raw integer counts, and software scales those counts into engineering units. I have seen those raw counts in systems I integrate against, and I have learned not to assume I know which scaling sits behind a value. The converter layer itself is a part of the chain I have not worked hands-on with yet. I start my industrial automation degree this fall and that layer is high on my list. When I have measured it myself, it gets its own post.</p>
<h2 id="noise-or-the-days-we-spent-chasing-ghost-data">Noise, or the days we spent chasing ghost data</h2>
<p>From my electrician years. We were on a project in a large building, pulling cables from a huge number of sensors to local nodes, and from the nodes into a main panel where all the building&rsquo;s sensor data came together. Lots of cable, lots of runs, everything landed and terminated. Our part was done.</p>
<p>Then the automation guys started commissioning, and the data was garbage. Not dead, which would have been easier, but wrong and weird and jumpy. They spent days troubleshooting before someone found it: the sensor cables were unshielded, and long stretches of them were lying on the same cable ladder as high voltage power cables. The power lines were inducing noise straight into the signal wires, and every reading arriving in that panel had a little bit of the building&rsquo;s electrical activity mixed into it.</p>
<p>This is what electrical noise looks like from the data side: values that vibrate a few counts around the real reading, and sudden jumps that look like process changes but are actually a motor starting somewhere. Frequency drives are notorious for this. Shielding helps, grounding the shield correctly matters (grounding it at both ends can create its own problem, a ground loop), and physical distance from power cables is the cheapest fix of all. None of which your code can see. It just sees a temperature that trembles.</p>
<figure class="diagram">
  <svg viewBox="0 0 720 270" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="An unshielded signal pair lying on the same cable ladder as a power cable picks up induced noise. The signal sent by the transmitter is smooth, the signal read by the PLC trembles.">
  <style>
    .lbl { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 13px; fill: var(--content, #333); }
    .sub { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 12px; fill: var(--secondary, #666); }
    .noise-lbl { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 12px; font-weight: 600; fill: #d64545; }
    .ladder { stroke: var(--border, #bbb); stroke-width: 2; }
    .power { stroke: var(--content, #333); stroke-width: 9; stroke-linecap: round; }
    .signal { stroke: #2f6fdd; stroke-width: 2.5; fill: none; }
    .noise { stroke: #d64545; stroke-width: 1.8; fill: none; }
  </style>

  <!-- cable ladder -->
  <line class="ladder" x1="50" y1="38" x2="670" y2="38"/>
  <line class="ladder" x1="50" y1="112" x2="670" y2="112"/>
  <line class="ladder" x1="90" y1="38" x2="90" y2="112"/>
  <line class="ladder" x1="190" y1="38" x2="190" y2="112"/>
  <line class="ladder" x1="290" y1="38" x2="290" y2="112"/>
  <line class="ladder" x1="390" y1="38" x2="390" y2="112"/>
  <line class="ladder" x1="490" y1="38" x2="490" y2="112"/>
  <line class="ladder" x1="590" y1="38" x2="590" y2="112"/>

  <!-- power cable -->
  <line class="power" x1="60" y1="58" x2="660" y2="58"/>
  <text class="lbl" x="60" y="30">power cable (motors, frequency drives)</text>

  <!-- unshielded signal pair -->
  <line class="signal" x1="60" y1="92" x2="660" y2="92"/>
  <line class="signal" x1="60" y1="99" x2="660" y2="99"/>
  <text class="sub" x="60" y="134">unshielded 4-20 mA signal pair, same ladder</text>

  <!-- induced noise arrows -->
  <path class="noise" d="M 230 64 q 5 8 -2 12 q -7 4 -2 12"/>
  <polygon fill="#d64545" points="222,86 228,90 230,82"/>
  <path class="noise" d="M 400 64 q 5 8 -2 12 q -7 4 -2 12"/>
  <polygon fill="#d64545" points="392,86 398,90 400,82"/>
  <path class="noise" d="M 570 64 q 5 8 -2 12 q -7 4 -2 12"/>
  <polygon fill="#d64545" points="562,86 568,90 570,82"/>
  <text class="noise-lbl" x="620" y="134" text-anchor="end">induced noise</text>

  <!-- sent vs read -->
  <path class="signal" d="M 70 215 C 130 215 150 195 200 195 C 250 195 270 210 320 210"/>
  <text class="sub" x="195" y="248" text-anchor="middle">what the transmitter sent</text>

  <path class="signal" d="M 400 215 L 408 213 L 414 218 L 422 210 L 430 216 L 436 194 L 444 214 L 452 210 L 458 216 L 466 197 L 474 199 L 482 194 L 490 200 L 498 190 L 506 197 L 514 212 L 522 194 L 530 208 L 538 206 L 546 213 L 554 208 L 562 215 L 570 209 L 578 213 L 586 207 L 594 212 L 602 209 L 610 212 L 618 208 L 626 211 L 634 209 L 642 210 L 650 210"/>
  <text class="sub" x="525" y="248" text-anchor="middle">what the PLC read</text>
</svg>
<figcaption>The project in one drawing: unshielded signal cable on the same ladder as power cables. The transmitter sends a clean signal, the PLC reads a trembling one.</figcaption></figure>
<h2 id="what-your-code-should-assume">What your code should assume</h2>
<p>The practical takeaways, same spirit as <a href="/posts/trust-your-sensor-data/">the sensor data post</a>:</p>
<ul>
<li>Treat anything below 3.6 mA or above 21 mA as a fault, never as a measurement. Do not let fault currents sneak into averages and aggregates.</li>
<li>Know the burnout direction of your transmitters. A sensor that fails high should not page anyone about an overheating process.</li>
<li>Do not trust that the span in the documentation is the span in the transmitter. Verify when you can, and log loudly when values stop making physical sense.</li>
<li>Expect a few counts of noise. Alert thresholds tighter than the noise floor produce alerts about electricity, not about the process.</li>
<li>If you can, log the raw mA or counts alongside the scaled value. It is the only way to debug a scaling problem after the fact.</li>
</ul>
<figure class="diagram">
  <svg viewBox="0 0 720 235" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="A small ingestion flow for 4-20 mA values. A value arrives as milliamps or raw counts. If it is outside 3.8 to 20.5 milliamps it goes to the fault path: alarm it and keep it out of statistics. Otherwise apply the span, validate range and rate of change, and store the scaled value together with the raw one.">
  <style>
    .box { fill: var(--entry, #f6f6f6); stroke: var(--content, #333); stroke-width: 1.5; }
    .fbox { fill: none; stroke: #d64545; stroke-width: 1.8; }
    .lbl { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 13px; fill: var(--content, #333); }
    .sub { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 11.5px; fill: var(--secondary, #666); }
    .yes { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 12px; font-weight: 700; fill: #2f6fdd; }
    .no { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 12px; font-weight: 700; fill: #d64545; }
    .fault { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 13px; font-weight: 600; fill: #d64545; }
    .wire { stroke: var(--content, #333); stroke-width: 1.8; fill: none; }
    .head { fill: var(--content, #333); }
  </style>

  <!-- value arrives -->
  <rect class="box" x="25" y="55" width="140" height="56" rx="6"/>
  <text class="lbl" x="95" y="79" text-anchor="middle">value arrives</text>
  <text class="sub" x="95" y="97" text-anchor="middle">mA or raw counts</text>

  <line class="wire" x1="165" y1="83" x2="196" y2="83"/>
  <polygon class="head" points="196,78 206,83 196,88"/>

  <!-- range check -->
  <rect class="box" x="206" y="55" width="150" height="56" rx="6"/>
  <text class="lbl" x="281" y="79" text-anchor="middle">inside 3.8-20.5 mA?</text>
  <text class="sub" x="281" y="97" text-anchor="middle">the zones from above</text>

  <!-- no: fault path -->
  <line class="wire" x1="281" y1="111" x2="281" y2="142"/>
  <polygon class="head" points="276,142 281,152 286,142"/>
  <text class="no" x="290" y="136">no</text>
  <rect class="fbox" x="191" y="152" width="180" height="56" rx="6"/>
  <text class="fault" x="281" y="176" text-anchor="middle">fault path</text>
  <text class="sub" x="281" y="194" text-anchor="middle">alarm it, keep it out of stats</text>

  <!-- yes: continue -->
  <line class="wire" x1="356" y1="83" x2="387" y2="83"/>
  <polygon class="head" points="387,78 397,83 387,88"/>
  <text class="yes" x="371" y="72" text-anchor="middle">yes</text>

  <rect class="box" x="397" y="55" width="160" height="56" rx="6"/>
  <text class="lbl" x="477" y="79" text-anchor="middle">apply the span</text>
  <text class="sub" x="477" y="97" text-anchor="middle">validate range + rate</text>

  <line class="wire" x1="557" y1="83" x2="588" y2="83"/>
  <polygon class="head" points="588,78 598,83 588,88"/>

  <rect class="box" x="598" y="55" width="100" height="56" rx="6"/>
  <text class="lbl" x="648" y="79" text-anchor="middle">store scaled</text>
  <text class="sub" x="648" y="97" text-anchor="middle">+ raw value</text>
</svg>
<figcaption>The whole list as an ingestion flow: fault currents take the alarm path, everything else gets scaled, validated and stored with the raw value.</figcaption></figure>
<p>And the biggest one, the assumption behind all the others: assume that nobody has the full picture. The electrician who pulled the cable, the automation tech who commissioned the system, the project engineer sitting on the datasheets, and you, the developer consuming the number at the end. Every one of them knows their own layer. Nobody is checking yours against theirs unless you ask.</p>
<p>The current loop is honest. It is your assumptions about it that lie.</p>
<p>This post is step two in a chain. Step one, what happens to the value after it becomes a number, is here: <a href="/posts/trust-your-sensor-data/">Can you trust your sensor data?</a></p>
<hr><p><em>Originally published at <a href="https://mariusgjerd.github.io/posts/what-4-20ma-actually-means/">mariusgjerd.github.io</a>, where I write about what happens when code touches the real world. New posts also go out by email: <a href="https://marius-newsletter-d94bcd.beehiiv.com/">subscribe here</a>.</em></p>]]></content:encoded></item></channel></rss>