<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ashkan hoseinpoor</title>
    <description>The latest articles on DEV Community by ashkan hoseinpoor (@ashkan_hoseinpoor).</description>
    <link>https://dev.to/ashkan_hoseinpoor</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4051992%2F73cef301-4838-4252-840c-60fe3408116c.png</url>
      <title>DEV Community: ashkan hoseinpoor</title>
      <link>https://dev.to/ashkan_hoseinpoor</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ashkan_hoseinpoor"/>
    <language>en</language>
    <item>
      <title>Detect Industrial Heater Failures with JavaScript: A Rule-Based Anomaly Detection System</title>
      <dc:creator>ashkan hoseinpoor</dc:creator>
      <pubDate>Tue, 28 Jul 2026 20:30:12 +0000</pubDate>
      <link>https://dev.to/ashkan_hoseinpoor/detect-industrial-heater-failures-with-javascript-a-rule-based-anomaly-detection-system-4lmd</link>
      <guid>https://dev.to/ashkan_hoseinpoor/detect-industrial-heater-failures-with-javascript-a-rule-based-anomaly-detection-system-4lmd</guid>
      <description>&lt;p&gt;Detect Industrial Heater Failures with JavaScript: A Rule-Based Anomaly Detection System&lt;/p&gt;

&lt;p&gt;Industrial electric heaters rarely fail without warning.&lt;/p&gt;

&lt;p&gt;Before a heating element burns out completely, the system may show signs such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A slower-than-normal temperature rise&lt;/li&gt;
&lt;li&gt;High current without sufficient heating&lt;/li&gt;
&lt;li&gt;Temperature increase while the heater is supposed to be off&lt;/li&gt;
&lt;li&gt;A temperature sensor stuck at one value&lt;/li&gt;
&lt;li&gt;Excessive overshoot above the target temperature&lt;/li&gt;
&lt;li&gt;Sudden and unrealistic temperature changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In industrial systems, these symptoms may indicate problems with the heating element, thermocouple, contactor, solid-state relay, wiring, airflow, or temperature controller.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will build a browser-based heater anomaly detector using JavaScript.&lt;/p&gt;

&lt;p&gt;The application will analyze simulated temperature and current readings and detect several common heater faults without using machine learning or external libraries.&lt;/p&gt;

&lt;p&gt;What We Are Building&lt;/p&gt;

&lt;p&gt;Our monitoring system will receive a new data sample every second.&lt;/p&gt;

&lt;p&gt;Each sample contains:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  timestamp: 1720000000000,&lt;br&gt;
  temperature: 82.4,&lt;br&gt;
  current: 13.6,&lt;br&gt;
  heaterCommand: true,&lt;br&gt;
  targetTemperature: 120&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The application will monitor:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Heating rate&lt;/li&gt;
&lt;li&gt;Current flow&lt;/li&gt;
&lt;li&gt;Temperature overshoot&lt;/li&gt;
&lt;li&gt;Sensor activity&lt;/li&gt;
&lt;li&gt;Unexpected heating&lt;/li&gt;
&lt;li&gt;Sudden sensor changes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It will then classify the system status as:&lt;/p&gt;

&lt;p&gt;NORMAL&lt;br&gt;
WARNING&lt;br&gt;
FAULT&lt;/p&gt;

&lt;p&gt;Why Use Rule-Based Detection?&lt;/p&gt;

&lt;p&gt;Machine learning can be useful when large historical datasets are available.&lt;/p&gt;

&lt;p&gt;However, many industrial heating systems do not have enough labeled failure data to train a reliable model.&lt;/p&gt;

&lt;p&gt;A rule-based system offers several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy to understand&lt;/li&gt;
&lt;li&gt;Easy to test&lt;/li&gt;
&lt;li&gt;Fast to execute&lt;/li&gt;
&lt;li&gt;No training dataset required&lt;/li&gt;
&lt;li&gt;Suitable for browsers and embedded dashboards&lt;/li&gt;
&lt;li&gt;Thresholds can be adjusted for each heater&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main limitation is that thresholds must be configured according to the real process.&lt;/p&gt;

&lt;p&gt;A 3 kW water heater behaves differently from a 30 kW industrial oven.&lt;/p&gt;

&lt;p&gt;Project Structure&lt;/p&gt;

&lt;p&gt;Create the following files:&lt;/p&gt;

&lt;p&gt;heater-anomaly-detector/&lt;br&gt;
├── index.html&lt;br&gt;
├── style.css&lt;br&gt;
└── app.js&lt;/p&gt;

&lt;p&gt;Step 1: Create the HTML Interface&lt;/p&gt;

&lt;p&gt;Add the following code to "index.html":&lt;/p&gt;

&lt;p&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&amp;lt;meta&lt;br&gt;
    name="viewport"&lt;br&gt;
    content="width=device-width, initial-scale=1.0"&lt;/p&gt;

&lt;blockquote&gt;
&lt;/blockquote&gt;

&lt;p&gt;&amp;lt;meta&lt;br&gt;
    name="description"&lt;br&gt;
    content="A JavaScript-based industrial heater anomaly detection dashboard."&lt;/p&gt;

&lt;blockquote&gt;
&lt;/blockquote&gt;


Industrial Heater Anomaly Detector




&lt;br&gt;
    &lt;br&gt;
      &lt;br&gt;
        &lt;span&gt;&lt;br&gt;
          Industrial Monitoring Demo&lt;br&gt;
        &lt;/span&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;lt;h1&amp;gt;Heater Anomaly Detector&amp;lt;/h1&amp;gt;

    &amp;lt;p&amp;gt;
      Monitor temperature, current, heating rate,
      and common heater failure patterns.
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div
    id="system-status"
    class="status status-normal"
  &amp;gt;
    NORMAL
  &amp;lt;/div&amp;gt;
&amp;lt;/header&amp;gt;

&amp;lt;section class="metrics"&amp;gt;
  &amp;lt;article class="metric-card"&amp;gt;
    &amp;lt;span&amp;gt;Temperature&amp;lt;/span&amp;gt;
    &amp;lt;strong id="temperature-value"&amp;gt;-- °C&amp;lt;/strong&amp;gt;
  &amp;lt;/article&amp;gt;

  &amp;lt;article class="metric-card"&amp;gt;
    &amp;lt;span&amp;gt;Target&amp;lt;/span&amp;gt;
    &amp;lt;strong id="target-value"&amp;gt;-- °C&amp;lt;/strong&amp;gt;
  &amp;lt;/article&amp;gt;

  &amp;lt;article class="metric-card"&amp;gt;
    &amp;lt;span&amp;gt;Current&amp;lt;/span&amp;gt;
    &amp;lt;strong id="current-value"&amp;gt;-- A&amp;lt;/strong&amp;gt;
  &amp;lt;/article&amp;gt;

  &amp;lt;article class="metric-card"&amp;gt;
    &amp;lt;span&amp;gt;Heating rate&amp;lt;/span&amp;gt;
    &amp;lt;strong id="heating-rate-value"&amp;gt;
      -- °C/min
    &amp;lt;/strong&amp;gt;
  &amp;lt;/article&amp;gt;
&amp;lt;/section&amp;gt;

&amp;lt;section class="panel"&amp;gt;
  &amp;lt;div class="panel-heading"&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;Temperature History&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;
        Recent process temperature and target value
      &amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;div class="legend"&amp;gt;
      &amp;lt;span&amp;gt;
        &amp;lt;i class="temperature-marker"&amp;gt;&amp;lt;/i&amp;gt;
        Temperature
      &amp;lt;/span&amp;gt;

      &amp;lt;span&amp;gt;
        &amp;lt;i class="target-marker"&amp;gt;&amp;lt;/i&amp;gt;
        Target
      &amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;canvas
    id="temperature-chart"
    width="900"
    height="360"
  &amp;gt;&amp;lt;/canvas&amp;gt;
&amp;lt;/section&amp;gt;

&amp;lt;section class="controls panel"&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;h2&amp;gt;Simulation Mode&amp;lt;/h2&amp;gt;

    &amp;lt;p&amp;gt;
      Select a condition to test the detection rules.
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div class="control-buttons"&amp;gt;
    &amp;lt;button data-mode="normal"&amp;gt;
      Normal operation
    &amp;lt;/button&amp;gt;

    &amp;lt;button data-mode="weak-heater"&amp;gt;
      Weak heater
    &amp;lt;/button&amp;gt;

    &amp;lt;button data-mode="open-circuit"&amp;gt;
      Open circuit
    &amp;lt;/button&amp;gt;

    &amp;lt;button data-mode="stuck-sensor"&amp;gt;
      Stuck sensor
    &amp;lt;/button&amp;gt;

    &amp;lt;button data-mode="relay-stuck"&amp;gt;
      Relay stuck on
    &amp;lt;/button&amp;gt;

    &amp;lt;button data-mode="overheating"&amp;gt;
      Overheating
    &amp;lt;/button&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/section&amp;gt;

&amp;lt;section class="panel"&amp;gt;
  &amp;lt;div class="panel-heading"&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;Diagnostic Events&amp;lt;/h2&amp;gt;

      &amp;lt;p&amp;gt;
        Warnings and detected fault conditions
      &amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;button
      id="clear-events"
      class="secondary-button"
    &amp;gt;
      Clear events
    &amp;lt;/button&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div id="event-list" class="event-list"&amp;gt;
    &amp;lt;p class="empty-state"&amp;gt;
      No abnormal condition detected.
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;





&lt;p&gt;Step 2: Style the Dashboard&lt;/p&gt;

&lt;p&gt;Add the following code to "style.css":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;{
box-sizing: border-box;
}&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;:root {&lt;br&gt;
  font-family:&lt;br&gt;
    Inter,&lt;br&gt;
    Arial,&lt;br&gt;
    Helvetica,&lt;br&gt;
    sans-serif;&lt;/p&gt;

&lt;p&gt;color: #172033;&lt;br&gt;
  background: #eef2f7;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;body {&lt;br&gt;
  margin: 0;&lt;br&gt;
  padding: 32px 20px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;button {&lt;br&gt;
  font: inherit;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.dashboard {&lt;br&gt;
  width: min(1180px, 100%);&lt;br&gt;
  margin: 0 auto;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.dashboard-header {&lt;br&gt;
  display: flex;&lt;br&gt;
  align-items: flex-start;&lt;br&gt;
  justify-content: space-between;&lt;br&gt;
  gap: 24px;&lt;br&gt;
  margin-bottom: 26px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.eyebrow {&lt;br&gt;
  display: block;&lt;br&gt;
  margin-bottom: 8px;&lt;br&gt;
  color: #d97706;&lt;br&gt;
  font-size: 13px;&lt;br&gt;
  font-weight: 800;&lt;br&gt;
  letter-spacing: 0.08em;&lt;br&gt;
  text-transform: uppercase;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;h1,&lt;br&gt;
h2,&lt;br&gt;
p {&lt;br&gt;
  margin-top: 0;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;h1 {&lt;br&gt;
  margin-bottom: 10px;&lt;br&gt;
  font-size: clamp(32px, 5vw, 52px);&lt;br&gt;
  line-height: 1.05;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;h2 {&lt;br&gt;
  margin-bottom: 6px;&lt;br&gt;
  font-size: 20px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.dashboard-header p,&lt;br&gt;
.panel-heading p,&lt;br&gt;
.controls p {&lt;br&gt;
  margin-bottom: 0;&lt;br&gt;
  color: #64748b;&lt;br&gt;
  line-height: 1.7;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.status {&lt;br&gt;
  min-width: 130px;&lt;br&gt;
  padding: 13px 18px;&lt;br&gt;
  border-radius: 999px;&lt;br&gt;
  text-align: center;&lt;br&gt;
  font-size: 14px;&lt;br&gt;
  font-weight: 800;&lt;br&gt;
  letter-spacing: 0.06em;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.status-normal {&lt;br&gt;
  background: #dcfce7;&lt;br&gt;
  color: #166534;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.status-warning {&lt;br&gt;
  background: #fef3c7;&lt;br&gt;
  color: #92400e;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.status-fault {&lt;br&gt;
  background: #fee2e2;&lt;br&gt;
  color: #b91c1c;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.metrics {&lt;br&gt;
  display: grid;&lt;br&gt;
  grid-template-columns: repeat(4, 1fr);&lt;br&gt;
  gap: 16px;&lt;br&gt;
  margin-bottom: 18px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.metric-card,&lt;br&gt;
.panel {&lt;br&gt;
  border: 1px solid #e2e8f0;&lt;br&gt;
  background: #ffffff;&lt;br&gt;
  box-shadow: 0 14px 35px rgba(15, 23, 42, 0.06);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.metric-card {&lt;br&gt;
  padding: 22px;&lt;br&gt;
  border-radius: 16px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.metric-card span {&lt;br&gt;
  display: block;&lt;br&gt;
  margin-bottom: 10px;&lt;br&gt;
  color: #64748b;&lt;br&gt;
  font-size: 14px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.metric-card strong {&lt;br&gt;
  font-size: 25px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.panel {&lt;br&gt;
  margin-bottom: 18px;&lt;br&gt;
  padding: 24px;&lt;br&gt;
  border-radius: 18px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.panel-heading {&lt;br&gt;
  display: flex;&lt;br&gt;
  align-items: flex-start;&lt;br&gt;
  justify-content: space-between;&lt;br&gt;
  gap: 20px;&lt;br&gt;
  margin-bottom: 22px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.legend {&lt;br&gt;
  display: flex;&lt;br&gt;
  gap: 16px;&lt;br&gt;
  color: #64748b;&lt;br&gt;
  font-size: 13px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.legend span {&lt;br&gt;
  display: flex;&lt;br&gt;
  align-items: center;&lt;br&gt;
  gap: 7px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.legend i {&lt;br&gt;
  width: 24px;&lt;br&gt;
  height: 4px;&lt;br&gt;
  border-radius: 99px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.temperature-marker {&lt;br&gt;
  background: #ea580c;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.target-marker {&lt;br&gt;
  background: #2563eb;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;canvas {&lt;br&gt;
  display: block;&lt;br&gt;
  width: 100%;&lt;br&gt;
  height: auto;&lt;br&gt;
  border-radius: 12px;&lt;br&gt;
  background: #f8fafc;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.controls {&lt;br&gt;
  display: flex;&lt;br&gt;
  align-items: center;&lt;br&gt;
  justify-content: space-between;&lt;br&gt;
  gap: 24px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.control-buttons {&lt;br&gt;
  display: flex;&lt;br&gt;
  flex-wrap: wrap;&lt;br&gt;
  justify-content: flex-end;&lt;br&gt;
  gap: 9px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.control-buttons button,&lt;br&gt;
.secondary-button {&lt;br&gt;
  min-height: 42px;&lt;br&gt;
  padding: 9px 14px;&lt;br&gt;
  border: 1px solid #d7dee8;&lt;br&gt;
  border-radius: 9px;&lt;br&gt;
  background: #f8fafc;&lt;br&gt;
  color: #334155;&lt;br&gt;
  cursor: pointer;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.control-buttons button:hover,&lt;br&gt;
.control-buttons button.active {&lt;br&gt;
  border-color: #f59e0b;&lt;br&gt;
  background: #fff7ed;&lt;br&gt;
  color: #9a3412;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.secondary-button:hover {&lt;br&gt;
  background: #eef2f7;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.event-list {&lt;br&gt;
  display: grid;&lt;br&gt;
  gap: 10px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.event-item {&lt;br&gt;
  padding: 15px 16px;&lt;br&gt;
  border-left: 5px solid;&lt;br&gt;
  border-radius: 9px;&lt;br&gt;
  background: #f8fafc;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.event-item strong {&lt;br&gt;
  display: block;&lt;br&gt;
  margin-bottom: 5px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.event-item p {&lt;br&gt;
  margin-bottom: 5px;&lt;br&gt;
  color: #475569;&lt;br&gt;
  line-height: 1.5;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.event-item time {&lt;br&gt;
  color: #94a3b8;&lt;br&gt;
  font-size: 12px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.event-warning {&lt;br&gt;
  border-color: #f59e0b;&lt;br&gt;
  background: #fffbeb;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.event-fault {&lt;br&gt;
  border-color: #dc2626;&lt;br&gt;
  background: #fef2f2;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.empty-state {&lt;br&gt;
  margin: 0;&lt;br&gt;
  padding: 20px;&lt;br&gt;
  border: 1px dashed #cbd5e1;&lt;br&gt;
  border-radius: 10px;&lt;br&gt;
  color: #64748b;&lt;br&gt;
  text-align: center;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/media"&gt;@media&lt;/a&gt; (max-width: 900px) {&lt;br&gt;
  .metrics {&lt;br&gt;
    grid-template-columns: repeat(2, 1fr);&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.controls {&lt;br&gt;
    align-items: flex-start;&lt;br&gt;
    flex-direction: column;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.control-buttons {&lt;br&gt;
    justify-content: flex-start;&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/media"&gt;@media&lt;/a&gt; (max-width: 560px) {&lt;br&gt;
  body {&lt;br&gt;
    padding: 20px 12px;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.dashboard-header {&lt;br&gt;
    flex-direction: column;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.metrics {&lt;br&gt;
    grid-template-columns: 1fr;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.panel-heading {&lt;br&gt;
    flex-direction: column;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.legend {&lt;br&gt;
    flex-wrap: wrap;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.control-buttons {&lt;br&gt;
    display: grid;&lt;br&gt;
    width: 100%;&lt;br&gt;
    grid-template-columns: 1fr;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.control-buttons button {&lt;br&gt;
    width: 100%;&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Step 3: Define the Detection Thresholds&lt;/p&gt;

&lt;p&gt;We need configurable thresholds before implementing the diagnostic logic.&lt;/p&gt;

&lt;p&gt;Add the following code to "app.js":&lt;/p&gt;

&lt;p&gt;const config = {&lt;br&gt;
  sampleIntervalMs: 1000,&lt;br&gt;
  maximumHistorySize: 90,&lt;/p&gt;

&lt;p&gt;minimumActiveCurrent: 1,&lt;br&gt;
  expectedCurrent: 13.6,&lt;br&gt;
  currentTolerance: 0.25,&lt;/p&gt;

&lt;p&gt;minimumHeatingRate: 0.35,&lt;br&gt;
  maximumHeatingRate: 8,&lt;/p&gt;

&lt;p&gt;maximumOvershoot: 12,&lt;br&gt;
  maximumUnexpectedHeatingRate: 0.4,&lt;/p&gt;

&lt;p&gt;stuckSensorWindow: 10,&lt;br&gt;
  stuckSensorTolerance: 0.05,&lt;/p&gt;

&lt;p&gt;suddenTemperatureChange: 12,&lt;/p&gt;

&lt;p&gt;faultConfirmationSamples: 5&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;These values are only suitable for a demonstration.&lt;/p&gt;

&lt;p&gt;In a real installation, thresholds should be determined from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Heater power&lt;/li&gt;
&lt;li&gt;Heated material&lt;/li&gt;
&lt;li&gt;Process volume&lt;/li&gt;
&lt;li&gt;Thermal insulation&lt;/li&gt;
&lt;li&gt;Sensor response time&lt;/li&gt;
&lt;li&gt;Ambient conditions&lt;/li&gt;
&lt;li&gt;Normal heating curve&lt;/li&gt;
&lt;li&gt;Control strategy&lt;/li&gt;
&lt;li&gt;Sample interval&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 4: Create the Application State&lt;/p&gt;

&lt;p&gt;Add the following code below the configuration:&lt;/p&gt;

&lt;p&gt;const state = {&lt;br&gt;
  mode: "normal",&lt;br&gt;
  temperature: 24,&lt;br&gt;
  targetTemperature: 120,&lt;br&gt;
  current: 0,&lt;br&gt;
  heaterCommand: true,&lt;br&gt;
  history: [],&lt;br&gt;
  faultCounters: new Map(),&lt;br&gt;
  reportedFaults: new Set()&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;const elements = {&lt;br&gt;
  status: document.getElementById("system-status"),&lt;br&gt;
  temperature: document.getElementById(&lt;br&gt;
    "temperature-value"&lt;br&gt;
  ),&lt;br&gt;
  target: document.getElementById("target-value"),&lt;br&gt;
  current: document.getElementById("current-value"),&lt;br&gt;
  heatingRate: document.getElementById(&lt;br&gt;
    "heating-rate-value"&lt;br&gt;
  ),&lt;br&gt;
  eventList: document.getElementById("event-list"),&lt;br&gt;
  chart: document.getElementById(&lt;br&gt;
    "temperature-chart"&lt;br&gt;
  ),&lt;br&gt;
  clearEvents: document.getElementById(&lt;br&gt;
    "clear-events"&lt;br&gt;
  )&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;const chartContext = elements.chart.getContext("2d");&lt;/p&gt;

&lt;p&gt;The application stores recent samples in the "history" array.&lt;/p&gt;

&lt;p&gt;Fault counters are used to avoid reporting an error after only one abnormal reading.&lt;/p&gt;

&lt;p&gt;Step 5: Simulate Heater Data&lt;/p&gt;

&lt;p&gt;Add the following simulation function:&lt;/p&gt;

&lt;p&gt;function generateSample() {&lt;br&gt;
  const noise = randomBetween(-0.08, 0.08);&lt;/p&gt;

&lt;p&gt;let temperatureChange = 0;&lt;br&gt;
  let current = 0;&lt;br&gt;
  let heaterCommand =&lt;br&gt;
    state.temperature &amp;lt; state.targetTemperature;&lt;/p&gt;

&lt;p&gt;switch (state.mode) {&lt;br&gt;
    case "normal":&lt;br&gt;
      current = heaterCommand&lt;br&gt;
        ? randomBetween(13.2, 13.9)&lt;br&gt;
        : 0;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  temperatureChange = heaterCommand
    ? randomBetween(0.11, 0.18)
    : randomBetween(-0.04, -0.01);

  break;

case "weak-heater":
  current = heaterCommand
    ? randomBetween(12.9, 13.8)
    : 0;

  temperatureChange = heaterCommand
    ? randomBetween(0.01, 0.035)
    : -0.02;

  break;

case "open-circuit":
  current = 0;
  temperatureChange = -0.02;
  break;

case "stuck-sensor":
  current = heaterCommand ? 13.6 : 0;
  temperatureChange = 0;
  break;

case "relay-stuck":
  heaterCommand = false;
  current = randomBetween(13.2, 13.9);
  temperatureChange = randomBetween(0.1, 0.16);
  break;

case "overheating":
  current = randomBetween(13.2, 13.9);
  temperatureChange = randomBetween(0.32, 0.45);
  heaterCommand = true;
  break;

default:
  throw new Error(
    `Unknown simulation mode: ${state.mode}`
  );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;state.temperature += temperatureChange + noise;&lt;/p&gt;

&lt;p&gt;if (&lt;br&gt;
    state.mode === "normal" &amp;amp;&amp;amp;&lt;br&gt;
    state.temperature &amp;gt;= state.targetTemperature&lt;br&gt;
  ) {&lt;br&gt;
    state.temperature =&lt;br&gt;
      state.targetTemperature +&lt;br&gt;
      randomBetween(-0.8, 0.8);&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;state.current = Math.max(0, current);&lt;br&gt;
  state.heaterCommand = heaterCommand;&lt;/p&gt;

&lt;p&gt;return {&lt;br&gt;
    timestamp: Date.now(),&lt;br&gt;
    temperature: state.temperature,&lt;br&gt;
    current: state.current,&lt;br&gt;
    heaterCommand: state.heaterCommand,&lt;br&gt;
    targetTemperature: state.targetTemperature&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;function randomBetween(minimum, maximum) {&lt;br&gt;
  return (&lt;br&gt;
    Math.random() * (maximum - minimum) +&lt;br&gt;
    minimum&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Each simulation mode represents a different operating condition.&lt;/p&gt;

&lt;p&gt;Normal operation&lt;/p&gt;

&lt;p&gt;The heater current is close to its expected value and temperature rises normally.&lt;/p&gt;

&lt;p&gt;Weak heater&lt;/p&gt;

&lt;p&gt;Current is present, but the temperature rises too slowly.&lt;/p&gt;

&lt;p&gt;Possible causes include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduced supply voltage&lt;/li&gt;
&lt;li&gt;Incorrect element resistance&lt;/li&gt;
&lt;li&gt;Partial element failure&lt;/li&gt;
&lt;li&gt;Excessive heat loss&lt;/li&gt;
&lt;li&gt;Poor thermal contact&lt;/li&gt;
&lt;li&gt;Insufficient heater capacity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open circuit&lt;/p&gt;

&lt;p&gt;The controller requests heat, but no current flows.&lt;/p&gt;

&lt;p&gt;Possible causes include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Burned heating element&lt;/li&gt;
&lt;li&gt;Open fuse&lt;/li&gt;
&lt;li&gt;Broken wire&lt;/li&gt;
&lt;li&gt;Failed contactor&lt;/li&gt;
&lt;li&gt;Disconnected terminal&lt;/li&gt;
&lt;li&gt;Protection device activated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stuck sensor&lt;/p&gt;

&lt;p&gt;The sensor value does not change despite heater operation.&lt;/p&gt;

&lt;p&gt;Possible causes include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frozen software value&lt;/li&gt;
&lt;li&gt;Failed transmitter&lt;/li&gt;
&lt;li&gt;Disconnected sensor&lt;/li&gt;
&lt;li&gt;Incorrect analog input configuration&lt;/li&gt;
&lt;li&gt;Communication failure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Relay stuck on&lt;/p&gt;

&lt;p&gt;The controller sends an off command, but current continues to flow and temperature rises.&lt;/p&gt;

&lt;p&gt;This can happen when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A contactor is mechanically welded&lt;/li&gt;
&lt;li&gt;A solid-state relay fails in a closed state&lt;/li&gt;
&lt;li&gt;Control wiring is incorrect&lt;/li&gt;
&lt;li&gt;The output signal is bypassed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overheating&lt;/p&gt;

&lt;p&gt;The temperature rises too rapidly or exceeds the acceptable target range.&lt;/p&gt;

&lt;p&gt;Step 6: Calculate the Heating Rate&lt;/p&gt;

&lt;p&gt;The temperature slope is one of the most useful values for heater monitoring.&lt;/p&gt;

&lt;p&gt;Add this function:&lt;/p&gt;

&lt;p&gt;function calculateHeatingRate(history) {&lt;br&gt;
  if (history.length &amp;lt; 2) {&lt;br&gt;
    return 0;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;const windowSize = Math.min(15, history.length);&lt;/p&gt;

&lt;p&gt;const firstSample =&lt;br&gt;
    history[history.length - windowSize];&lt;/p&gt;

&lt;p&gt;const lastSample =&lt;br&gt;
    history[history.length - 1];&lt;/p&gt;

&lt;p&gt;const temperatureDifference =&lt;br&gt;
    lastSample.temperature -&lt;br&gt;
    firstSample.temperature;&lt;/p&gt;

&lt;p&gt;const timeDifferenceMinutes =&lt;br&gt;
    (&lt;br&gt;
      lastSample.timestamp -&lt;br&gt;
      firstSample.timestamp&lt;br&gt;
    ) / 60000;&lt;/p&gt;

&lt;p&gt;if (timeDifferenceMinutes &amp;lt;= 0) {&lt;br&gt;
    return 0;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;return (&lt;br&gt;
    temperatureDifference /&lt;br&gt;
    timeDifferenceMinutes&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The formula is:&lt;/p&gt;

&lt;p&gt;Heating rate =&lt;br&gt;
Temperature change ÷ Time change&lt;/p&gt;

&lt;p&gt;The result is expressed in degrees Celsius per minute.&lt;/p&gt;

&lt;p&gt;Using several samples instead of only two readings reduces the effect of sensor noise.&lt;/p&gt;

&lt;p&gt;Step 7: Create the Diagnostic Rules&lt;/p&gt;

&lt;p&gt;Add the main analysis function:&lt;/p&gt;

&lt;p&gt;function analyzeSample(sample) {&lt;br&gt;
  const events = [];&lt;br&gt;
  const heatingRate = calculateHeatingRate(&lt;br&gt;
    state.history&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;detectOpenCircuit(sample, events);&lt;br&gt;
  detectWeakHeating(sample, heatingRate, events);&lt;br&gt;
  detectUnexpectedHeating(&lt;br&gt;
    sample,&lt;br&gt;
    heatingRate,&lt;br&gt;
    events&lt;br&gt;
  );&lt;br&gt;
  detectOverheating(sample, events);&lt;br&gt;
  detectStuckSensor(sample, events);&lt;br&gt;
  detectSuddenTemperatureChange(events);&lt;/p&gt;

&lt;p&gt;updateFaultConfirmations(events);&lt;/p&gt;

&lt;p&gt;return {&lt;br&gt;
    heatingRate,&lt;br&gt;
    events&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Now add each individual rule.&lt;/p&gt;

&lt;p&gt;Rule 1: Detect an Open Circuit&lt;/p&gt;

&lt;p&gt;function detectOpenCircuit(sample, events) {&lt;br&gt;
  const heatingRequested =&lt;br&gt;
    sample.heaterCommand === true;&lt;/p&gt;

&lt;p&gt;const noCurrent =&lt;br&gt;
    sample.current &amp;lt;&lt;br&gt;
    config.minimumActiveCurrent;&lt;/p&gt;

&lt;p&gt;if (heatingRequested &amp;amp;&amp;amp; noCurrent) {&lt;br&gt;
    events.push({&lt;br&gt;
      code: "OPEN_CIRCUIT",&lt;br&gt;
      level: "fault",&lt;br&gt;
      title: "No heater current detected",&lt;br&gt;
      message:&lt;br&gt;
        "The controller is requesting heat, but the measured current is below the active threshold."&lt;br&gt;
    });&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The rule compares the controller command with measured current.&lt;/p&gt;

&lt;p&gt;A heater command without current is a strong indicator of an electrical fault.&lt;/p&gt;

&lt;p&gt;Rule 2: Detect Weak Heating&lt;/p&gt;

&lt;p&gt;function detectWeakHeating(&lt;br&gt;
  sample,&lt;br&gt;
  heatingRate,&lt;br&gt;
  events&lt;br&gt;
) {&lt;br&gt;
  const heaterIsActive =&lt;br&gt;
    sample.heaterCommand &amp;amp;&amp;amp;&lt;br&gt;
    sample.current &amp;gt;=&lt;br&gt;
      config.minimumActiveCurrent;&lt;/p&gt;

&lt;p&gt;const enoughSamples =&lt;br&gt;
    state.history.length &amp;gt;= 12;&lt;/p&gt;

&lt;p&gt;const rateIsTooLow =&lt;br&gt;
    heatingRate &amp;lt;&lt;br&gt;
    config.minimumHeatingRate;&lt;/p&gt;

&lt;p&gt;if (&lt;br&gt;
    heaterIsActive &amp;amp;&amp;amp;&lt;br&gt;
    enoughSamples &amp;amp;&amp;amp;&lt;br&gt;
    rateIsTooLow&lt;br&gt;
  ) {&lt;br&gt;
    events.push({&lt;br&gt;
      code: "LOW_HEATING_RATE",&lt;br&gt;
      level: "warning",&lt;br&gt;
      title: "Heating rate is too low",&lt;br&gt;
      message:&lt;br&gt;
        &lt;code&gt;The heater is drawing current, but the measured heating rate is only ${heatingRate.toFixed(2)} °C/min.&lt;/code&gt;&lt;br&gt;
    });&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This condition can detect a heater that still consumes electrical power but does not transfer enough heat into the process.&lt;/p&gt;

&lt;p&gt;Rule 3: Detect Unexpected Heating&lt;/p&gt;

&lt;p&gt;function detectUnexpectedHeating(&lt;br&gt;
  sample,&lt;br&gt;
  heatingRate,&lt;br&gt;
  events&lt;br&gt;
) {&lt;br&gt;
  const heaterShouldBeOff =&lt;br&gt;
    sample.heaterCommand === false;&lt;/p&gt;

&lt;p&gt;const currentIsFlowing =&lt;br&gt;
    sample.current &amp;gt;=&lt;br&gt;
    config.minimumActiveCurrent;&lt;/p&gt;

&lt;p&gt;const temperatureIsRising =&lt;br&gt;
    heatingRate &amp;gt;&lt;br&gt;
    config.maximumUnexpectedHeatingRate;&lt;/p&gt;

&lt;p&gt;if (&lt;br&gt;
    heaterShouldBeOff &amp;amp;&amp;amp;&lt;br&gt;
    currentIsFlowing &amp;amp;&amp;amp;&lt;br&gt;
    temperatureIsRising&lt;br&gt;
  ) {&lt;br&gt;
    events.push({&lt;br&gt;
      code: "RELAY_STUCK_ON",&lt;br&gt;
      level: "fault",&lt;br&gt;
      title: "Possible relay stuck on",&lt;br&gt;
      message:&lt;br&gt;
        "Current is flowing and temperature is increasing while the heater command is off."&lt;br&gt;
    });&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This is one of the most important safety rules in the application.&lt;/p&gt;

&lt;p&gt;A stuck contactor or solid-state relay may continue energizing the heater even after the controller turns the output off.&lt;/p&gt;

&lt;p&gt;Rule 4: Detect Overheating&lt;/p&gt;

&lt;p&gt;function detectOverheating(sample, events) {&lt;br&gt;
  const maximumAllowedTemperature =&lt;br&gt;
    sample.targetTemperature +&lt;br&gt;
    config.maximumOvershoot;&lt;/p&gt;

&lt;p&gt;if (&lt;br&gt;
    sample.temperature &amp;gt;&lt;br&gt;
    maximumAllowedTemperature&lt;br&gt;
  ) {&lt;br&gt;
    events.push({&lt;br&gt;
      code: "OVER_TEMPERATURE",&lt;br&gt;
      level: "fault",&lt;br&gt;
      title: "Overtemperature detected",&lt;br&gt;
      message:&lt;br&gt;
        &lt;code&gt;The process temperature is ${sample.temperature.toFixed(1)} °C, above the maximum allowed value of ${maximumAllowedTemperature.toFixed(1)} °C.&lt;/code&gt;&lt;br&gt;
    });&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;A real industrial system should also have an independent hardware overtemperature protection device.&lt;/p&gt;

&lt;p&gt;Software monitoring must not be the only safety layer.&lt;/p&gt;

&lt;p&gt;Rule 5: Detect a Stuck Sensor&lt;/p&gt;

&lt;p&gt;function detectStuckSensor(sample, events) {&lt;br&gt;
  const windowSize =&lt;br&gt;
    config.stuckSensorWindow;&lt;/p&gt;

&lt;p&gt;if (state.history.length &amp;lt; windowSize) {&lt;br&gt;
    return;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;const recentSamples =&lt;br&gt;
    state.history.slice(-windowSize);&lt;/p&gt;

&lt;p&gt;const temperatures =&lt;br&gt;
    recentSamples.map(&lt;br&gt;
      (item) =&amp;gt; item.temperature&lt;br&gt;
    );&lt;/p&gt;

&lt;p&gt;const minimumTemperature =&lt;br&gt;
    Math.min(...temperatures);&lt;/p&gt;

&lt;p&gt;const maximumTemperature =&lt;br&gt;
    Math.max(...temperatures);&lt;/p&gt;

&lt;p&gt;const totalVariation =&lt;br&gt;
    maximumTemperature -&lt;br&gt;
    minimumTemperature;&lt;/p&gt;

&lt;p&gt;const heaterHasBeenActive =&lt;br&gt;
    recentSamples.every(&lt;br&gt;
      (item) =&amp;gt;&lt;br&gt;
        item.heaterCommand &amp;amp;&amp;amp;&lt;br&gt;
        item.current &amp;gt;=&lt;br&gt;
          config.minimumActiveCurrent&lt;br&gt;
    );&lt;/p&gt;

&lt;p&gt;if (&lt;br&gt;
    heaterHasBeenActive &amp;amp;&amp;amp;&lt;br&gt;
    totalVariation &amp;lt;=&lt;br&gt;
      config.stuckSensorTolerance&lt;br&gt;
  ) {&lt;br&gt;
    events.push({&lt;br&gt;
      code: "STUCK_SENSOR",&lt;br&gt;
      level: "fault",&lt;br&gt;
      title: "Temperature sensor may be stuck",&lt;br&gt;
      message:&lt;br&gt;
        "The temperature value has not changed while the heater has remained active."&lt;br&gt;
    });&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The rule checks whether the sensor value remains nearly constant over several samples.&lt;/p&gt;

&lt;p&gt;Rule 6: Detect Sudden Temperature Changes&lt;/p&gt;

&lt;p&gt;function detectSuddenTemperatureChange(&lt;br&gt;
  events&lt;br&gt;
) {&lt;br&gt;
  if (state.history.length &amp;lt; 2) {&lt;br&gt;
    return;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;const currentSample =&lt;br&gt;
    state.history[state.history.length - 1];&lt;/p&gt;

&lt;p&gt;const previousSample =&lt;br&gt;
    state.history[state.history.length - 2];&lt;/p&gt;

&lt;p&gt;const difference = Math.abs(&lt;br&gt;
    currentSample.temperature -&lt;br&gt;
    previousSample.temperature&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;if (&lt;br&gt;
    difference &amp;gt;&lt;br&gt;
    config.suddenTemperatureChange&lt;br&gt;
  ) {&lt;br&gt;
    events.push({&lt;br&gt;
      code: "SENSOR_SPIKE",&lt;br&gt;
      level: "warning",&lt;br&gt;
      title: "Unrealistic temperature change",&lt;br&gt;
      message:&lt;br&gt;
        &lt;code&gt;The sensor value changed by ${difference.toFixed(1)} °C between two consecutive samples.&lt;/code&gt;&lt;br&gt;
    });&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;A large one-second temperature jump may indicate electrical noise, a loose thermocouple connection, data corruption, or a scaling problem.&lt;/p&gt;

&lt;p&gt;Step 8: Prevent False Alarms&lt;/p&gt;

&lt;p&gt;One abnormal sample should not always generate a fault.&lt;/p&gt;

&lt;p&gt;Add this confirmation system:&lt;/p&gt;

&lt;p&gt;function updateFaultConfirmations(events) {&lt;br&gt;
  const activeCodes = new Set(&lt;br&gt;
    events.map((event) =&amp;gt; event.code)&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;for (const event of events) {&lt;br&gt;
    const currentCount =&lt;br&gt;
      state.faultCounters.get(event.code) ?? 0;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const newCount = currentCount + 1;

state.faultCounters.set(
  event.code,
  newCount
);

if (
  newCount &amp;gt;=
    config.faultConfirmationSamples &amp;amp;&amp;amp;
  !state.reportedFaults.has(event.code)
) {
  addDiagnosticEvent(event);
  state.reportedFaults.add(event.code);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;for (&lt;br&gt;
    const code&lt;br&gt;
    of state.faultCounters.keys()&lt;br&gt;
  ) {&lt;br&gt;
    if (!activeCodes.has(code)) {&lt;br&gt;
      state.faultCounters.set(code, 0);&lt;br&gt;
      state.reportedFaults.delete(code);&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The anomaly must remain active for several consecutive samples before it is reported.&lt;/p&gt;

&lt;p&gt;This approach is called persistence filtering or fault debounce.&lt;/p&gt;

&lt;p&gt;It reduces false alarms caused by brief electrical noise or normal process variation.&lt;/p&gt;

&lt;p&gt;Step 9: Update the Interface&lt;/p&gt;

&lt;p&gt;Add the following functions:&lt;/p&gt;

&lt;p&gt;function updateMetrics(&lt;br&gt;
  sample,&lt;br&gt;
  heatingRate&lt;br&gt;
) {&lt;br&gt;
  elements.temperature.textContent =&lt;br&gt;
    &lt;code&gt;${sample.temperature.toFixed(1)} °C&lt;/code&gt;;&lt;/p&gt;

&lt;p&gt;elements.target.textContent =&lt;br&gt;
    &lt;code&gt;${sample.targetTemperature.toFixed(1)} °C&lt;/code&gt;;&lt;/p&gt;

&lt;p&gt;elements.current.textContent =&lt;br&gt;
    &lt;code&gt;${sample.current.toFixed(1)} A&lt;/code&gt;;&lt;/p&gt;

&lt;p&gt;elements.heatingRate.textContent =&lt;br&gt;
    &lt;code&gt;${heatingRate.toFixed(2)} °C/min&lt;/code&gt;;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;function updateStatus(events) {&lt;br&gt;
  const hasFault = events.some(&lt;br&gt;
    (event) =&amp;gt; event.level === "fault"&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;const hasWarning = events.some(&lt;br&gt;
    (event) =&amp;gt; event.level === "warning"&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;elements.status.className = "status";&lt;/p&gt;

&lt;p&gt;if (hasFault) {&lt;br&gt;
    elements.status.textContent = "FAULT";&lt;br&gt;
    elements.status.classList.add(&lt;br&gt;
      "status-fault"&lt;br&gt;
    );&lt;br&gt;
    return;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (hasWarning) {&lt;br&gt;
    elements.status.textContent = "WARNING";&lt;br&gt;
    elements.status.classList.add(&lt;br&gt;
      "status-warning"&lt;br&gt;
    );&lt;br&gt;
    return;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;elements.status.textContent = "NORMAL";&lt;br&gt;
  elements.status.classList.add(&lt;br&gt;
    "status-normal"&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Add the event renderer:&lt;/p&gt;

&lt;p&gt;function addDiagnosticEvent(event) {&lt;br&gt;
  const emptyState =&lt;br&gt;
    elements.eventList.querySelector(&lt;br&gt;
      ".empty-state"&lt;br&gt;
    );&lt;/p&gt;

&lt;p&gt;if (emptyState) {&lt;br&gt;
    emptyState.remove();&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;const item =&lt;br&gt;
    document.createElement("article");&lt;/p&gt;

&lt;p&gt;item.className =&lt;br&gt;
    &lt;code&gt;event-item event-${event.level}&lt;/code&gt;;&lt;/p&gt;

&lt;p&gt;const timestamp =&lt;br&gt;
    new Date().toLocaleTimeString();&lt;/p&gt;

&lt;p&gt;item.innerHTML = `&lt;br&gt;
    &lt;strong&gt;${escapeHtml(event.title)}&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;p&amp;gt;${escapeHtml(event.message)}&amp;lt;/p&amp;gt;

&amp;lt;time&amp;gt;${timestamp}&amp;lt;/time&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;`;&lt;/p&gt;

&lt;p&gt;elements.eventList.prepend(item);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;function escapeHtml(value) {&lt;br&gt;
  const element =&lt;br&gt;
    document.createElement("div");&lt;/p&gt;

&lt;p&gt;element.textContent = value;&lt;/p&gt;

&lt;p&gt;return element.innerHTML;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Even though our messages are generated internally, escaping dynamic values is a good habit when using "innerHTML".&lt;/p&gt;

&lt;p&gt;Step 10: Draw the Temperature Chart&lt;/p&gt;

&lt;p&gt;We can create a lightweight chart with the Canvas API instead of using an external library.&lt;/p&gt;

&lt;p&gt;Add this function:&lt;/p&gt;

&lt;p&gt;function drawChart() {&lt;br&gt;
  const canvas = elements.chart;&lt;br&gt;
  const context = chartContext;&lt;/p&gt;

&lt;p&gt;const width = canvas.width;&lt;br&gt;
  const height = canvas.height;&lt;/p&gt;

&lt;p&gt;const padding = {&lt;br&gt;
    top: 28,&lt;br&gt;
    right: 30,&lt;br&gt;
    bottom: 38,&lt;br&gt;
    left: 58&lt;br&gt;
  };&lt;/p&gt;

&lt;p&gt;context.clearRect(0, 0, width, height);&lt;/p&gt;

&lt;p&gt;context.fillStyle = "#f8fafc";&lt;br&gt;
  context.fillRect(0, 0, width, height);&lt;/p&gt;

&lt;p&gt;if (state.history.length &amp;lt; 2) {&lt;br&gt;
    return;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;const temperatures =&lt;br&gt;
    state.history.map(&lt;br&gt;
      (sample) =&amp;gt; sample.temperature&lt;br&gt;
    );&lt;/p&gt;

&lt;p&gt;const targets =&lt;br&gt;
    state.history.map(&lt;br&gt;
      (sample) =&amp;gt; sample.targetTemperature&lt;br&gt;
    );&lt;/p&gt;

&lt;p&gt;const allValues = [&lt;br&gt;
    ...temperatures,&lt;br&gt;
    ...targets&lt;br&gt;
  ];&lt;/p&gt;

&lt;p&gt;const minimumValue =&lt;br&gt;
    Math.floor(Math.min(...allValues) - 10);&lt;/p&gt;

&lt;p&gt;const maximumValue =&lt;br&gt;
    Math.ceil(Math.max(...allValues) + 10);&lt;/p&gt;

&lt;p&gt;drawGrid(&lt;br&gt;
    context,&lt;br&gt;
    width,&lt;br&gt;
    height,&lt;br&gt;
    padding,&lt;br&gt;
    minimumValue,&lt;br&gt;
    maximumValue&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;drawLine({&lt;br&gt;
    context,&lt;br&gt;
    values: temperatures,&lt;br&gt;
    color: "#ea580c",&lt;br&gt;
    width,&lt;br&gt;
    height,&lt;br&gt;
    padding,&lt;br&gt;
    minimumValue,&lt;br&gt;
    maximumValue&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;drawLine({&lt;br&gt;
    context,&lt;br&gt;
    values: targets,&lt;br&gt;
    color: "#2563eb",&lt;br&gt;
    width,&lt;br&gt;
    height,&lt;br&gt;
    padding,&lt;br&gt;
    minimumValue,&lt;br&gt;
    maximumValue,&lt;br&gt;
    dashed: true&lt;br&gt;
  });&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Now add the helper functions:&lt;/p&gt;

&lt;p&gt;function drawGrid(&lt;br&gt;
  context,&lt;br&gt;
  width,&lt;br&gt;
  height,&lt;br&gt;
  padding,&lt;br&gt;
  minimumValue,&lt;br&gt;
  maximumValue&lt;br&gt;
) {&lt;br&gt;
  const chartWidth =&lt;br&gt;
    width -&lt;br&gt;
    padding.left -&lt;br&gt;
    padding.right;&lt;/p&gt;

&lt;p&gt;const chartHeight =&lt;br&gt;
    height -&lt;br&gt;
    padding.top -&lt;br&gt;
    padding.bottom;&lt;/p&gt;

&lt;p&gt;context.strokeStyle = "#dbe4ee";&lt;br&gt;
  context.fillStyle = "#64748b";&lt;br&gt;
  context.font = "12px Arial";&lt;br&gt;
  context.lineWidth = 1;&lt;/p&gt;

&lt;p&gt;const horizontalLines = 5;&lt;/p&gt;

&lt;p&gt;for (&lt;br&gt;
    let index = 0;&lt;br&gt;
    index &amp;lt;= horizontalLines;&lt;br&gt;
    index += 1&lt;br&gt;
  ) {&lt;br&gt;
    const ratio =&lt;br&gt;
      index / horizontalLines;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const y =
  padding.top +
  chartHeight * ratio;

const value =
  maximumValue -
  (
    maximumValue -
    minimumValue
  ) * ratio;

context.beginPath();
context.moveTo(padding.left, y);
context.lineTo(
  padding.left + chartWidth,
  y
);
context.stroke();

context.fillText(
  `${value.toFixed(0)} °C`,
  8,
  y + 4
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;function drawLine({&lt;br&gt;
  context,&lt;br&gt;
  values,&lt;br&gt;
  color,&lt;br&gt;
  width,&lt;br&gt;
  height,&lt;br&gt;
  padding,&lt;br&gt;
  minimumValue,&lt;br&gt;
  maximumValue,&lt;br&gt;
  dashed = false&lt;br&gt;
}) {&lt;br&gt;
  const chartWidth =&lt;br&gt;
    width -&lt;br&gt;
    padding.left -&lt;br&gt;
    padding.right;&lt;/p&gt;

&lt;p&gt;const chartHeight =&lt;br&gt;
    height -&lt;br&gt;
    padding.top -&lt;br&gt;
    padding.bottom;&lt;/p&gt;

&lt;p&gt;const range =&lt;br&gt;
    maximumValue -&lt;br&gt;
    minimumValue;&lt;/p&gt;

&lt;p&gt;context.beginPath();&lt;br&gt;
  context.strokeStyle = color;&lt;br&gt;
  context.lineWidth = 3;&lt;br&gt;
  context.lineJoin = "round";&lt;br&gt;
  context.lineCap = "round";&lt;/p&gt;

&lt;p&gt;context.setLineDash(&lt;br&gt;
    dashed ? [9, 8] : []&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;values.forEach((value, index) =&amp;gt; {&lt;br&gt;
    const x =&lt;br&gt;
      padding.left +&lt;br&gt;
      (&lt;br&gt;
        index /&lt;br&gt;
        Math.max(values.length - 1, 1)&lt;br&gt;
      ) *&lt;br&gt;
      chartWidth;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const normalizedValue =
  (value - minimumValue) / range;

const y =
  padding.top +
  chartHeight -
  normalizedValue * chartHeight;

if (index === 0) {
  context.moveTo(x, y);
} else {
  context.lineTo(x, y);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;});&lt;/p&gt;

&lt;p&gt;context.stroke();&lt;br&gt;
  context.setLineDash([]);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Step 11: Run the Monitoring Loop&lt;/p&gt;

&lt;p&gt;Add the main application loop:&lt;/p&gt;

&lt;p&gt;function monitoringLoop() {&lt;br&gt;
  const sample = generateSample();&lt;/p&gt;

&lt;p&gt;state.history.push(sample);&lt;/p&gt;

&lt;p&gt;if (&lt;br&gt;
    state.history.length &amp;gt;&lt;br&gt;
    config.maximumHistorySize&lt;br&gt;
  ) {&lt;br&gt;
    state.history.shift();&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;const analysis =&lt;br&gt;
    analyzeSample(sample);&lt;/p&gt;

&lt;p&gt;updateMetrics(&lt;br&gt;
    sample,&lt;br&gt;
    analysis.heatingRate&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;updateStatus(analysis.events);&lt;br&gt;
  drawChart();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Start the loop:&lt;/p&gt;

&lt;p&gt;setInterval(&lt;br&gt;
  monitoringLoop,&lt;br&gt;
  config.sampleIntervalMs&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;monitoringLoop();&lt;/p&gt;

&lt;p&gt;Step 12: Add the Simulation Controls&lt;/p&gt;

&lt;p&gt;Add the following event listeners:&lt;/p&gt;

&lt;p&gt;const modeButtons =&lt;br&gt;
  document.querySelectorAll("[data-mode]");&lt;/p&gt;

&lt;p&gt;modeButtons.forEach((button) =&amp;gt; {&lt;br&gt;
  button.addEventListener("click", () =&amp;gt; {&lt;br&gt;
    state.mode = button.dataset.mode;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;state.faultCounters.clear();
state.reportedFaults.clear();

modeButtons.forEach(
  (item) =&amp;gt;
    item.classList.remove("active")
);

button.classList.add("active");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;});&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;elements.clearEvents.addEventListener(&lt;br&gt;
  "click",&lt;br&gt;
  () =&amp;gt; {&lt;br&gt;
    elements.eventList.innerHTML = &lt;code&gt;&lt;br&gt;
      &amp;lt;p class="empty-state"&amp;gt;&lt;br&gt;
        No abnormal condition detected.&lt;br&gt;
      &amp;lt;/p&amp;gt;&lt;br&gt;
&lt;/code&gt;;&lt;br&gt;
  }&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;document&lt;br&gt;
  .querySelector('[data-mode="normal"]')&lt;br&gt;
  .classList.add("active");&lt;/p&gt;

&lt;p&gt;The user can now switch between normal operation and different failure scenarios.&lt;/p&gt;

&lt;p&gt;Understanding the Detection Strategy&lt;/p&gt;

&lt;p&gt;The detector does not decide whether the heater is healthy based on only one measurement.&lt;/p&gt;

&lt;p&gt;It combines several process signals:&lt;/p&gt;

&lt;p&gt;Controller command&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Electrical current&lt;/li&gt;
&lt;li&gt;Temperature&lt;/li&gt;
&lt;li&gt;Temperature rate of change&lt;/li&gt;
&lt;li&gt;Target temperature&lt;/li&gt;
&lt;li&gt;Time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is more reliable than monitoring temperature alone.&lt;/p&gt;

&lt;p&gt;For example, a low temperature does not necessarily indicate a fault. The process may have just started.&lt;/p&gt;

&lt;p&gt;However, the combination below is more meaningful:&lt;/p&gt;

&lt;p&gt;Heater command = ON&lt;br&gt;
Current = Normal&lt;br&gt;
Heating rate = Very low&lt;/p&gt;

&lt;p&gt;This pattern may indicate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Insufficient heater capacity&lt;/li&gt;
&lt;li&gt;Heat loss&lt;/li&gt;
&lt;li&gt;Partial heater failure&lt;/li&gt;
&lt;li&gt;Incorrect process conditions&lt;/li&gt;
&lt;li&gt;Mechanical installation problems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another important pattern is:&lt;/p&gt;

&lt;p&gt;Heater command = OFF&lt;br&gt;
Current = Present&lt;br&gt;
Temperature = Rising&lt;/p&gt;

&lt;p&gt;This combination strongly suggests a failed switching device or incorrect control wiring.&lt;/p&gt;

&lt;p&gt;Improving Heating-Rate Calculation with Linear Regression&lt;/p&gt;

&lt;p&gt;The basic heating-rate function compares the first and last samples in a time window.&lt;/p&gt;

&lt;p&gt;A more stable method is linear regression.&lt;/p&gt;

&lt;p&gt;Add this alternative function:&lt;/p&gt;

&lt;p&gt;function calculateHeatingRateWithRegression(&lt;br&gt;
  history&lt;br&gt;
) {&lt;br&gt;
  const windowSize =&lt;br&gt;
    Math.min(20, history.length);&lt;/p&gt;

&lt;p&gt;if (windowSize &amp;lt; 2) {&lt;br&gt;
    return 0;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;const samples =&lt;br&gt;
    history.slice(-windowSize);&lt;/p&gt;

&lt;p&gt;const startTime =&lt;br&gt;
    samples[0].timestamp;&lt;/p&gt;

&lt;p&gt;const points = samples.map(&lt;br&gt;
    (sample) =&amp;gt; ({&lt;br&gt;
      x:&lt;br&gt;
        (&lt;br&gt;
          sample.timestamp -&lt;br&gt;
          startTime&lt;br&gt;
        ) / 60000,&lt;br&gt;
      y: sample.temperature&lt;br&gt;
    })&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;const count = points.length;&lt;/p&gt;

&lt;p&gt;const sumX = points.reduce(&lt;br&gt;
    (total, point) =&amp;gt;&lt;br&gt;
      total + point.x,&lt;br&gt;
    0&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;const sumY = points.reduce(&lt;br&gt;
    (total, point) =&amp;gt;&lt;br&gt;
      total + point.y,&lt;br&gt;
    0&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;const sumXY = points.reduce(&lt;br&gt;
    (total, point) =&amp;gt;&lt;br&gt;
      total + point.x * point.y,&lt;br&gt;
    0&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;const sumXX = points.reduce(&lt;br&gt;
    (total, point) =&amp;gt;&lt;br&gt;
      total + point.x * point.x,&lt;br&gt;
    0&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;const denominator =&lt;br&gt;
    count * sumXX -&lt;br&gt;
    sumX * sumX;&lt;/p&gt;

&lt;p&gt;if (denominator === 0) {&lt;br&gt;
    return 0;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;return (&lt;br&gt;
    count * sumXY -&lt;br&gt;
    sumX * sumY&lt;br&gt;
  ) / denominator;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The returned slope represents the estimated temperature change per minute.&lt;/p&gt;

&lt;p&gt;Linear regression is less sensitive to individual noisy readings.&lt;/p&gt;

&lt;p&gt;Connecting the Dashboard to Real Data&lt;/p&gt;

&lt;p&gt;The simulation can later be replaced with data from a real system.&lt;/p&gt;

&lt;p&gt;Possible data sources include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WebSocket server&lt;/li&gt;
&lt;li&gt;MQTT broker with a web gateway&lt;/li&gt;
&lt;li&gt;REST API&lt;/li&gt;
&lt;li&gt;Industrial PC&lt;/li&gt;
&lt;li&gt;PLC communication gateway&lt;/li&gt;
&lt;li&gt;Modbus TCP service&lt;/li&gt;
&lt;li&gt;OPC UA gateway&lt;/li&gt;
&lt;li&gt;Microcontroller&lt;/li&gt;
&lt;li&gt;ESP32&lt;/li&gt;
&lt;li&gt;Raspberry Pi&lt;/li&gt;
&lt;li&gt;Cloud IoT platform&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A WebSocket connection may look like this:&lt;/p&gt;

&lt;p&gt;const socket = new WebSocket(&lt;br&gt;
  "wss://example.com/heater-data"&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;socket.addEventListener(&lt;br&gt;
  "message",&lt;br&gt;
  (message) =&amp;gt; {&lt;br&gt;
    try {&lt;br&gt;
      const sample =&lt;br&gt;
        JSON.parse(message.data);&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  validateIncomingSample(sample);

  state.history.push(sample);

  const analysis =
    analyzeSample(sample);

  updateMetrics(
    sample,
    analysis.heatingRate
  );

  updateStatus(analysis.events);
  drawChart();
} catch (error) {
  console.error(
    "Invalid heater data:",
    error
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;Incoming data must be validated before it is used.&lt;/p&gt;

&lt;p&gt;function validateIncomingSample(sample) {&lt;br&gt;
  const requiredNumericFields = [&lt;br&gt;
    "timestamp",&lt;br&gt;
    "temperature",&lt;br&gt;
    "current",&lt;br&gt;
    "targetTemperature"&lt;br&gt;
  ];&lt;/p&gt;

&lt;p&gt;for (&lt;br&gt;
    const field&lt;br&gt;
    of requiredNumericFields&lt;br&gt;
  ) {&lt;br&gt;
    if (&lt;br&gt;
      !Number.isFinite(sample[field])&lt;br&gt;
    ) {&lt;br&gt;
      throw new TypeError(&lt;br&gt;
        &lt;code&gt;Invalid ${field}&lt;/code&gt;&lt;br&gt;
      );&lt;br&gt;
    }&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (&lt;br&gt;
    typeof sample.heaterCommand !==&lt;br&gt;
    "boolean"&lt;br&gt;
  ) {&lt;br&gt;
    throw new TypeError(&lt;br&gt;
      "Invalid heaterCommand"&lt;br&gt;
    );&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Additional Faults You Can Detect&lt;/p&gt;

&lt;p&gt;The system can be expanded to identify more advanced conditions.&lt;/p&gt;

&lt;p&gt;Unbalanced three-phase current&lt;/p&gt;

&lt;p&gt;For a three-phase heater, monitor all three line currents:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  currentL1: 27.3,&lt;br&gt;
  currentL2: 26.9,&lt;br&gt;
  currentL3: 13.5&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;A large difference may indicate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One failed heating branch&lt;/li&gt;
&lt;li&gt;Loose connection&lt;/li&gt;
&lt;li&gt;Damaged fuse&lt;/li&gt;
&lt;li&gt;Incorrect star or delta wiring&lt;/li&gt;
&lt;li&gt;Supply voltage imbalance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Excessive current&lt;/p&gt;

&lt;p&gt;Higher-than-normal current may indicate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Incorrect supply voltage&lt;/li&gt;
&lt;li&gt;Shorted heating element&lt;/li&gt;
&lt;li&gt;Incorrect element resistance&lt;/li&gt;
&lt;li&gt;Wiring mistake&lt;/li&gt;
&lt;li&gt;Wrong star or delta connection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Slow cooling&lt;/p&gt;

&lt;p&gt;When the heater is off, abnormal cooling behavior may reveal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unexpected external heat&lt;/li&gt;
&lt;li&gt;Incorrect sensor position&lt;/li&gt;
&lt;li&gt;Process flow problems&lt;/li&gt;
&lt;li&gt;Stuck switching device&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repeated short cycling&lt;/p&gt;

&lt;p&gt;Frequent switching may indicate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Incorrect PID tuning&lt;/li&gt;
&lt;li&gt;Small control differential&lt;/li&gt;
&lt;li&gt;Oversized heater&lt;/li&gt;
&lt;li&gt;Poor sensor location&lt;/li&gt;
&lt;li&gt;Excessive electrical noise&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Increasing heat-up time&lt;/p&gt;

&lt;p&gt;The system can compare the current heat-up cycle with previous cycles.&lt;/p&gt;

&lt;p&gt;A gradual increase in heating time may indicate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Element aging&lt;/li&gt;
&lt;li&gt;Scale formation&lt;/li&gt;
&lt;li&gt;Reduced airflow&lt;/li&gt;
&lt;li&gt;Contamination&lt;/li&gt;
&lt;li&gt;Thermal insulation damage&lt;/li&gt;
&lt;li&gt;Lower supply voltage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Safety Limitations&lt;/p&gt;

&lt;p&gt;This project is intended for monitoring and educational use.&lt;/p&gt;

&lt;p&gt;It must not directly replace:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Certified safety relays&lt;/li&gt;
&lt;li&gt;Independent overtemperature controllers&lt;/li&gt;
&lt;li&gt;Thermal fuses&lt;/li&gt;
&lt;li&gt;Pressure protection&lt;/li&gt;
&lt;li&gt;Level switches&lt;/li&gt;
&lt;li&gt;Emergency stop circuits&lt;/li&gt;
&lt;li&gt;Circuit breakers&lt;/li&gt;
&lt;li&gt;Ground fault protection&lt;/li&gt;
&lt;li&gt;Proper electrical design&lt;/li&gt;
&lt;li&gt;Qualified engineering review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A browser application may crash, lose network access, or receive delayed data.&lt;/p&gt;

&lt;p&gt;Critical heater shutdown functions should be implemented in reliable industrial hardware, such as a PLC, safety controller, or independent temperature limiter.&lt;/p&gt;

&lt;p&gt;Possible Next Improvements&lt;/p&gt;

&lt;p&gt;This application can be developed further by adding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-time MQTT data&lt;/li&gt;
&lt;li&gt;Three-phase current monitoring&lt;/li&gt;
&lt;li&gt;Alarm acknowledgment&lt;/li&gt;
&lt;li&gt;User authentication&lt;/li&gt;
&lt;li&gt;Historical event storage&lt;/li&gt;
&lt;li&gt;CSV data export&lt;/li&gt;
&lt;li&gt;PDF maintenance reports&lt;/li&gt;
&lt;li&gt;Configurable thresholds&lt;/li&gt;
&lt;li&gt;Multiple heater zones&lt;/li&gt;
&lt;li&gt;Equipment-specific profiles&lt;/li&gt;
&lt;li&gt;Notification services&lt;/li&gt;
&lt;li&gt;Predictive maintenance scoring&lt;/li&gt;
&lt;li&gt;Statistical process control&lt;/li&gt;
&lt;li&gt;Offline PWA support&lt;/li&gt;
&lt;li&gt;TypeScript type safety&lt;/li&gt;
&lt;li&gt;Automated unit tests&lt;/li&gt;
&lt;li&gt;Database integration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You could also calculate a simple heater health score:&lt;/p&gt;

&lt;p&gt;Heater health =&lt;br&gt;
100&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Current deviation penalty&lt;/li&gt;
&lt;li&gt;Heating-rate penalty&lt;/li&gt;
&lt;li&gt;Overshoot penalty&lt;/li&gt;
&lt;li&gt;Sensor stability penalty&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The score could help maintenance teams compare multiple heating zones in one dashboard.&lt;/p&gt;

&lt;p&gt;Final Thoughts&lt;/p&gt;

&lt;p&gt;JavaScript can be used for more than websites and user interfaces.&lt;/p&gt;

&lt;p&gt;With the right process data, it can also support industrial monitoring, diagnostics, maintenance planning, and engineering calculations.&lt;/p&gt;

&lt;p&gt;This rule-based anomaly detector combines electrical current, temperature, controller commands, and heating rate to identify common heater problems.&lt;/p&gt;

&lt;p&gt;Although it is not a replacement for industrial safety hardware, it can become a useful maintenance and visualization tool when connected to real equipment data.&lt;/p&gt;

&lt;p&gt;I work on the design and manufacturing of industrial electric heaters, including flanged heaters, tubular heating elements, cartridge heaters, immersion heaters, ceramic heaters, and electric fan heaters at Techno Design.&lt;/p&gt;

&lt;p&gt;More information about industrial heating systems:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://technodesignn.ir" rel="noopener noreferrer"&gt;https://technodesignn.ir&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Suggested DEV Community Tags&lt;/p&gt;

&lt;h1&gt;
  
  
  javascript
&lt;/h1&gt;

&lt;h1&gt;
  
  
  iot
&lt;/h1&gt;

&lt;h1&gt;
  
  
  webdev
&lt;/h1&gt;

&lt;h1&gt;
  
  
  engineering
&lt;/h1&gt;

&lt;p&gt;Suggested Article Description&lt;/p&gt;

&lt;p&gt;Learn how to build a JavaScript-based industrial heater anomaly detector that monitors temperature, current, heating rate, sensor faults, overheating, and relay failures.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Build an Industrial Heater Current Calculator with JavaScript</title>
      <dc:creator>ashkan hoseinpoor</dc:creator>
      <pubDate>Tue, 28 Jul 2026 20:26:10 +0000</pubDate>
      <link>https://dev.to/ashkan_hoseinpoor/build-an-industrial-heater-current-calculator-with-javascript-2k6h</link>
      <guid>https://dev.to/ashkan_hoseinpoor/build-an-industrial-heater-current-calculator-with-javascript-2k6h</guid>
      <description>&lt;p&gt;Build a Single-Phase and Three-Phase Heater Current Calculator with JavaScript&lt;/p&gt;

&lt;p&gt;Industrial electric heaters are commonly designed for either single-phase or three-phase electrical systems.&lt;/p&gt;

&lt;p&gt;Before choosing a circuit breaker, contactor, cable, or control panel, engineers need an initial estimate of the heater's operating current.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will build a responsive electrical current calculator using HTML, CSS, and JavaScript.&lt;/p&gt;

&lt;p&gt;The calculator will support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single-phase resistive heaters&lt;/li&gt;
&lt;li&gt;Three-phase resistive heaters&lt;/li&gt;
&lt;li&gt;Different supply voltages&lt;/li&gt;
&lt;li&gt;Power values in watts or kilowatts&lt;/li&gt;
&lt;li&gt;Custom power factor values&lt;/li&gt;
&lt;li&gt;Input validation&lt;/li&gt;
&lt;li&gt;Automatic resistance calculation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;«This calculator provides an initial engineering estimate. Final electrical protection and cable sizing must follow applicable standards and actual installation conditions.»&lt;/p&gt;

&lt;p&gt;Electrical Formulas&lt;/p&gt;

&lt;p&gt;For a single-phase electrical load:&lt;/p&gt;

&lt;p&gt;P = V × I × PF&lt;/p&gt;

&lt;p&gt;Therefore:&lt;/p&gt;

&lt;p&gt;I = P ÷ (V × PF)&lt;/p&gt;

&lt;p&gt;For a balanced three-phase electrical load:&lt;/p&gt;

&lt;p&gt;P = √3 × V × I × PF&lt;/p&gt;

&lt;p&gt;Therefore:&lt;/p&gt;

&lt;p&gt;I = P ÷ (√3 × V × PF)&lt;/p&gt;

&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"P" is active power in watts&lt;/li&gt;
&lt;li&gt;"V" is supply voltage in volts&lt;/li&gt;
&lt;li&gt;"I" is current in amperes&lt;/li&gt;
&lt;li&gt;"PF" is the power factor&lt;/li&gt;
&lt;li&gt;"√3" is approximately "1.732"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Industrial resistance heaters are almost purely resistive loads, so their power factor is typically close to "1".&lt;/p&gt;

&lt;p&gt;What We Are Building&lt;/p&gt;

&lt;p&gt;The user will select:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Electrical phase&lt;/li&gt;
&lt;li&gt;Heater power&lt;/li&gt;
&lt;li&gt;Power unit&lt;/li&gt;
&lt;li&gt;Supply voltage&lt;/li&gt;
&lt;li&gt;Power factor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The calculator will display:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Estimated operating current&lt;/li&gt;
&lt;li&gt;Equivalent heater resistance&lt;/li&gt;
&lt;li&gt;Applied calculation formula&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Project Structure&lt;/p&gt;

&lt;p&gt;Create the following files:&lt;/p&gt;

&lt;p&gt;heater-current-calculator/&lt;br&gt;
├── index.html&lt;br&gt;
├── style.css&lt;br&gt;
└── script.js&lt;/p&gt;

&lt;p&gt;Step 1: Create the HTML&lt;/p&gt;

&lt;p&gt;Add the following code to "index.html":&lt;/p&gt;

&lt;p&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&amp;lt;meta&lt;br&gt;
    name="viewport"&lt;br&gt;
    content="width=device-width, initial-scale=1.0"&lt;/p&gt;

&lt;blockquote&gt;
&lt;/blockquote&gt;

&lt;p&gt;&amp;lt;meta&lt;br&gt;
    name="description"&lt;br&gt;
    content="Calculate the operating current of single-phase and three-phase industrial electric heaters."&lt;/p&gt;

&lt;blockquote&gt;
&lt;/blockquote&gt;


Industrial Heater Current Calculator




&lt;br&gt;
    &lt;br&gt;
      &lt;span&gt;Electrical Engineering Tool&lt;/span&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  &amp;lt;h1&amp;gt;Industrial Heater Current Calculator&amp;lt;/h1&amp;gt;

  &amp;lt;p&amp;gt;
    Estimate the operating current and electrical resistance
    of a single-phase or three-phase electric heater.
  &amp;lt;/p&amp;gt;
&amp;lt;/header&amp;gt;

&amp;lt;form id="calculator-form"&amp;gt;
  &amp;lt;div class="field"&amp;gt;
    &amp;lt;label for="phase"&amp;gt;Electrical system&amp;lt;/label&amp;gt;

    &amp;lt;select id="phase"&amp;gt;
      &amp;lt;option value="single"&amp;gt;Single phase&amp;lt;/option&amp;gt;
      &amp;lt;option value="three"&amp;gt;Three phase&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div class="power-row"&amp;gt;
    &amp;lt;div class="field"&amp;gt;
      &amp;lt;label for="power"&amp;gt;Heater power&amp;lt;/label&amp;gt;

      &amp;lt;input
        type="number"
        id="power"
        min="0.01"
        step="0.01"
        placeholder="Example: 18"
        required
      &amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;div class="field unit-field"&amp;gt;
      &amp;lt;label for="power-unit"&amp;gt;Unit&amp;lt;/label&amp;gt;

      &amp;lt;select id="power-unit"&amp;gt;
        &amp;lt;option value="kw"&amp;gt;kW&amp;lt;/option&amp;gt;
        &amp;lt;option value="w"&amp;gt;W&amp;lt;/option&amp;gt;
      &amp;lt;/select&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div class="field"&amp;gt;
    &amp;lt;label for="voltage"&amp;gt;Supply voltage&amp;lt;/label&amp;gt;

    &amp;lt;input
      type="number"
      id="voltage"
      min="1"
      step="1"
      value="220"
      required
    &amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div class="voltage-buttons"&amp;gt;
    &amp;lt;button type="button" data-voltage="220"&amp;gt;
      220 V
    &amp;lt;/button&amp;gt;

    &amp;lt;button type="button" data-voltage="230"&amp;gt;
      230 V
    &amp;lt;/button&amp;gt;

    &amp;lt;button type="button" data-voltage="380"&amp;gt;
      380 V
    &amp;lt;/button&amp;gt;

    &amp;lt;button type="button" data-voltage="400"&amp;gt;
      400 V
    &amp;lt;/button&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div class="field"&amp;gt;
    &amp;lt;label for="power-factor"&amp;gt;Power factor&amp;lt;/label&amp;gt;

    &amp;lt;input
      type="number"
      id="power-factor"
      min="0.01"
      max="1"
      step="0.01"
      value="1"
      required
    &amp;gt;

    &amp;lt;small&amp;gt;
      For a standard resistance heater, use a value close to 1.
    &amp;lt;/small&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;button class="calculate-button" type="submit"&amp;gt;
    Calculate Current
  &amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;

&amp;lt;section
  id="result"
  class="result"
  aria-live="polite"
&amp;gt;&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;





&lt;p&gt;Step 2: Style the Calculator&lt;/p&gt;

&lt;p&gt;Add the following code to "style.css":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;{
box-sizing: border-box;
}&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;:root {&lt;br&gt;
  font-family: Arial, Helvetica, sans-serif;&lt;br&gt;
  color: #1f2937;&lt;br&gt;
  background: #eef1f5;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;body {&lt;br&gt;
  min-height: 100vh;&lt;br&gt;
  margin: 0;&lt;br&gt;
  padding: 24px;&lt;br&gt;
  display: flex;&lt;br&gt;
  align-items: center;&lt;br&gt;
  justify-content: center;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.calculator-card {&lt;br&gt;
  width: 100%;&lt;br&gt;
  max-width: 620px;&lt;br&gt;
  padding: 36px;&lt;br&gt;
  background: #ffffff;&lt;br&gt;
  border-radius: 20px;&lt;br&gt;
  box-shadow: 0 16px 45px rgba(15, 23, 42, 0.1);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;header {&lt;br&gt;
  margin-bottom: 28px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.label {&lt;br&gt;
  display: inline-block;&lt;br&gt;
  margin-bottom: 10px;&lt;br&gt;
  color: #d97706;&lt;br&gt;
  font-size: 13px;&lt;br&gt;
  font-weight: 700;&lt;br&gt;
  letter-spacing: 0.05em;&lt;br&gt;
  text-transform: uppercase;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;h1 {&lt;br&gt;
  margin: 0 0 12px;&lt;br&gt;
  font-size: clamp(28px, 5vw, 40px);&lt;br&gt;
  line-height: 1.15;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;header p {&lt;br&gt;
  margin: 0;&lt;br&gt;
  color: #64748b;&lt;br&gt;
  line-height: 1.7;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.field {&lt;br&gt;
  margin-bottom: 20px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.power-row {&lt;br&gt;
  display: grid;&lt;br&gt;
  grid-template-columns: 1fr 120px;&lt;br&gt;
  gap: 14px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;label {&lt;br&gt;
  display: block;&lt;br&gt;
  margin-bottom: 8px;&lt;br&gt;
  font-weight: 700;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;input,&lt;br&gt;
select {&lt;br&gt;
  width: 100%;&lt;br&gt;
  min-height: 50px;&lt;br&gt;
  padding: 10px 14px;&lt;br&gt;
  border: 1px solid #cbd5e1;&lt;br&gt;
  border-radius: 10px;&lt;br&gt;
  background: #ffffff;&lt;br&gt;
  color: #1f2937;&lt;br&gt;
  font-size: 16px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;input:focus,&lt;br&gt;
select:focus {&lt;br&gt;
  outline: 3px solid rgba(245, 158, 11, 0.2);&lt;br&gt;
  border-color: #f59e0b;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;small {&lt;br&gt;
  display: block;&lt;br&gt;
  margin-top: 8px;&lt;br&gt;
  color: #64748b;&lt;br&gt;
  line-height: 1.5;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.voltage-buttons {&lt;br&gt;
  display: grid;&lt;br&gt;
  grid-template-columns: repeat(4, 1fr);&lt;br&gt;
  gap: 8px;&lt;br&gt;
  margin-top: -8px;&lt;br&gt;
  margin-bottom: 22px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.voltage-buttons button {&lt;br&gt;
  min-height: 40px;&lt;br&gt;
  border: 1px solid #dbe2ea;&lt;br&gt;
  border-radius: 8px;&lt;br&gt;
  background: #f8fafc;&lt;br&gt;
  color: #334155;&lt;br&gt;
  cursor: pointer;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.voltage-buttons button:hover {&lt;br&gt;
  border-color: #f59e0b;&lt;br&gt;
  background: #fff7ed;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.calculate-button {&lt;br&gt;
  width: 100%;&lt;br&gt;
  min-height: 54px;&lt;br&gt;
  padding: 12px 20px;&lt;br&gt;
  border: 0;&lt;br&gt;
  border-radius: 10px;&lt;br&gt;
  background: #f59e0b;&lt;br&gt;
  color: #ffffff;&lt;br&gt;
  font-size: 17px;&lt;br&gt;
  font-weight: 700;&lt;br&gt;
  cursor: pointer;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.calculate-button:hover {&lt;br&gt;
  background: #d97706;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.result {&lt;br&gt;
  display: none;&lt;br&gt;
  margin-top: 26px;&lt;br&gt;
  padding: 22px;&lt;br&gt;
  border: 1px solid #fed7aa;&lt;br&gt;
  border-radius: 12px;&lt;br&gt;
  background: #fff7ed;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.result.visible {&lt;br&gt;
  display: block;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.result.error {&lt;br&gt;
  border-color: #fecaca;&lt;br&gt;
  background: #fef2f2;&lt;br&gt;
  color: #b91c1c;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.result h2 {&lt;br&gt;
  margin-top: 0;&lt;br&gt;
  font-size: 20px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.result-grid {&lt;br&gt;
  display: grid;&lt;br&gt;
  grid-template-columns: repeat(2, 1fr);&lt;br&gt;
  gap: 12px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.result-item {&lt;br&gt;
  padding: 14px;&lt;br&gt;
  border-radius: 10px;&lt;br&gt;
  background: #ffffff;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.result-item span {&lt;br&gt;
  display: block;&lt;br&gt;
  margin-bottom: 6px;&lt;br&gt;
  color: #64748b;&lt;br&gt;
  font-size: 14px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.result-item strong {&lt;br&gt;
  font-size: 22px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.formula {&lt;br&gt;
  margin-bottom: 0;&lt;br&gt;
  padding-top: 14px;&lt;br&gt;
  border-top: 1px solid #fed7aa;&lt;br&gt;
  font-family: monospace;&lt;br&gt;
  line-height: 1.7;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/media"&gt;@media&lt;/a&gt; (max-width: 520px) {&lt;br&gt;
  body {&lt;br&gt;
    padding: 14px;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.calculator-card {&lt;br&gt;
    padding: 24px 18px;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.power-row {&lt;br&gt;
    grid-template-columns: 1fr 90px;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.voltage-buttons {&lt;br&gt;
    grid-template-columns: repeat(2, 1fr);&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;.result-grid {&lt;br&gt;
    grid-template-columns: 1fr;&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The interface is responsive, so it can be used on both desktop and mobile devices.&lt;/p&gt;

&lt;p&gt;Step 3: Add the JavaScript Logic&lt;/p&gt;

&lt;p&gt;Add the following code to "script.js":&lt;/p&gt;

&lt;p&gt;const form = document.getElementById("calculator-form");&lt;br&gt;
const phaseInput = document.getElementById("phase");&lt;br&gt;
const powerInput = document.getElementById("power");&lt;br&gt;
const powerUnitInput = document.getElementById("power-unit");&lt;br&gt;
const voltageInput = document.getElementById("voltage");&lt;br&gt;
const powerFactorInput = document.getElementById("power-factor");&lt;br&gt;
const result = document.getElementById("result");&lt;/p&gt;

&lt;p&gt;const voltageButtons = document.querySelectorAll(&lt;br&gt;
  "[data-voltage]"&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;const SQRT_THREE = Math.sqrt(3);&lt;/p&gt;

&lt;p&gt;voltageButtons.forEach((button) =&amp;gt; {&lt;br&gt;
  button.addEventListener("click", () =&amp;gt; {&lt;br&gt;
    voltageInput.value = button.dataset.voltage;&lt;br&gt;
  });&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;phaseInput.addEventListener("change", () =&amp;gt; {&lt;br&gt;
  if (phaseInput.value === "single") {&lt;br&gt;
    voltageInput.value = 220;&lt;br&gt;
  } else {&lt;br&gt;
    voltageInput.value = 380;&lt;br&gt;
  }&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;form.addEventListener("submit", (event) =&amp;gt; {&lt;br&gt;
  event.preventDefault();&lt;/p&gt;

&lt;p&gt;const phase = phaseInput.value;&lt;br&gt;
  const enteredPower = Number(powerInput.value);&lt;br&gt;
  const powerUnit = powerUnitInput.value;&lt;br&gt;
  const voltage = Number(voltageInput.value);&lt;br&gt;
  const powerFactor = Number(powerFactorInput.value);&lt;/p&gt;

&lt;p&gt;clearResult();&lt;/p&gt;

&lt;p&gt;if (&lt;br&gt;
    !Number.isFinite(enteredPower) ||&lt;br&gt;
    !Number.isFinite(voltage) ||&lt;br&gt;
    !Number.isFinite(powerFactor)&lt;br&gt;
  ) {&lt;br&gt;
    showError("Please enter valid numeric values.");&lt;br&gt;
    return;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (enteredPower &amp;lt;= 0) {&lt;br&gt;
    showError("Heater power must be greater than zero.");&lt;br&gt;
    return;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (voltage &amp;lt;= 0) {&lt;br&gt;
    showError("Supply voltage must be greater than zero.");&lt;br&gt;
    return;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (powerFactor &amp;lt;= 0 || powerFactor &amp;gt; 1) {&lt;br&gt;
    showError("Power factor must be between 0 and 1.");&lt;br&gt;
    return;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;const powerInWatts =&lt;br&gt;
    powerUnit === "kw"&lt;br&gt;
      ? enteredPower * 1000&lt;br&gt;
      : enteredPower;&lt;/p&gt;

&lt;p&gt;const current =&lt;br&gt;
    phase === "single"&lt;br&gt;
      ? calculateSinglePhaseCurrent(&lt;br&gt;
          powerInWatts,&lt;br&gt;
          voltage,&lt;br&gt;
          powerFactor&lt;br&gt;
        )&lt;br&gt;
      : calculateThreePhaseCurrent(&lt;br&gt;
          powerInWatts,&lt;br&gt;
          voltage,&lt;br&gt;
          powerFactor&lt;br&gt;
        );&lt;/p&gt;

&lt;p&gt;const resistance =&lt;br&gt;
    phase === "single"&lt;br&gt;
      ? calculateSinglePhaseResistance(&lt;br&gt;
          voltage,&lt;br&gt;
          powerInWatts&lt;br&gt;
        )&lt;br&gt;
      : calculateThreePhaseEquivalentResistance(&lt;br&gt;
          voltage,&lt;br&gt;
          powerInWatts&lt;br&gt;
        );&lt;/p&gt;

&lt;p&gt;showResult({&lt;br&gt;
    phase,&lt;br&gt;
    powerInWatts,&lt;br&gt;
    voltage,&lt;br&gt;
    powerFactor,&lt;br&gt;
    current,&lt;br&gt;
    resistance&lt;br&gt;
  });&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;function calculateSinglePhaseCurrent(&lt;br&gt;
  power,&lt;br&gt;
  voltage,&lt;br&gt;
  powerFactor&lt;br&gt;
) {&lt;br&gt;
  return power / (voltage * powerFactor);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;function calculateThreePhaseCurrent(&lt;br&gt;
  power,&lt;br&gt;
  voltage,&lt;br&gt;
  powerFactor&lt;br&gt;
) {&lt;br&gt;
  return power / (&lt;br&gt;
    SQRT_THREE *&lt;br&gt;
    voltage *&lt;br&gt;
    powerFactor&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;function calculateSinglePhaseResistance(&lt;br&gt;
  voltage,&lt;br&gt;
  power&lt;br&gt;
) {&lt;br&gt;
  return Math.pow(voltage, 2) / power;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;function calculateThreePhaseEquivalentResistance(&lt;br&gt;
  voltage,&lt;br&gt;
  power&lt;br&gt;
) {&lt;br&gt;
  return Math.pow(voltage, 2) / power;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;function showResult({&lt;br&gt;
  phase,&lt;br&gt;
  powerInWatts,&lt;br&gt;
  voltage,&lt;br&gt;
  powerFactor,&lt;br&gt;
  current,&lt;br&gt;
  resistance&lt;br&gt;
}) {&lt;br&gt;
  const phaseLabel =&lt;br&gt;
    phase === "single"&lt;br&gt;
      ? "Single phase"&lt;br&gt;
      : "Three phase";&lt;/p&gt;

&lt;p&gt;const formula =&lt;br&gt;
    phase === "single"&lt;br&gt;
      ? "I = P ÷ (V × PF)"&lt;br&gt;
      : "I = P ÷ (√3 × V × PF)";&lt;/p&gt;

&lt;p&gt;result.innerHTML = `&lt;br&gt;
    &lt;/p&gt;
&lt;h2&gt;${phaseLabel} calculation&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="result-grid"&amp;gt;
  &amp;lt;div class="result-item"&amp;gt;
    &amp;lt;span&amp;gt;Operating current&amp;lt;/span&amp;gt;
    &amp;lt;strong&amp;gt;${current.toFixed(2)} A&amp;lt;/strong&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div class="result-item"&amp;gt;
    &amp;lt;span&amp;gt;Equivalent resistance&amp;lt;/span&amp;gt;
    &amp;lt;strong&amp;gt;${resistance.toFixed(2)} Ω&amp;lt;/strong&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div class="result-item"&amp;gt;
    &amp;lt;span&amp;gt;Heater power&amp;lt;/span&amp;gt;
    &amp;lt;strong&amp;gt;
      ${(powerInWatts / 1000).toFixed(2)} kW
    &amp;lt;/strong&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div class="result-item"&amp;gt;
    &amp;lt;span&amp;gt;Supply voltage&amp;lt;/span&amp;gt;
    &amp;lt;strong&amp;gt;${voltage.toFixed(0)} V&amp;lt;/strong&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;p class="formula"&amp;gt;
  ${formula}&amp;lt;br&amp;gt;
  Power factor: ${powerFactor.toFixed(2)}
&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;`;&lt;/p&gt;

&lt;p&gt;result.classList.add("visible");&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;function showError(message) {&lt;br&gt;
  result.textContent = message;&lt;br&gt;
  result.classList.add("visible", "error");&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;function clearResult() {&lt;br&gt;
  result.className = "result";&lt;br&gt;
  result.innerHTML = "";&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;How the Calculation Works&lt;/p&gt;

&lt;p&gt;The calculator first converts kilowatts into watts:&lt;/p&gt;

&lt;p&gt;const powerInWatts =&lt;br&gt;
  powerUnit === "kw"&lt;br&gt;
    ? enteredPower * 1000&lt;br&gt;
    : enteredPower;&lt;/p&gt;

&lt;p&gt;It then checks whether the user selected a single-phase or three-phase system.&lt;/p&gt;

&lt;p&gt;For single-phase loads:&lt;/p&gt;

&lt;p&gt;return power / (voltage * powerFactor);&lt;/p&gt;

&lt;p&gt;For three-phase loads:&lt;/p&gt;

&lt;p&gt;return power / (&lt;br&gt;
  Math.sqrt(3) *&lt;br&gt;
  voltage *&lt;br&gt;
  powerFactor&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;The application also performs validation to prevent calculations with invalid values.&lt;/p&gt;

&lt;p&gt;Example 1: Single-Phase Heater&lt;/p&gt;

&lt;p&gt;Suppose we have the following heater:&lt;/p&gt;

&lt;p&gt;Power: 3,000 W&lt;br&gt;
Voltage: 220 V&lt;br&gt;
Power factor: 1&lt;/p&gt;

&lt;p&gt;The current is:&lt;/p&gt;

&lt;p&gt;I = 3000 ÷ 220&lt;br&gt;
I = 13.64 A&lt;/p&gt;

&lt;p&gt;Therefore, the heater has an estimated operating current of approximately:&lt;/p&gt;

&lt;p&gt;13.64 A&lt;/p&gt;

&lt;p&gt;The calculated value is the heater operating current, not necessarily the final circuit breaker rating.&lt;/p&gt;

&lt;p&gt;Example 2: Three-Phase Heater&lt;/p&gt;

&lt;p&gt;Consider an industrial three-phase heater with the following specifications:&lt;/p&gt;

&lt;p&gt;Power: 18 kW&lt;br&gt;
Voltage: 380 V&lt;br&gt;
Power factor: 1&lt;/p&gt;

&lt;p&gt;The current is:&lt;/p&gt;

&lt;p&gt;I = 18000 ÷ (1.732 × 380)&lt;br&gt;
I = 27.35 A&lt;/p&gt;

&lt;p&gt;Each line conductor carries approximately:&lt;/p&gt;

&lt;p&gt;27.35 A&lt;/p&gt;

&lt;p&gt;This is one reason why three-phase power is commonly used for high-power industrial heaters.&lt;/p&gt;

&lt;p&gt;Why Three-Phase Power Is Used&lt;/p&gt;

&lt;p&gt;Three-phase electrical systems offer several advantages for industrial heating applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lower line current for the same total power&lt;/li&gt;
&lt;li&gt;Better load distribution&lt;/li&gt;
&lt;li&gt;Smaller conductor requirements in many installations&lt;/li&gt;
&lt;li&gt;Easier control of high-power heating systems&lt;/li&gt;
&lt;li&gt;Better compatibility with industrial electrical infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, an 18 kW heater connected to a 220 V single-phase supply would draw approximately:&lt;/p&gt;

&lt;p&gt;18000 ÷ 220 = 81.82 A&lt;/p&gt;

&lt;p&gt;The same power connected to a 380 V three-phase supply draws approximately:&lt;/p&gt;

&lt;p&gt;18000 ÷ (1.732 × 380) = 27.35 A&lt;/p&gt;

&lt;p&gt;The comparison demonstrates why high-capacity industrial heaters are frequently designed for three-phase operation.&lt;/p&gt;

&lt;p&gt;Star and Delta Connections&lt;/p&gt;

&lt;p&gt;Three-phase heating elements may be connected in star or delta configurations.&lt;/p&gt;

&lt;p&gt;Star Connection&lt;/p&gt;

&lt;p&gt;In a star connection:&lt;/p&gt;

&lt;p&gt;Vphase = Vline ÷ √3&lt;/p&gt;

&lt;p&gt;At a line voltage of 380 V:&lt;/p&gt;

&lt;p&gt;Vphase = 380 ÷ 1.732&lt;br&gt;
Vphase ≈ 219.4 V&lt;/p&gt;

&lt;p&gt;This means each heating element receives approximately 220 V.&lt;/p&gt;

&lt;p&gt;A star connection is therefore useful when the individual heater elements are rated for approximately 220 V but the available three-phase line voltage is 380 V.&lt;/p&gt;

&lt;p&gt;Delta Connection&lt;/p&gt;

&lt;p&gt;In a delta connection:&lt;/p&gt;

&lt;p&gt;Vphase = Vline&lt;/p&gt;

&lt;p&gt;With a 380 V supply, each heating element receives the full 380 V.&lt;/p&gt;

&lt;p&gt;The selected connection must match the voltage and resistance rating of each heating element.&lt;/p&gt;

&lt;p&gt;Connecting a heating element to the wrong voltage can dramatically increase its power and may cause rapid failure.&lt;/p&gt;

&lt;p&gt;Power and Resistance Relationship&lt;/p&gt;

&lt;p&gt;For a resistive heater:&lt;/p&gt;

&lt;p&gt;P = V² ÷ R&lt;/p&gt;

&lt;p&gt;Therefore:&lt;/p&gt;

&lt;p&gt;R = V² ÷ P&lt;/p&gt;

&lt;p&gt;For a 3 kW single-phase heater operating at 220 V:&lt;/p&gt;

&lt;p&gt;R = 220² ÷ 3000&lt;br&gt;
R = 16.13 Ω&lt;/p&gt;

&lt;p&gt;The approximate heater resistance is:&lt;/p&gt;

&lt;p&gt;16.13 Ω&lt;/p&gt;

&lt;p&gt;Actual measured resistance may differ slightly due to manufacturing tolerance and temperature.&lt;/p&gt;

&lt;p&gt;The electrical resistance of many heating alloys changes as the element temperature increases.&lt;/p&gt;

&lt;p&gt;Important Safety Considerations&lt;/p&gt;

&lt;p&gt;A current calculator is only the first step in designing an industrial heater circuit.&lt;/p&gt;

&lt;p&gt;A complete design may also require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cable current-carrying capacity&lt;/li&gt;
&lt;li&gt;Ambient-temperature correction&lt;/li&gt;
&lt;li&gt;Cable installation method&lt;/li&gt;
&lt;li&gt;Circuit length and voltage drop&lt;/li&gt;
&lt;li&gt;Circuit breaker characteristics&lt;/li&gt;
&lt;li&gt;Contactor utilization category&lt;/li&gt;
&lt;li&gt;Solid-state relay cooling&lt;/li&gt;
&lt;li&gt;Short-circuit protection&lt;/li&gt;
&lt;li&gt;Earth leakage protection&lt;/li&gt;
&lt;li&gt;Overtemperature protection&lt;/li&gt;
&lt;li&gt;Phase failure protection&lt;/li&gt;
&lt;li&gt;Grounding and bonding&lt;/li&gt;
&lt;li&gt;Control-panel ventilation&lt;/li&gt;
&lt;li&gt;Local electrical regulations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The selected circuit breaker should not be based only on the displayed operating current.&lt;/p&gt;

&lt;p&gt;The complete installation conditions must be reviewed by a qualified electrical professional.&lt;/p&gt;

&lt;p&gt;Possible Improvements&lt;/p&gt;

&lt;p&gt;This calculator can be expanded with additional features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Star and delta connection selection&lt;/li&gt;
&lt;li&gt;Current per heating element&lt;/li&gt;
&lt;li&gt;Resistance per phase&lt;/li&gt;
&lt;li&gt;Circuit breaker suggestions&lt;/li&gt;
&lt;li&gt;Cable cross-section estimates&lt;/li&gt;
&lt;li&gt;Contactor current selection&lt;/li&gt;
&lt;li&gt;Solid-state relay sizing&lt;/li&gt;
&lt;li&gt;Energy consumption calculations&lt;/li&gt;
&lt;li&gt;Estimated operating costs&lt;/li&gt;
&lt;li&gt;Celsius and Fahrenheit support&lt;/li&gt;
&lt;li&gt;Exporting results as PDF&lt;/li&gt;
&lt;li&gt;Saving calculations in local storage&lt;/li&gt;
&lt;li&gt;Creating a printable engineering report&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You could also rebuild the application using React, Vue, TypeScript, or another frontend framework.&lt;/p&gt;

&lt;p&gt;Final Thoughts&lt;/p&gt;

&lt;p&gt;This project shows how basic electrical engineering formulas can be transformed into a practical browser-based tool.&lt;/p&gt;

&lt;p&gt;JavaScript is not limited to traditional web applications. It can also help engineers, technicians, manufacturers, and customers perform fast initial calculations.&lt;/p&gt;

&lt;p&gt;I work on the design and manufacturing of industrial electric heaters, including flanged heaters, tubular heating elements, cartridge heaters, immersion heaters, and electric fan heaters at Techno Design.&lt;/p&gt;

&lt;p&gt;More information about industrial heating systems is available at:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://technodesignn.ir" rel="noopener noreferrer"&gt;https://technodesignn.ir&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Suggested DEV Community tags:&lt;/p&gt;

&lt;h1&gt;
  
  
  javascript
&lt;/h1&gt;

&lt;h1&gt;
  
  
  webdev
&lt;/h1&gt;

&lt;h1&gt;
  
  
  engineering
&lt;/h1&gt;

&lt;h1&gt;
  
  
  beginners
&lt;/h1&gt;

&lt;p&gt;Suggested article description:&lt;/p&gt;

&lt;p&gt;Build a responsive JavaScript calculator for estimating the operating current and resistance of single-phase and three-phase industrial electric heaters.&lt;/p&gt;

</description>
      <category>coding</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Industrial Heater Sizing Calculator with JavaScript</title>
      <dc:creator>ashkan hoseinpoor</dc:creator>
      <pubDate>Tue, 28 Jul 2026 20:21:59 +0000</pubDate>
      <link>https://dev.to/ashkan_hoseinpoor/industrial-heater-sizing-calculator-with-javascript-e3o</link>
      <guid>https://dev.to/ashkan_hoseinpoor/industrial-heater-sizing-calculator-with-javascript-e3o</guid>
      <description>&lt;p&gt;How to Build an Industrial Electric Heater Sizing Calculator with JavaScript&lt;/p&gt;

&lt;p&gt;Selecting the correct power for an industrial electric heater is not always straightforward.&lt;/p&gt;

&lt;p&gt;If the heater is too small, the system may take too long to reach the desired temperature. If it is too powerful, it may increase energy consumption, create temperature-control problems, or damage the equipment.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will build a simple industrial heater sizing calculator using HTML, CSS, and JavaScript.&lt;/p&gt;

&lt;p&gt;The calculator estimates the electrical power required to heat water from an initial temperature to a target temperature within a specified time.&lt;/p&gt;

&lt;p&gt;What We Are Building&lt;/p&gt;

&lt;p&gt;The user will enter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Water volume in liters&lt;/li&gt;
&lt;li&gt;Initial temperature&lt;/li&gt;
&lt;li&gt;Target temperature&lt;/li&gt;
&lt;li&gt;Heating time in minutes&lt;/li&gt;
&lt;li&gt;Safety factor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The calculator will return the estimated heater power in kilowatts.&lt;/p&gt;

&lt;p&gt;The Basic Heat Calculation Formula&lt;/p&gt;

&lt;p&gt;The energy required to heat a material can be calculated using:&lt;/p&gt;

&lt;p&gt;Q = m × c × ΔT&lt;/p&gt;

&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Q" is the required heat energy&lt;/li&gt;
&lt;li&gt;"m" is the mass of the material&lt;/li&gt;
&lt;li&gt;"c" is the specific heat capacity&lt;/li&gt;
&lt;li&gt;"ΔT" is the temperature difference&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For water:&lt;/p&gt;

&lt;p&gt;c = 4.186 kJ/kg°C&lt;/p&gt;

&lt;p&gt;Because one liter of water has a mass of approximately one kilogram, the water volume in liters can be used as an approximate mass value in kilograms.&lt;/p&gt;

&lt;p&gt;To calculate the required power:&lt;/p&gt;

&lt;p&gt;Power = Energy / Time&lt;/p&gt;

&lt;p&gt;The final formula becomes:&lt;/p&gt;

&lt;p&gt;Power in kW =&lt;br&gt;
Mass × Specific Heat × Temperature Difference&lt;br&gt;
÷ Heating Time in Seconds&lt;/p&gt;

&lt;p&gt;A safety factor is usually added to compensate for heat losses through the tank, pipes, surrounding air, and other parts of the system.&lt;/p&gt;

&lt;p&gt;Project Structure&lt;/p&gt;

&lt;p&gt;Create three files:&lt;/p&gt;

&lt;p&gt;heater-calculator/&lt;br&gt;
├── index.html&lt;br&gt;
├── style.css&lt;br&gt;
└── script.js&lt;/p&gt;

&lt;p&gt;Step 1: Create the HTML&lt;/p&gt;

&lt;p&gt;Add the following code to "index.html":&lt;/p&gt;

&lt;p&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;br&gt;
  &amp;lt;meta&lt;br&gt;
    name="viewport"&lt;br&gt;
    content="width=device-width, initial-scale=1.0"&lt;/p&gt;

&lt;blockquote&gt;
&lt;/blockquote&gt;


Industrial Heater Sizing Calculator




&lt;br&gt;
    &lt;h1&gt;Industrial Heater Sizing Calculator&lt;/h1&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;p class="description"&amp;gt;
  Estimate the electrical power required to heat water
  within a specified period.
&amp;lt;/p&amp;gt;

&amp;lt;form id="heater-form"&amp;gt;
  &amp;lt;div class="form-group"&amp;gt;
    &amp;lt;label for="volume"&amp;gt;Water volume in liters&amp;lt;/label&amp;gt;

    &amp;lt;input
      type="number"
      id="volume"
      min="0.1"
      step="0.1"
      required
    &amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div class="form-group"&amp;gt;
    &amp;lt;label for="initial-temperature"&amp;gt;
      Initial temperature in °C
    &amp;lt;/label&amp;gt;

    &amp;lt;input
      type="number"
      id="initial-temperature"
      step="0.1"
      required
    &amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div class="form-group"&amp;gt;
    &amp;lt;label for="target-temperature"&amp;gt;
      Target temperature in °C
    &amp;lt;/label&amp;gt;

    &amp;lt;input
      type="number"
      id="target-temperature"
      step="0.1"
      required
    &amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div class="form-group"&amp;gt;
    &amp;lt;label for="heating-time"&amp;gt;
      Heating time in minutes
    &amp;lt;/label&amp;gt;

    &amp;lt;input
      type="number"
      id="heating-time"
      min="1"
      step="1"
      required
    &amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div class="form-group"&amp;gt;
    &amp;lt;label for="safety-factor"&amp;gt;
      Safety factor
    &amp;lt;/label&amp;gt;

    &amp;lt;select id="safety-factor"&amp;gt;
      &amp;lt;option value="1"&amp;gt;No safety factor&amp;lt;/option&amp;gt;
      &amp;lt;option value="1.1"&amp;gt;10%&amp;lt;/option&amp;gt;
      &amp;lt;option value="1.15" selected&amp;gt;15%&amp;lt;/option&amp;gt;
      &amp;lt;option value="1.2"&amp;gt;20%&amp;lt;/option&amp;gt;
      &amp;lt;option value="1.25"&amp;gt;25%&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;button type="submit"&amp;gt;
    Calculate Heater Power
  &amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;

&amp;lt;section
  id="result"
  class="result"
  aria-live="polite"
&amp;gt;&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;





&lt;p&gt;The form uses numeric inputs so users can enter the system conditions.&lt;/p&gt;

&lt;p&gt;The "aria-live" attribute helps screen readers announce the calculated result when it changes.&lt;/p&gt;

&lt;p&gt;Step 2: Add the CSS&lt;/p&gt;

&lt;p&gt;Add the following styles to "style.css":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;{
box-sizing: border-box;
}&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;body {&lt;br&gt;
  margin: 0;&lt;br&gt;
  min-height: 100vh;&lt;br&gt;
  display: flex;&lt;br&gt;
  align-items: center;&lt;br&gt;
  justify-content: center;&lt;br&gt;
  padding: 24px;&lt;br&gt;
  font-family: Arial, sans-serif;&lt;br&gt;
  background: #f1f3f5;&lt;br&gt;
  color: #212529;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.calculator {&lt;br&gt;
  width: 100%;&lt;br&gt;
  max-width: 560px;&lt;br&gt;
  padding: 32px;&lt;br&gt;
  background: #ffffff;&lt;br&gt;
  border-radius: 16px;&lt;br&gt;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;h1 {&lt;br&gt;
  margin-top: 0;&lt;br&gt;
  margin-bottom: 12px;&lt;br&gt;
  font-size: 30px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.description {&lt;br&gt;
  margin-bottom: 28px;&lt;br&gt;
  color: #5f6368;&lt;br&gt;
  line-height: 1.7;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.form-group {&lt;br&gt;
  margin-bottom: 18px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;label {&lt;br&gt;
  display: block;&lt;br&gt;
  margin-bottom: 8px;&lt;br&gt;
  font-weight: 700;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;input,&lt;br&gt;
select,&lt;br&gt;
button {&lt;br&gt;
  width: 100%;&lt;br&gt;
  min-height: 48px;&lt;br&gt;
  border-radius: 8px;&lt;br&gt;
  font-size: 16px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;input,&lt;br&gt;
select {&lt;br&gt;
  padding: 10px 12px;&lt;br&gt;
  border: 1px solid #ced4da;&lt;br&gt;
  background: #ffffff;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;input:focus,&lt;br&gt;
select:focus {&lt;br&gt;
  outline: 2px solid #ff8a00;&lt;br&gt;
  border-color: transparent;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;button {&lt;br&gt;
  margin-top: 8px;&lt;br&gt;
  padding: 12px 18px;&lt;br&gt;
  border: 0;&lt;br&gt;
  background: #ff8a00;&lt;br&gt;
  color: #ffffff;&lt;br&gt;
  font-weight: 700;&lt;br&gt;
  cursor: pointer;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;button:hover {&lt;br&gt;
  opacity: 0.9;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.result {&lt;br&gt;
  display: none;&lt;br&gt;
  margin-top: 24px;&lt;br&gt;
  padding: 20px;&lt;br&gt;
  border-radius: 10px;&lt;br&gt;
  background: #fff4e6;&lt;br&gt;
  line-height: 1.7;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.result.visible {&lt;br&gt;
  display: block;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.result.error {&lt;br&gt;
  background: #ffe3e3;&lt;br&gt;
  color: #c92a2a;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This layout is responsive and works well on both desktop and mobile screens.&lt;/p&gt;

&lt;p&gt;Step 3: Write the JavaScript Calculation&lt;/p&gt;

&lt;p&gt;Add the following code to "script.js":&lt;/p&gt;

&lt;p&gt;const form = document.getElementById("heater-form");&lt;br&gt;
const result = document.getElementById("result");&lt;/p&gt;

&lt;p&gt;const WATER_SPECIFIC_HEAT = 4.186;&lt;/p&gt;

&lt;p&gt;form.addEventListener("submit", function (event) {&lt;br&gt;
  event.preventDefault();&lt;/p&gt;

&lt;p&gt;const volume = Number(&lt;br&gt;
    document.getElementById("volume").value&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;const initialTemperature = Number(&lt;br&gt;
    document.getElementById("initial-temperature").value&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;const targetTemperature = Number(&lt;br&gt;
    document.getElementById("target-temperature").value&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;const heatingTimeMinutes = Number(&lt;br&gt;
    document.getElementById("heating-time").value&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;const safetyFactor = Number(&lt;br&gt;
    document.getElementById("safety-factor").value&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;result.classList.remove("error", "visible");&lt;/p&gt;

&lt;p&gt;if (&lt;br&gt;
    !Number.isFinite(volume) ||&lt;br&gt;
    !Number.isFinite(initialTemperature) ||&lt;br&gt;
    !Number.isFinite(targetTemperature) ||&lt;br&gt;
    !Number.isFinite(heatingTimeMinutes) ||&lt;br&gt;
    !Number.isFinite(safetyFactor)&lt;br&gt;
  ) {&lt;br&gt;
    showError("Please enter valid numeric values.");&lt;br&gt;
    return;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (volume &amp;lt;= 0 || heatingTimeMinutes &amp;lt;= 0) {&lt;br&gt;
    showError(&lt;br&gt;
      "Water volume and heating time must be greater than zero."&lt;br&gt;
    );&lt;br&gt;
    return;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (targetTemperature &amp;lt;= initialTemperature) {&lt;br&gt;
    showError(&lt;br&gt;
      "The target temperature must be higher than the initial temperature."&lt;br&gt;
    );&lt;br&gt;
    return;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;const waterMass = volume;&lt;/p&gt;

&lt;p&gt;const temperatureDifference =&lt;br&gt;
    targetTemperature - initialTemperature;&lt;/p&gt;

&lt;p&gt;const heatingTimeSeconds =&lt;br&gt;
    heatingTimeMinutes * 60;&lt;/p&gt;

&lt;p&gt;const requiredEnergy =&lt;br&gt;
    waterMass *&lt;br&gt;
    WATER_SPECIFIC_HEAT *&lt;br&gt;
    temperatureDifference;&lt;/p&gt;

&lt;p&gt;const theoreticalPower =&lt;br&gt;
    requiredEnergy / heatingTimeSeconds;&lt;/p&gt;

&lt;p&gt;const recommendedPower =&lt;br&gt;
    theoreticalPower * safetyFactor;&lt;/p&gt;

&lt;p&gt;showResult({&lt;br&gt;
    temperatureDifference,&lt;br&gt;
    theoreticalPower,&lt;br&gt;
    recommendedPower&lt;br&gt;
  });&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;function showResult({&lt;br&gt;
  temperatureDifference,&lt;br&gt;
  theoreticalPower,&lt;br&gt;
  recommendedPower&lt;br&gt;
}) {&lt;br&gt;
  result.innerHTML = `&lt;br&gt;
    &lt;strong&gt;Calculation result&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;p&amp;gt;
  Temperature increase:
  ${temperatureDifference.toFixed(1)} °C
&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;
  Theoretical heater power:
  ${theoreticalPower.toFixed(2)} kW
&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;
  Recommended heater power:
  &amp;lt;strong&amp;gt;${recommendedPower.toFixed(2)} kW&amp;lt;/strong&amp;gt;
&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;`;&lt;/p&gt;

&lt;p&gt;result.classList.add("visible");&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;function showError(message) {&lt;br&gt;
  result.textContent = message;&lt;br&gt;
  result.classList.add("visible", "error");&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Understanding the JavaScript&lt;/p&gt;

&lt;p&gt;First, we define the specific heat capacity of water:&lt;/p&gt;

&lt;p&gt;const WATER_SPECIFIC_HEAT = 4.186;&lt;/p&gt;

&lt;p&gt;The value is expressed in kilojoules per kilogram per degree Celsius.&lt;/p&gt;

&lt;p&gt;Next, the script reads the form values and converts them into numbers.&lt;/p&gt;

&lt;p&gt;const volume = Number(&lt;br&gt;
  document.getElementById("volume").value&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;We then calculate the temperature difference:&lt;/p&gt;

&lt;p&gt;const temperatureDifference =&lt;br&gt;
  targetTemperature - initialTemperature;&lt;/p&gt;

&lt;p&gt;The heating time is converted from minutes to seconds:&lt;/p&gt;

&lt;p&gt;const heatingTimeSeconds =&lt;br&gt;
  heatingTimeMinutes * 60;&lt;/p&gt;

&lt;p&gt;The required heat energy is calculated using:&lt;/p&gt;

&lt;p&gt;const requiredEnergy =&lt;br&gt;
  waterMass *&lt;br&gt;
  WATER_SPECIFIC_HEAT *&lt;br&gt;
  temperatureDifference;&lt;/p&gt;

&lt;p&gt;Because the energy is expressed in kilojoules and time is expressed in seconds, dividing the two gives us kilowatts:&lt;/p&gt;

&lt;p&gt;const theoreticalPower =&lt;br&gt;
  requiredEnergy / heatingTimeSeconds;&lt;/p&gt;

&lt;p&gt;Finally, we apply the selected safety factor:&lt;/p&gt;

&lt;p&gt;const recommendedPower =&lt;br&gt;
  theoreticalPower * safetyFactor;&lt;/p&gt;

&lt;p&gt;Example Calculation&lt;/p&gt;

&lt;p&gt;Suppose we need to heat 500 liters of water:&lt;/p&gt;

&lt;p&gt;Initial temperature: 20°C&lt;br&gt;
Target temperature: 70°C&lt;br&gt;
Heating time: 120 minutes&lt;br&gt;
Safety factor: 15%&lt;/p&gt;

&lt;p&gt;The temperature difference is:&lt;/p&gt;

&lt;p&gt;70 - 20 = 50°C&lt;/p&gt;

&lt;p&gt;The theoretical heater power is approximately:&lt;/p&gt;

&lt;p&gt;500 × 4.186 × 50 ÷ 7200&lt;br&gt;
= 14.53 kW&lt;/p&gt;

&lt;p&gt;After adding a 15% safety factor:&lt;/p&gt;

&lt;p&gt;14.53 × 1.15&lt;br&gt;
= 16.71 kW&lt;/p&gt;

&lt;p&gt;Therefore, the estimated required heater power is approximately:&lt;/p&gt;

&lt;p&gt;16.7 kW&lt;/p&gt;

&lt;p&gt;Depending on available standard heater sizes, a designer may select an 18 kW heater and use an appropriate temperature-control system.&lt;/p&gt;

&lt;p&gt;Important Engineering Limitations&lt;/p&gt;

&lt;p&gt;This calculator provides an initial estimate. It does not replace a complete thermal engineering calculation.&lt;/p&gt;

&lt;p&gt;Real industrial systems may also require consideration of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tank surface heat losses&lt;/li&gt;
&lt;li&gt;Insulation thickness and material&lt;/li&gt;
&lt;li&gt;Ambient temperature&lt;/li&gt;
&lt;li&gt;Pipe and pump heat losses&lt;/li&gt;
&lt;li&gt;Water circulation&lt;/li&gt;
&lt;li&gt;Heater surface watt density&lt;/li&gt;
&lt;li&gt;Electrical supply voltage&lt;/li&gt;
&lt;li&gt;Single-phase or three-phase power&lt;/li&gt;
&lt;li&gt;Control method&lt;/li&gt;
&lt;li&gt;Maximum allowable sheath temperature&lt;/li&gt;
&lt;li&gt;Required heating-element material&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, heating water in an insulated stainless-steel tank is different from heating oil in an open steel container.&lt;/p&gt;

&lt;p&gt;Each liquid has a different specific heat capacity, viscosity, boiling point, and maximum recommended watt density.&lt;/p&gt;

&lt;p&gt;Extending the Calculator&lt;/p&gt;

&lt;p&gt;This project can be improved by adding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Support for oil, air, and other materials&lt;/li&gt;
&lt;li&gt;Automatic current calculation&lt;/li&gt;
&lt;li&gt;Single-phase and three-phase options&lt;/li&gt;
&lt;li&gt;Voltage selection&lt;/li&gt;
&lt;li&gt;Heater resistance calculation&lt;/li&gt;
&lt;li&gt;Energy consumption estimation&lt;/li&gt;
&lt;li&gt;Estimated operating cost&lt;/li&gt;
&lt;li&gt;Heat-loss calculations&lt;/li&gt;
&lt;li&gt;Unit conversion between Celsius and Fahrenheit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a three-phase resistive heater, the approximate current can be calculated using:&lt;/p&gt;

&lt;p&gt;Current =&lt;br&gt;
Power ÷ (√3 × Voltage)&lt;/p&gt;

&lt;p&gt;For example, the current of an 18 kW three-phase heater operating at 380 volts is approximately:&lt;/p&gt;

&lt;p&gt;18,000 ÷ (1.732 × 380)&lt;br&gt;
= 27.35 A&lt;/p&gt;

&lt;p&gt;The final cable size, contactor, circuit breaker, and protection system must be selected according to applicable electrical standards and actual installation conditions.&lt;/p&gt;

&lt;p&gt;Final Thoughts&lt;/p&gt;

&lt;p&gt;This project demonstrates how JavaScript can be used to solve a real industrial engineering problem.&lt;/p&gt;

&lt;p&gt;Even a simple browser-based calculator can help engineers and customers make a better initial estimate before selecting an electric heater.&lt;/p&gt;

&lt;p&gt;However, the final heater design should always consider the material being heated, operating temperature, heater geometry, watt density, electrical supply, control system, and installation environment.&lt;/p&gt;

&lt;p&gt;I work on the design and manufacturing of industrial electric heaters, flanged heating elements, tubular heaters, cartridge heaters, and electric fan heaters at Techno Design.&lt;/p&gt;

&lt;p&gt;You can learn more about industrial heating systems at:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://technodesignn.ir" rel="noopener noreferrer"&gt;https://technodesignn.ir&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Suggested DEV Community tags:&lt;/p&gt;

&lt;h1&gt;
  
  
  javascript
&lt;/h1&gt;

&lt;h1&gt;
  
  
  webdev
&lt;/h1&gt;

&lt;h1&gt;
  
  
  engineering
&lt;/h1&gt;

&lt;h1&gt;
  
  
  tutorial
&lt;/h1&gt;

</description>
      <category>javascript</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
