27 Oct 2026

HSSP Gate Control System — Technical Report

HSSP Gate Control System — Technical Report

  • Raymond Chauke

    Senior Engineer

HSSP Gate Control System — Technical Report

HSSP Gate Control System — Technical Report

Web code-entry page + local Python poller + Wi-Fi relay. Prepared as system documentation.

1. Overview

This system lets an authorized user type a numeric code into a web page. The code is written to a local file on the web server. A Python script running on a PC on the same network polls that file, validates the code, and — if valid and fresh — pulses a Wi-Fi relay (e.g. a Shelly device) that is wired to the gate motor and/or lighting circuit.

[ Browser ] --POST code--> [ index.php on web server ] --writes--> [ 2r.txt ] | (polled every 1s) v [ gate_poller.py on PC ] | HTTP call if code valid v [ Wi-Fi Relay ] --> Gate / Lights
Scope note: This design assumes the web server, the file it writes to, and the PC running the poller are all on a trusted local network, or the file is synced over a secure private channel. It is not intended to be exposed directly to the public internet without the hardening steps in Section 5.

2. Components

ComponentRoleTechnology
index.phpCode entry form, writes code + timestamp to filePHP
2r.txtShared hand-off file between web page and pollerPlain text
gate_poller.pyWatches the file, validates code, triggers relayPython 3
Wi-Fi relay (Shelly or similar)Physically switches the gate motor / lightsLocal HTTP API

3. Web Page — index.php

Responsibilities:

  • Prompt for a numeric code via a simple HTML form.
  • Require HTTP Basic Authentication before the page loads, so it isn't usable by anyone who stumbles on the URL.
  • Validate the submitted code against a format pattern (^[0-9]{4,8}$) before writing it anywhere.
  • Rate-limit submissions (minimum 2 seconds between accepted writes) to slow down brute-force attempts.
  • Write code|timestamp to 2r.txt, so the poller can judge whether the code is still fresh.
  • Auto-refresh itself every 20 seconds so the form resets for the next person — no redirect to any other site or domain.

Key snippet — auth check

if (!isset($_SERVER['PHP_AUTH_USER']) ||
    $_SERVER['PHP_AUTH_USER'] !== $AUTH_USER ||
    $_SERVER['PHP_AUTH_PW'] !== $AUTH_PASS) {
    header('WWW-Authenticate: Basic realm="Gate Control"');
    header('HTTP/1.0 401 Unauthorized');
    exit;
}

Key snippet — validated write with rate limit

if ($now - $lastTime < $MinGapSec) {
    // too soon, reject
} elseif (preg_match('/^[0-9]{4,8}$/', $code)) {
    file_put_contents($File, $code . "|" . $now . "\n", LOCK_EX);
}

4. PC Poller — gate_poller.py

Runs continuously on a PC on the same network as the relay. Each cycle:

  1. Reads 2r.txt; skips if empty or already processed (tracked by timestamp).
  2. Rejects codes older than MAX_CODE_AGE_S (default 30s) — prevents an old code left in the file from triggering the gate later (replay protection).
  3. Rejects codes not in the VALID_CODES allow-list.
  4. On a valid, fresh code: calls the relay's local HTTP API to turn it on, waits RELAY_PULSE_S, then turns it off — a momentary pulse, matching how most gate motors expect a trigger signal.
  5. Clears 2r.txt after every attempt (valid or not) so nothing is processed twice.
  6. Logs every event (received code, accepted/rejected, relay result) to gate_poller.log.

Key snippet — relay pulse

def pulse_gate():
    if call_relay(True):
        time.sleep(RELAY_PULSE_S)
        call_relay(False)

Configuration values to set before running

VariablePurpose
CODE_FILEMust match the path index.php writes to
VALID_CODESSet of codes allowed to trigger the gate
RELAY_IPLocal IP address of the Wi-Fi relay
MAX_CODE_AGE_SAnti-replay window, in seconds

5. Relay Integration (Shelly)

Shelly Gen1 devices expose a simple local HTTP API:

GET http://<relay_ip>/relay/0?turn=on
GET http://<relay_ip>/relay/0?turn=off

Gen2/Gen3 (Plus/Pro) devices use a JSON-RPC style endpoint instead:

GET http://<relay_ip>/rpc/Switch.Set?id=0&on=true

For other relay brands (Sonoff/eWeLink, TP-Link Kasa, Tasmota-flashed devices), the call_relay() function in the poller is the only part that needs to change — swap in that vendor's local API call.

6. Security Considerations & Hardening Checklist

RiskMitigation in this designRecommended if internet-facing
Unauthorized access to the formHTTP Basic AuthUse HTTPS so credentials aren't sent in the clear; consider a stronger auth method
Brute-forcing the code2-second rate limit per submissionAdd a lockout after N failed attempts, and/or longer/alphanumeric codes
Replaying an old codeTimestamp + 30s freshness check in the pollerConsider one-time codes instead of a fixed allow-list
Plaintext code storageN/A — short-lived hand-off file, cleared after each useIf retained long-term, don't store codes in plaintext
Network exposureAssumes local/trusted networkDo not expose 2r.txt or the PHP page directly to the public internet without a firewall, VPN, or reverse proxy with auth
Do not deploy as-is to a public server. The default credentials in index.php (admin / change-me-now) must be changed, and the page should sit behind HTTPS before it is reachable from outside your home/local network.

7. Setup Steps

  1. Place index.php on your web server (e.g. a Raspberry Pi running Apache/Nginx + PHP, or any LAN-accessible PHP host).
  2. Edit $AUTH_USER / $AUTH_PASS in index.php.
  3. Confirm 2r.txt is writable by the web server process, and reachable by the PC running the poller (same machine, or a shared/mounted path).
  4. On the PC: pip install requests.
  5. Edit CODE_FILE, VALID_CODES, and RELAY_IP at the top of gate_poller.py.
  6. Confirm the relay's Gen (1 vs 2/3) and adjust the API call in call_relay() if needed.
  7. Run python3 gate_poller.py and watch gate_poller.log while testing a submission from the web page.
  8. Set the poller to run as a background service (e.g. a systemd service on Linux, or Task Scheduler on Windows) so it survives reboots.

8. Testing Checklist

  • Submit a valid code → relay should pulse, log should show "Gate pulsed successfully."
  • Submit an invalid-format code (letters, too short/long) → page shows "Invalid code format," nothing written.
  • Submit two valid codes within 2 seconds → second one should be rejected by the rate limit.
  • Manually write an old timestamp into 2r.txt → poller should log "Ignored stale code."
  • Stop the poller, submit a code, restart the poller → confirm it does not fire on stale leftover data older than MAX_CODE_AGE_S.
System documentation generated for the HSSP gate/light control project. Review the security checklist in Section 6 before exposing this system beyond a trusted local network.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HSSP Gate Control System — Technical Report</title>
<style>
  :root {
    --bg: #ffffff; --fg: #1a1a1a; --muted: #555; --accent: #2a6f97;
    --code-bg: #f4f6f8; --border: #ddd; --warn-bg: #fff4e5; --warn-border: #e8a33d;
  }
  @media (prefers-color-scheme: dark) {
    :root { --bg:#14181c; --fg:#e8e8e8; --muted:#aaa; --accent:#6fb3d9;
             --code-bg:#1e242a; --border:#333; --warn-bg:#2a2210; --warn-border:#9c6b1f; }
  }
  body { background:var(--bg); color:var(--fg); font-family: -apple-system, Segoe UI, Roboto, Arial, sans-serif;
         line-height:1.6; max-width:860px; margin:0 auto; padding:40px 20px; }
  h1 { font-size:1.9em; border-bottom:3px solid var(--accent); padding-bottom:10px; }
  h2 { color:var(--accent); margin-top:2em; border-bottom:1px solid var(--border); padding-bottom:6px; }
  h3 { margin-top:1.4em; }
  code, pre { background:var(--code-bg); border:1px solid var(--border); border-radius:4px; }
  code { padding:2px 6px; font-size:0.92em; }
  pre { padding:14px; overflow-x:auto; }
  pre code { border:none; padding:0; }
  table { border-collapse:collapse; width:100%; margin:1em 0; }
  th, td { border:1px solid var(--border); padding:8px 10px; text-align:left; font-size:0.95em; }
  th { background:var(--code-bg); }
  .warn { background:var(--warn-bg); border:1px solid var(--warn-border); border-radius:6px; padding:12px 16px; margin:1em 0; }
  .muted { color:var(--muted); font-size:0.92em; }
  .flow { font-family: monospace; background:var(--code-bg); border:1px solid var(--border); border-radius:6px;
          padding:16px; white-space:pre; overflow-x:auto; }
  footer { margin-top:3em; padding-top:1em; border-top:1px solid var(--border); color:var(--muted); font-size:0.85em; }
</style>
</head>
<body>

<h1>HSSP Gate Control System — Technical Report</h1>
<p class="muted">Web code-entry page + local Python poller + Wi-Fi relay. Prepared as system documentation.</p>

<h2>1. Overview</h2>
<p>This system lets an authorized user type a numeric code into a web page. The code is written to a local
file on the web server. A Python script running on a PC on the same network polls that file, validates the
code, and — if valid and fresh — pulses a Wi-Fi relay (e.g. a Shelly device) that is wired to the gate motor
and/or lighting circuit.</p>

<div class="flow">
[ Browser ] --POST code--> [ index.php on web server ] --writes--> [ 2r.txt ]
                                                                        |
                                                              (polled every 1s)
                                                                        v
                                                        [ gate_poller.py on PC ]
                                                                        |
                                                              HTTP call if code valid
                                                                        v
                                                        [ Wi-Fi Relay ] --> Gate / Lights
</div>

<div class="warn">
<strong>Scope note:</strong> This design assumes the web server, the file it writes to, and the PC running
the poller are all on a trusted local network, or the file is synced over a secure private channel. It is
not intended to be exposed directly to the public internet without the hardening steps in Section 5.
</div>

<h2>2. Components</h2>
<table>
<tr><th>Component</th><th>Role</th><th>Technology</th></tr>
<tr><td><code>index.php</code></td><td>Code entry form, writes code + timestamp to file</td><td>PHP</td></tr>
<tr><td><code>2r.txt</code></td><td>Shared hand-off file between web page and poller</td><td>Plain text</td></tr>
<tr><td><code>gate_poller.py</code></td><td>Watches the file, validates code, triggers relay</td><td>Python 3</td></tr>
<tr><td>Wi-Fi relay (Shelly or similar)</td><td>Physically switches the gate motor / lights</td><td>Local HTTP API</td></tr>
</table>

<h2>3. Web Page — <code>index.php</code></h2>
<p>Responsibilities:</p>
<ul>
  <li>Prompt for a numeric code via a simple HTML form.</li>
  <li>Require HTTP Basic Authentication before the page loads, so it isn't usable by anyone who stumbles
      on the URL.</li>
  <li>Validate the submitted code against a format pattern (<code>^[0-9]{4,8}$</code>) before writing it
      anywhere.</li>
  <li>Rate-limit submissions (minimum 2 seconds between accepted writes) to slow down brute-force attempts.</li>
  <li>Write <code>code|timestamp</code> to <code>2r.txt</code>, so the poller can judge whether the code is
      still fresh.</li>
  <li>Auto-refresh itself every 20 seconds so the form resets for the next person — no redirect to any
      other site or domain.</li>
</ul>

<h3>Key snippet — auth check</h3>
<pre><code>if (!isset($_SERVER['PHP_AUTH_USER']) ||
    $_SERVER['PHP_AUTH_USER'] !== $AUTH_USER ||
    $_SERVER['PHP_AUTH_PW'] !== $AUTH_PASS) {
    header('WWW-Authenticate: Basic realm="Gate Control"');
    header('HTTP/1.0 401 Unauthorized');
    exit;
}</code></pre>

<h3>Key snippet — validated write with rate limit</h3>
<pre><code>if ($now - $lastTime &lt; $MinGapSec) {
    // too soon, reject
} elseif (preg_match('/^[0-9]{4,8}$/', $code)) {
    file_put_contents($File, $code . "|" . $now . "\n", LOCK_EX);
}</code></pre>

<h2>4. PC Poller — <code>gate_poller.py</code></h2>
<p>Runs continuously on a PC on the same network as the relay. Each cycle:</p>
<ol>
  <li>Reads <code>2r.txt</code>; skips if empty or already processed (tracked by timestamp).</li>
  <li>Rejects codes older than <code>MAX_CODE_AGE_S</code> (default 30s) — prevents an old code
      left in the file from triggering the gate later (replay protection).</li>
  <li>Rejects codes not in the <code>VALID_CODES</code> allow-list.</li>
  <li>On a valid, fresh code: calls the relay's local HTTP API to turn it <code>on</code>, waits
      <code>RELAY_PULSE_S</code>, then turns it <code>off</code> — a momentary pulse, matching how most
      gate motors expect a trigger signal.</li>
  <li>Clears <code>2r.txt</code> after every attempt (valid or not) so nothing is processed twice.</li>
  <li>Logs every event (received code, accepted/rejected, relay result) to <code>gate_poller.log</code>.</li>
</ol>

<h3>Key snippet — relay pulse</h3>
<pre><code>def pulse_gate():
    if call_relay(True):
        time.sleep(RELAY_PULSE_S)
        call_relay(False)</code></pre>

<h3>Configuration values to set before running</h3>
<table>
<tr><th>Variable</th><th>Purpose</th></tr>
<tr><td><code>CODE_FILE</code></td><td>Must match the path <code>index.php</code> writes to</td></tr>
<tr><td><code>VALID_CODES</code></td><td>Set of codes allowed to trigger the gate</td></tr>
<tr><td><code>RELAY_IP</code></td><td>Local IP address of the Wi-Fi relay</td></tr>
<tr><td><code>MAX_CODE_AGE_S</code></td><td>Anti-replay window, in seconds</td></tr>
</table>

<h2>5. Relay Integration (Shelly)</h2>
<p>Shelly Gen1 devices expose a simple local HTTP API:</p>
<pre><code>GET http://&lt;relay_ip&gt;/relay/0?turn=on
GET http://&lt;relay_ip&gt;/relay/0?turn=off</code></pre>
<p>Gen2/Gen3 (Plus/Pro) devices use a JSON-RPC style endpoint instead:</p>
<pre><code>GET http://&lt;relay_ip&gt;/rpc/Switch.Set?id=0&amp;on=true</code></pre>
<p class="muted">For other relay brands (Sonoff/eWeLink, TP-Link Kasa, Tasmota-flashed devices), the
<code>call_relay()</code> function in the poller is the only part that needs to change — swap in that
vendor's local API call.</p>

<h2>6. Security Considerations &amp; Hardening Checklist</h2>
<table>
<tr><th>Risk</th><th>Mitigation in this design</th><th>Recommended if internet-facing</th></tr>
<tr><td>Unauthorized access to the form</td><td>HTTP Basic Auth</td><td>Use HTTPS so credentials aren't sent in the clear; consider a stronger auth method</td></tr>
<tr><td>Brute-forcing the code</td><td>2-second rate limit per submission</td><td>Add a lockout after N failed attempts, and/or longer/alphanumeric codes</td></tr>
<tr><td>Replaying an old code</td><td>Timestamp + 30s freshness check in the poller</td><td>Consider one-time codes instead of a fixed allow-list</td></tr>
<tr><td>Plaintext code storage</td><td>N/A — short-lived hand-off file, cleared after each use</td><td>If retained long-term, don't store codes in plaintext</td></tr>
<tr><td>Network exposure</td><td>Assumes local/trusted network</td><td>Do not expose <code>2r.txt</code> or the PHP page directly to the public internet without a firewall, VPN, or reverse proxy with auth</td></tr>
</table>

<div class="warn">
<strong>Do not deploy as-is to a public server.</strong> The default credentials in <code>index.php</code>
(<code>admin</code> / <code>change-me-now</code>) must be changed, and the page should sit behind HTTPS
before it is reachable from outside your home/local network.
</div>

<h2>7. Setup Steps</h2>
<ol>
  <li>Place <code>index.php</code> on your web server (e.g. a Raspberry Pi running Apache/Nginx + PHP, or any LAN-accessible PHP host).</li>
  <li>Edit <code>$AUTH_USER</code> / <code>$AUTH_PASS</code> in <code>index.php</code>.</li>
  <li>Confirm <code>2r.txt</code> is writable by the web server process, and reachable by the PC running the poller (same machine, or a shared/mounted path).</li>
  <li>On the PC: <code>pip install requests</code>.</li>
  <li>Edit <code>CODE_FILE</code>, <code>VALID_CODES</code>, and <code>RELAY_IP</code> at the top of <code>gate_poller.py</code>.</li>
  <li>Confirm the relay's Gen (1 vs 2/3) and adjust the API call in <code>call_relay()</code> if needed.</li>
  <li>Run <code>python3 gate_poller.py</code> and watch <code>gate_poller.log</code> while testing a submission from the web page.</li>
  <li>Set the poller to run as a background service (e.g. a systemd service on Linux, or Task Scheduler on Windows) so it survives reboots.</li>
</ol>

<h2>8. Testing Checklist</h2>
<ul>
  <li>Submit a valid code → relay should pulse, log should show "Gate pulsed successfully."</li>
  <li>Submit an invalid-format code (letters, too short/long) → page shows "Invalid code format," nothing written.</li>
  <li>Submit two valid codes within 2 seconds → second one should be rejected by the rate limit.</li>
  <li>Manually write an old timestamp into <code>2r.txt</code> → poller should log "Ignored stale code."</li>
  <li>Stop the poller, submit a code, restart the poller → confirm it does <em>not</em> fire on stale leftover data older than <code>MAX_CODE_AGE_S</code>.</li>
</ul>

<footer>
  System documentation generated for the HSSP gate/light control project. Review the security checklist
  in Section 6 before exposing this system beyond a trusted local network.
</footer>

</body>
</html>