diff --git a/SwitchBox/SwitchBox5/New b/SwitchBox/SwitchBox5/New new file mode 100644 index 0000000..c43032b --- /dev/null +++ b/SwitchBox/SwitchBox5/New @@ -0,0 +1 @@ +New Board diff --git a/SwitchBox/SwitchBox5/README.md b/SwitchBox/SwitchBox5/README.md new file mode 100644 index 0000000..b2a4104 --- /dev/null +++ b/SwitchBox/SwitchBox5/README.md @@ -0,0 +1,65 @@ +# Cab Switchbox 5 + +ESP32-based 16-input network switchbox for the AgOpenGPS Rate Controller. + +## Repository contents + +- `firmware/ESP32_Cab_Switchbox/` — Arduino firmware source. +- `release/` — precompiled ESP32 firmware files. +- `docs/` — user manual, printable HTML, screenshots, and manual generator. +- `hardware/SwitchBox5_Terminals_PCB_01/` — Rev 01 PCB production files. + +## Hardware target + +- ESP32 board: ESP32 DevKit / DOIT ESP32 DEVKIT V1 +- Arduino FQBN: `esp32:esp32:esp32doit-devkit-v1` +- PCB: `SwitchBox5-Terminals-PCB-01` +- Recommended enclosure: Hammond `E1555K2GY`, NEMA 4X +- Board supply: 12-24 VDC +- Onboard buzzer: 12 V only; use a 12 V supply if buzzer operation is required + +## Build + +Install Arduino CLI and the Espressif ESP32 Arduino core, then run from the +repository root: + +```powershell +arduino-cli compile ` + --fqbn esp32:esp32:esp32doit-devkit-v1 ` + --output-dir build ` + firmware/ESP32_Cab_Switchbox +``` + +## Flashing + +The easiest full-device image is: + +```text +release/ESP32_Cab_Switchbox.ino.merged.bin +``` + +Flash the merged image at address `0x0` with an ESP32 flashing tool. + +Arduino CLI can also compile and upload the source directly: + +```powershell +arduino-cli upload ` + -p COMx ` + --fqbn esp32:esp32:esp32doit-devkit-v1 ` + firmware/ESP32_Cab_Switchbox +``` + +Replace `COMx` with the switchbox serial port. + +## Documentation + +See `docs/CAB_SWITCHBOX_MANUAL.pdf` for setup, terminal assignments, network +configuration, wiring, buzzer behavior, and troubleshooting. + +## Current build + +Compiled June 19, 2026: + +- Program storage: 1,045,571 bytes (79%) +- Dynamic memory: 50,504 bytes (15%) + diff --git a/SwitchBox/SwitchBox5/RELEASE_NOTES.md b/SwitchBox/SwitchBox5/RELEASE_NOTES.md new file mode 100644 index 0000000..fc55753 --- /dev/null +++ b/SwitchBox/SwitchBox5/RELEASE_NOTES.md @@ -0,0 +1,18 @@ +# Release Notes + +## Cab Switchbox 5 — June 19, 2026 + +- Supports 16 configurable 12-24 VDC switch inputs. +- Ethernet and WiFi communication with the Rate Controller. +- Web-based setup, status, and troubleshooting pages. +- Board-mounted 12 V alarm buzzer with Off, Reduced, and Full settings. +- Rate Controller alarm packet support. +- Rev 01 terminal PCB documentation and production files. +- Updated manual for the Hammond E1555K2GY NEMA 4X enclosure. + +### Important power note + +The PCB inputs and electronics support 12-24 VDC. The installed buzzer is a +12 V component connected to input power, so buzzer operation requires a 12 V +board supply. + diff --git a/SwitchBox/SwitchBox5/docs/CAB_SWITCHBOX_MANUAL.md b/SwitchBox/SwitchBox5/docs/CAB_SWITCHBOX_MANUAL.md new file mode 100644 index 0000000..1743dfc --- /dev/null +++ b/SwitchBox/SwitchBox5/docs/CAB_SWITCHBOX_MANUAL.md @@ -0,0 +1,521 @@ +# Cab Switchbox Manual + +Last updated: June 19, 2026 + +Hardware shown in this manual: **SwitchBox5-Terminals-PCB-01 (Rev 01)**. + +## Overview + +The ESP32 Cab Switchbox is a network switch input module for the rate controller system. It reads up to 16 cab switch inputs, converts them into the switchbox packet used by the rate controller, and sends that packet over UDP by Ethernet and WiFi. + +The switchbox is intended for cab controls such as: + +- Master on / master off +- Rate up / rate down +- Manual rate override +- Manual section override +- Rate 2 +- Work switch +- Section switches 1 through 16 +- Board-mounted alarm buzzer output + +The current firmware is in: + +```text +ESP32_Cab_Switchbox/ESP32_Cab_Switchbox.ino +``` + +## Default Network Settings + +The switchbox starts its own WiFi access point and can also use Ethernet. + +Default settings: + +| Item | Default | +| --- | --- | +| Access point name | `CabSwitchbox-XXXX` | +| Access point password | blank / open network | +| WiFi setup IP | `192.168.0.71` | +| Ethernet IP | `192.168.0.70` | +| Gateway | `192.168.0.1` | +| Subnet | `255.255.255.0` | +| Web page port | `80` | +| mDNS name | `cabswitchbox.local` | +| UDP send port | `29999` | +| Local UDP port | `28888` | + +## Additional Parts Needed + +The SwitchBox5 Rev 01 terminal PCB needs these additional parts installed before use: + +| Part | Notes | +| --- | --- | +| 2 amp mini fuse | Installs in the fuse holder. Protects the input/switch-feed circuit. | +| ESP32-DEVKitC-VE | Main controller board. Install in the ESP32 header sockets. | +| W5500 Ethernet Module | Ethernet network module. Install in the W5500 module header. | +| Hammond `E1555K2GY` enclosure | Polycarbonate NEMA 4X enclosure, 6.3 x 3.5 x 3.5 inches. The SwitchBox5 PCB fits this enclosure. | + +## Enclosure + +The SwitchBox5 PCB fits the **Hammond Manufacturing E1555K2GY** +polycarbonate enclosure: + +| Item | Specification | +| --- | --- | +| Manufacturer | Hammond Manufacturing | +| Part number | `E1555K2GY` | +| Outside size | 6.3 x 3.5 x 3.5 inches | +| Protection rating | NEMA 4X | +| Material | Polycarbonate | + +Plan cable glands, Ethernet access, switch wiring, and board mounting before +drilling the enclosure. Keep the lid sealing surface clear of wiring and +hardware so the NEMA 4X seal is maintained. + +## Power And Switch Feed Terminals + +Power the board from the input power terminals: + +The upper six-position terminal block on Rev 01 is ordered left to right: + +| Position | Terminal | Use | +| --- | --- | --- | +| 1 | `12Vin` | Connect to supply +12-24 VDC. | +| 2 | `GND` | Connect to supply negative / ground. | +| 3 | `12V` | Fused +12-24 VDC output for switch feeds. | +| 4 | `12V` | Fused +12-24 VDC output for switch feeds. | +| 5 | `GND` | Ground return terminal. | +| 6 | `GND` | Ground return terminal. | + +The `12V` terminals are fused outputs from the board input power. Use these fused `12V` terminals to feed the operator switches, then return the switched voltage to the desired `INx` input. + +Typical wiring: + +```text +Supply +12-24 VDC -------- 12Vin +Supply negative ---------- GND + +Fused 12V terminal ------- switch ------- INx +``` + +The Rev 01 input power fuse is 2 amps. The fused `12V` terminals are intended for feeding switch inputs, not for powering heavy loads. + +The PCB electronics and switch inputs can operate from **12-24 VDC**. However, +the buzzer is a **12 V board-mounted component connected directly to input +power**. The buzzer will only operate correctly when the board is powered from +12 VDC. Do not expect buzzer operation with a 24 V supply, and do not apply +24 V to the installed 12 V buzzer. + +## Rev 01 Terminal Layout + +The four six-position terminal blocks are ordered as follows: + +| Block | Left-to-right terminal order | +| --- | --- | +| Power | `12Vin`, `GND`, `12V`, `12V`, `GND`, `GND` | +| Inputs 0-5 | `IN0`, `IN1`, `IN2`, `IN3`, `IN4`, `IN5` | +| Inputs 6-11 | `IN6`, `IN7`, `IN8`, `IN9`, `IN10`, `IN11` | +| Inputs 12-15 / switch feed | `IN12`, `IN13`, `IN14`, `IN15`, `12V`, `12V` | + +The Rev 01 silkscreen also identifies the default functions below the input +terminals: master on/off, rate increase/decrease, manual rate, manual section, +Rate 2, and sections S1 through S9. + +## Accessing The Setup Page + +### Using The Switchbox WiFi Access Point + +1. Power the Cab Switchbox. +2. On a phone, tablet, or laptop, connect to the WiFi network named `CabSwitchbox`. +3. Open a browser and go to: + +```text +http://192.168.0.71/ +``` + +If mDNS is working on the device you are using, this may also work: + +```text +http://cabswitchbox.local/ +``` + +### Using Ethernet + +1. Connect the switchbox Ethernet port to the same network as the rate controller / setup computer. +2. Make sure the computer is on the same subnet as the switchbox. +3. Open: + +```text +http://192.168.0.70/ +``` + +If needed, first connect by WiFi and change the Ethernet IP address to match the network you are using. + +## Setup Page Sections + +The setup page has a live status area and a configuration form. After changing settings, click **Save and restart**. The switchbox saves the settings to EEPROM and restarts. + +### Status + +The status area shows: + +| Status item | Meaning | +| --- | --- | +| Input board | The switch input board is detected and ready. | +| Ethernet | Ethernet hardware is detected and the link is up. | +| WiFi station | The switchbox is connected to an existing WiFi network. | +| Buzzer | The GPIO27 buzzer output is currently on. | +| Alarm packet | A recent alarm packet has been received from Rate Controller. | +| Alarm active | Rate Controller is requesting the cab buzzer. | +| Packet | The six-byte packet being sent to the rate controller. | +| Decoded PGN32618 | Human-readable state of the outgoing switchbox packet. | +| Inputs | Live state and assigned action for each input `IN0` through `IN15`. | + +### Network Settings + +| Setting | Description | +| --- | --- | +| Ethernet IP | Static IP used by the Ethernet interface. | +| WiFi IP | IP used by the switchbox access point. | +| Gateway | Gateway used for Ethernet and station WiFi. | +| Subnet | Subnet mask. | +| Also connect to existing WiFi network | Enables WiFi station mode in addition to the switchbox access point. | +| Existing WiFi SSID | Name of the WiFi network the switchbox should join. | +| Existing WiFi Password | Password for the existing WiFi network. | +| Access Point Name | Name of the WiFi network created by the switchbox. | +| Access Point Password | Password for the switchbox access point. Leave blank for open WiFi, or use 8 or more characters. | + +### Input Settings + +| Setting | Default | Description | +| --- | --- | --- | +| Override section switches ON | Off | Forces mapped section switch inputs ON without jumpers. Use this when section switches are not installed. | +| Send period ms | `100` | How often the switchbox packet is sent. Valid range is 50 to 1000 ms. | +| Pulse length ms | `250` | Length of master and rate pulses. Valid range is 50 to 1000 ms. | +| IN0-IN15 action | See table below | Assigns what each physical input does. | + +### Alarm Buzzer Settings + +| Setting | Default | Description | +| --- | --- | --- | +| Buzzer level | `Reduced` | Select `Off`, `Reduced`, or `Full` for the board buzzer. | +| Test buzzer | n/a | Sounds the buzzer for two seconds without changing saved settings. | + +## Default Input Map + +Factory/default input assignments: + +| Input | Default action | Typical wiring | +| --- | --- | --- | +| IN0 | Master on | Momentary switch feeding +12-24 VDC to IN0 | +| IN1 | Master off | Momentary switch feeding +12-24 VDC to IN1 | +| IN2 | Rate up | Momentary switch feeding +12-24 VDC to IN2 | +| IN3 | Rate down | Momentary switch feeding +12-24 VDC to IN3 | +| IN4 | Manual rate override | Toggle switch feeding +12-24 VDC to IN4 | +| IN5 | Manual section override | Toggle switch feeding +12-24 VDC to IN5 | +| IN6 | Rate 2 | Toggle switch feeding +12-24 VDC to IN6 | +| IN7 | Section 1 | Toggle switch feeding +12-24 VDC to IN7 | +| IN8 | Section 2 | Toggle switch feeding +12-24 VDC to IN8 | +| IN9 | Section 3 | Toggle switch feeding +12-24 VDC to IN9 | +| IN10 | Section 4 | Toggle switch feeding +12-24 VDC to IN10 | +| IN11 | Section 5 | Toggle switch feeding +12-24 VDC to IN11 | +| IN12 | Section 6 | Toggle switch feeding +12-24 VDC to IN12 | +| IN13 | Section 7 | Toggle switch feeding +12-24 VDC to IN13 | +| IN14 | Section 8 | Toggle switch feeding +12-24 VDC to IN14 | +| IN15 | Section 9 | Toggle switch feeding +12-24 VDC to IN15 | + +Any input can be reassigned from the web page. + +Available actions: + +| Action | Behavior | +| --- | --- | +| Disabled | Input is ignored. | +| Master on | Momentary master on pulse. | +| Master off | Momentary master off pulse. | +| Rate up | Rate up command. | +| Rate down | Rate down command. | +| Work switch | Implement up/down permissive. ON allows application; OFF forces master/sections off when Work Switch is enabled in the Rate Controller app. | +| Manual section override | When active, requests manual section override. | +| Manual rate override | When active, requests manual rate override. | +| Rate 2 | Sends Rate 2 while active. | +| Master | Maintained master switch. ON sends master on; OFF sends master off. | +| Section 1-16 | Sends the matching section switch bit while active. | + +## Wiring Inputs + +The switchbox5 board inputs are made for 12-24 VDC switch signals. Each switch input turns ON when +12-24 VDC is applied to that `INx` terminal. + +Input behavior: + +- An open / unpowered input reads OFF. +- Applying +12-24 VDC to an `INx` terminal reads ON. +- The 12-24 VDC supply negative must be connected to the switchbox input `GND`. + +Typical switchbox5 field wiring: + +```text +12Vin ---------------- supply +12-24 VDC +GND ------------------- supply negative +12V fused output ------ switch ------ INx +``` + +For a momentary button, use a normally-open button between a fused `12V` terminal and the assigned `INx` terminal. + +For a toggle switch, use a maintained switch between a fused `12V` terminal and the assigned `INx` terminal. + +Use the `IN0` through `IN15` input terminals on the switchbox board. Do not connect field wiring to internal board components. + +## Wiring The Master And Rate Controls + +A simple installation without section switches can use only: + +| Function | Default input | +| --- | --- | +| Master on | IN0 | +| Master off | IN1 | +| Rate up | IN2 | +| Rate down | IN3 | +| Manual rate override | IN4 | +| Manual section override | IN5 | +| Rate 2 | IN6 | + +Wire each installed switch so it feeds fused +12-24 VDC from a `12V` terminal to the assigned `INx` terminal when the switch is ON or pressed. + +If section switches are not installed, enable **Override section switches ON**. This makes all inputs assigned to `Section 1` through `Section 16` behave as ON in the outgoing packet, so the system does not require jumper wires on the unused section switch inputs. + +## Wiring Section Switches + +For normal section switch operation: + +1. Leave **Override section switches ON** unchecked. +2. Wire each section toggle so it feeds fused +12-24 VDC from a `12V` terminal to its assigned `INx` terminal when ON. + +Default section switch wiring: + +| Section | Default input | +| --- | --- | +| Section 1 | IN7 | +| Section 2 | IN8 | +| Section 3 | IN9 | +| Section 4 | IN10 | +| Section 5 | IN11 | +| Section 6 | IN12 | +| Section 7 | IN13 | +| Section 8 | IN14 | +| Section 9 | IN15 | + +Sections 10 through 16 can be used by reassigning inputs from the setup page. The board has 16 total inputs, so using more section switches may require disabling or moving other functions. + +## Override Section Switches ON + +Use **Override section switches ON** when the operator does not have physical section switches installed. + +When enabled: + +- Inputs assigned to `Section 1` through `Section 16` are forced ON in the switchbox packet. +- Master, rate, and override inputs still use the real physical switch state. +- No section input jumpers are required. + +When disabled: + +- Section inputs behave normally. +- An open / unpowered section input is OFF. + +## Work Switch + +The `Work switch` input is normally used for an implement lift switch, height switch, whisker switch, pressure switch, or other signal that tells the rate controller whether the implement is in work. + +Typical use: + +```text +Implement down / in work = +12-24 VDC to Work switch input = ON +Implement up / out of work = input unpowered = OFF +``` + +When Work Switch is enabled in the Rate Controller app, it acts as a master permissive: + +- Work switch ON allows the rate controller to apply when the master and section conditions are also satisfied. +- Work switch OFF forces master/sections off. +- It does not replace Auto Rate or Auto Section. Those are separate switchbox functions. + +In the Rate Controller app, Work Switch must be enabled in the switch settings. If it is not enabled there, the app ignores the work switch signal and behaves as if work is always allowed. + +## Alarm Buzzer Output + +The active 12 V alarm buzzer is installed directly on the SwitchBox5 PCB. It is +not an additional external part. The buzzer is driven from ESP32 Dev Board pin +`GPIO27` through the onboard low-side MOSFET. + +The buzzer receives board input voltage directly. Therefore: + +- With a 12 VDC board supply, the onboard buzzer operates normally. +- The remainder of the switchbox can operate from 12-24 VDC. +- With a 24 VDC board supply, the installed 12 V buzzer must not be operated. + +GPIO27 does not need an external pull-up. The 100k pulldown keeps the buzzer off while the ESP32 is booting or reset. + +## Packet Behavior + +The switchbox sends a six-byte packet to UDP port `29999`. + +Packet layout: + +| Byte | Meaning | +| --- | --- | +| 0 | Header byte `106` | +| 1 | Header byte `127` | +| 2 | Status bits | +| 3 | Section bits 1-8 | +| 4 | Section bits 9-16 | +| 5 | Checksum | + +Status byte bits: + +| Bit | Meaning | +| --- | --- | +| 0 | Rate 2 | +| 1 | Master on | +| 2 | Master off | +| 3 | Rate up | +| 4 | Rate down | +| 5 | Auto section | +| 6 | Auto rate | +| 7 | Work switch | + +Section byte behavior: + +- Byte 3 bit 0 = Section 1 +- Byte 3 bit 7 = Section 8 +- Byte 4 bit 0 = Section 9 +- Byte 4 bit 7 = Section 16 + +The checksum is the sum of bytes 0 through 4, stored in one byte. + +## Recommended First Setup + +1. Power the switchbox. +2. Connect to WiFi network `CabSwitchbox`. +3. Open `http://192.168.0.71/`. +4. Confirm the status page shows `Input board` ON. +5. If using Ethernet, set the Ethernet IP to match the rate controller network. +6. If section switches are not installed, check **Override section switches ON**. +7. Confirm each installed switch changes state in the Inputs status area. +8. Click **Save and restart**. +9. After restart, verify the rate controller responds to master and rate commands. + +## Example Setups + +### Master And Rate Only, No Section Switches + +Use this when only master and rate controls are installed. + +Recommended settings: + +| Setting | Value | +| --- | --- | +| Override section switches ON | Checked | +| IN0 | Master on | +| IN1 | Master off | +| IN2 | Rate up | +| IN3 | Rate down | +| IN4-IN6 | As needed | +| IN7-IN15 | Section 1-9, or Disabled | + +### Full Section Switchbox + +Use this when physical section switches are installed. + +Recommended settings: + +| Setting | Value | +| --- | --- | +| Override section switches ON | Unchecked | +| IN0-IN6 | Master/rate controls | +| IN7-IN15 | Section 1-9 by default | + +### Maintained Master Switch + +If using a maintained master switch instead of separate master on/off buttons: + +1. Set one input action to `Master`. +2. Wire the maintained switch so it feeds fused +12-24 VDC from a `12V` terminal to that input when ON. +3. Set unused `Master on` and `Master off` inputs to `Disabled`, or reassign them. + +When the maintained master input turns ON, the switchbox sends master on. When it turns OFF, the switchbox sends master off. + +## Troubleshooting + +### Cannot Open The Web Page + +- Make sure the switchbox is powered. +- If using WiFi, connect to the `CabSwitchbox` access point first. +- Try `http://192.168.0.71/` for WiFi access. +- Try `http://192.168.0.70/` for Ethernet access. +- If the IP address was changed, connect by the other interface or reflash/reset configuration if needed. + +### Input Board Shows OFF + +The switchbox is not detecting the input board. + +Check: + +- Input board power and ground +- I2C SDA/SCL wiring +- Soldering around the input board + +If Input board is OFF, switch input states will not be reliable. + +### Ethernet Shows OFF + +Check: + +- Ethernet cable +- Link lights on the W5500 module or connector +- Network switch/router +- W5500 SPI wiring and chip select + +The switchbox can still be configured by WiFi if Ethernet is unavailable. + +### Inputs Do Not Turn ON + +For the switchbox5 board, each input needs +12-24 VDC on the `INx` terminal to turn ON. + +Check: + +- The switch feed has +12-24 VDC. +- The switch output is connected to the correct `INx` terminal. +- The 12-24 VDC supply negative is connected to switchbox input `GND`. +- The input is assigned to the expected action in the setup page. + +### Missing Section Switches Stop Operation + +Enable **Override section switches ON**. This forces section switch bits ON without changing master or rate input behavior. + +### Rate Up Or Rate Down Sticks ON + +Check whether the input is assigned to `Rate up` or `Rate down` and whether the physical switch is stuck active. The status page shows live input states. + +Check whether the physical switch is stuck on, or whether +12-24 VDC is being backfed into that input. + +### WiFi Station Does Not Connect + +Check: + +- SSID spelling +- Password spelling +- WiFi signal strength +- Whether the WiFi network uses a compatible 2.4 GHz mode + +The switchbox access point remains available even when station WiFi is enabled. + +## Notes For Firmware Updates + +Configuration is stored in EEPROM. The current firmware uses config version 3. + +Defaults are restored when: + +- EEPROM does not contain a valid switchbox config. +- The stored config version is not recognized. + +The firmware includes migration for older switchbox config versions 1 and 2. diff --git a/SwitchBox/SwitchBox5/docs/CAB_SWITCHBOX_MANUAL.pdf b/SwitchBox/SwitchBox5/docs/CAB_SWITCHBOX_MANUAL.pdf new file mode 100644 index 0000000..21a1ade Binary files /dev/null and b/SwitchBox/SwitchBox5/docs/CAB_SWITCHBOX_MANUAL.pdf differ diff --git a/SwitchBox/SwitchBox5/docs/CAB_SWITCHBOX_MANUAL_PRINT.html b/SwitchBox/SwitchBox5/docs/CAB_SWITCHBOX_MANUAL_PRINT.html new file mode 100644 index 0000000..c7c8048 --- /dev/null +++ b/SwitchBox/SwitchBox5/docs/CAB_SWITCHBOX_MANUAL_PRINT.html @@ -0,0 +1,395 @@ + + + + + Cab Switchbox Manual + + + +

Cab Switchbox Manual

+

Last updated: June 19, 2026

+

Hardware shown in this manual: SwitchBox5-Terminals-PCB-01 (Rev 01).

+

Overview

+

The ESP32 Cab Switchbox is a network switch input module for the rate controller system. It reads up to 16 cab switch inputs, converts them into the switchbox packet used by the rate controller, and sends that packet over UDP by Ethernet and WiFi.

+

The switchbox is intended for cab controls such as:

+ +

The current firmware is in:

+

+ESP32_Cab_Switchbox/ESP32_Cab_Switchbox.ino
+
+
+

Default Network Settings

+

The switchbox starts its own WiFi access point and can also use Ethernet.

+

Default settings:

+
ItemDefault
Access point nameCabSwitchbox-XXXX
Access point passwordblank / open network
WiFi setup IP192.168.0.71
Ethernet IP192.168.0.70
Gateway192.168.0.1
Subnet255.255.255.0
Web page port80
mDNS namecabswitchbox.local
UDP send port29999
Local UDP port28888
+

Additional Parts Needed

+

The SwitchBox5 Rev 01 terminal PCB needs these additional parts installed before use:

+
PartNotes
2 amp mini fuseInstalls in the fuse holder. Protects the input/switch-feed circuit.
ESP32-DEVKitC-VEMain controller board. Install in the ESP32 header sockets.
W5500 Ethernet ModuleEthernet network module. Install in the W5500 module header.
Hammond E1555K2GY enclosurePolycarbonate NEMA 4X enclosure, 6.3 x 3.5 x 3.5 inches. The SwitchBox5 PCB fits this enclosure.
+

Enclosure

+

The SwitchBox5 PCB fits the Hammond Manufacturing E1555K2GY

+

polycarbonate enclosure:

+
ItemSpecification
ManufacturerHammond Manufacturing
Part numberE1555K2GY
Outside size6.3 x 3.5 x 3.5 inches
Protection ratingNEMA 4X
MaterialPolycarbonate
+

Plan cable glands, Ethernet access, switch wiring, and board mounting before

+

drilling the enclosure. Keep the lid sealing surface clear of wiring and

+

hardware so the NEMA 4X seal is maintained.

+ +
+

Board Power And Switch Feed

+
+ SwitchBox5 Rev 01 board terminal layout +
SwitchBox5-Terminals-PCB-01 (Rev 01). Connect board power to 12Vin and GND. Use the fused 12V terminals through switches to power the IN0-IN15 inputs.
+
+
+

Power And Switch Feed Terminals

+

Power the board from the input power terminals:

+

The upper six-position terminal block on Rev 01 is ordered left to right:

+
PositionTerminalUse
112VinConnect to supply +12-24 VDC.
2GNDConnect to supply negative / ground.
312VFused +12-24 VDC output for switch feeds.
412VFused +12-24 VDC output for switch feeds.
5GNDGround return terminal.
6GNDGround return terminal.
+

The 12V terminals are fused outputs from the board input power. Use these fused 12V terminals to feed the operator switches, then return the switched voltage to the desired INx input.

+

Typical wiring:

+

+Supply +12-24 VDC -------- 12Vin
+
+Supply negative ---------- GND
+
+
+
+Fused 12V terminal ------- switch ------- INx
+
+
+

The Rev 01 input power fuse is 2 amps. The fused 12V terminals are intended for feeding switch inputs, not for powering heavy loads.

+

The PCB electronics and switch inputs can operate from 12-24 VDC. However,

+

the buzzer is a **12 V board-mounted component connected directly to input

+

power**. The buzzer will only operate correctly when the board is powered from

+

12 VDC. Do not expect buzzer operation with a 24 V supply, and do not apply

+

24 V to the installed 12 V buzzer.

+

Rev 01 Terminal Layout

+

The four six-position terminal blocks are ordered as follows:

+
BlockLeft-to-right terminal order
Power12Vin, GND, 12V, 12V, GND, GND
Inputs 0-5IN0, IN1, IN2, IN3, IN4, IN5
Inputs 6-11IN6, IN7, IN8, IN9, IN10, IN11
Inputs 12-15 / switch feedIN12, IN13, IN14, IN15, 12V, 12V
+

The Rev 01 silkscreen also identifies the default functions below the input

+

terminals: master on/off, rate increase/decrease, manual rate, manual section,

+

Rate 2, and sections S1 through S9.

+

Accessing The Setup Page

+

Using The Switchbox WiFi Access Point

+
    +
  1. Power the Cab Switchbox.
  2. +
  3. On a phone, tablet, or laptop, connect to the WiFi network named CabSwitchbox.
  4. +
  5. Open a browser and go to:
  6. +
+

+http://192.168.0.71/
+
+
+

If mDNS is working on the device you are using, this may also work:

+

+http://cabswitchbox.local/
+
+
+

Using Ethernet

+
    +
  1. Connect the switchbox Ethernet port to the same network as the rate controller / setup computer.
  2. +
  3. Make sure the computer is on the same subnet as the switchbox.
  4. +
  5. Open:
  6. +
+

+http://192.168.0.70/
+
+
+

If needed, first connect by WiFi and change the Ethernet IP address to match the network you are using.

+ +
+

Setup Page Screenshots

+
+ Cab Switchbox setup page desktop screenshot +
Desktop setup page with live status, decoded packet, network settings, and input configuration.
+
+
+ Cab Switchbox setup page mobile screenshot +
Mobile layout used when configuring the switchbox from a phone or tablet in the cab.
+
+
+

Setup Page Sections

+

The setup page has a live status area and a configuration form. After changing settings, click Save and restart. The switchbox saves the settings to EEPROM and restarts.

+

Status

+

The status area shows:

+
Status itemMeaning
Input boardThe switch input board is detected and ready.
EthernetEthernet hardware is detected and the link is up.
WiFi stationThe switchbox is connected to an existing WiFi network.
BuzzerThe GPIO27 buzzer output is currently on.
Alarm packetA recent alarm packet has been received from Rate Controller.
Alarm activeRate Controller is requesting the cab buzzer.
PacketThe six-byte packet being sent to the rate controller.
Decoded PGN32618Human-readable state of the outgoing switchbox packet.
InputsLive state and assigned action for each input IN0 through IN15.
+

Network Settings

+
SettingDescription
Ethernet IPStatic IP used by the Ethernet interface.
WiFi IPIP used by the switchbox access point.
GatewayGateway used for Ethernet and station WiFi.
SubnetSubnet mask.
Also connect to existing WiFi networkEnables WiFi station mode in addition to the switchbox access point.
Existing WiFi SSIDName of the WiFi network the switchbox should join.
Existing WiFi PasswordPassword for the existing WiFi network.
Access Point NameName of the WiFi network created by the switchbox.
Access Point PasswordPassword for the switchbox access point. Leave blank for open WiFi, or use 8 or more characters.
+

Input Settings

+
SettingDefaultDescription
Override section switches ONOffForces mapped section switch inputs ON without jumpers. Use this when section switches are not installed.
Send period ms100How often the switchbox packet is sent. Valid range is 50 to 1000 ms.
Pulse length ms250Length of master and rate pulses. Valid range is 50 to 1000 ms.
IN0-IN15 actionSee table belowAssigns what each physical input does.
+

Alarm Buzzer Settings

+
SettingDefaultDescription
Buzzer levelReducedSelect Off, Reduced, or Full for the board buzzer.
Test buzzern/aSounds the buzzer for two seconds without changing saved settings.
+

Default Input Map

+

Factory/default input assignments:

+
InputDefault actionTypical wiring
IN0Master onMomentary switch feeding +12-24 VDC to IN0
IN1Master offMomentary switch feeding +12-24 VDC to IN1
IN2Rate upMomentary switch feeding +12-24 VDC to IN2
IN3Rate downMomentary switch feeding +12-24 VDC to IN3
IN4Manual rate overrideToggle switch feeding +12-24 VDC to IN4
IN5Manual section overrideToggle switch feeding +12-24 VDC to IN5
IN6Rate 2Toggle switch feeding +12-24 VDC to IN6
IN7Section 1Toggle switch feeding +12-24 VDC to IN7
IN8Section 2Toggle switch feeding +12-24 VDC to IN8
IN9Section 3Toggle switch feeding +12-24 VDC to IN9
IN10Section 4Toggle switch feeding +12-24 VDC to IN10
IN11Section 5Toggle switch feeding +12-24 VDC to IN11
IN12Section 6Toggle switch feeding +12-24 VDC to IN12
IN13Section 7Toggle switch feeding +12-24 VDC to IN13
IN14Section 8Toggle switch feeding +12-24 VDC to IN14
IN15Section 9Toggle switch feeding +12-24 VDC to IN15
+

Any input can be reassigned from the web page.

+

Available actions:

+
ActionBehavior
DisabledInput is ignored.
Master onMomentary master on pulse.
Master offMomentary master off pulse.
Rate upRate up command.
Rate downRate down command.
Work switchImplement up/down permissive. ON allows application; OFF forces master/sections off when Work Switch is enabled in the Rate Controller app.
Manual section overrideWhen active, requests manual section override.
Manual rate overrideWhen active, requests manual rate override.
Rate 2Sends Rate 2 while active.
MasterMaintained master switch. ON sends master on; OFF sends master off.
Section 1-16Sends the matching section switch bit while active.
+

Wiring Inputs

+

The switchbox5 board inputs are made for 12-24 VDC switch signals. Each switch input turns ON when +12-24 VDC is applied to that INx terminal.

+

Input behavior:

+ +

Typical switchbox5 field wiring:

+

+12Vin ---------------- supply +12-24 VDC
+
+GND ------------------- supply negative
+
+12V fused output ------ switch ------ INx
+
+
+

For a momentary button, use a normally-open button between a fused 12V terminal and the assigned INx terminal.

+

For a toggle switch, use a maintained switch between a fused 12V terminal and the assigned INx terminal.

+

Use the IN0 through IN15 input terminals on the switchbox board. Do not connect field wiring to internal board components.

+

Wiring The Master And Rate Controls

+

A simple installation without section switches can use only:

+
FunctionDefault input
Master onIN0
Master offIN1
Rate upIN2
Rate downIN3
Manual rate overrideIN4
Manual section overrideIN5
Rate 2IN6
+

Wire each installed switch so it feeds fused +12-24 VDC from a 12V terminal to the assigned INx terminal when the switch is ON or pressed.

+

If section switches are not installed, enable Override section switches ON. This makes all inputs assigned to Section 1 through Section 16 behave as ON in the outgoing packet, so the system does not require jumper wires on the unused section switch inputs.

+

Wiring Section Switches

+

For normal section switch operation:

+
    +
  1. Leave Override section switches ON unchecked.
  2. +
  3. Wire each section toggle so it feeds fused +12-24 VDC from a 12V terminal to its assigned INx terminal when ON.
  4. +
+

Default section switch wiring:

+
SectionDefault input
Section 1IN7
Section 2IN8
Section 3IN9
Section 4IN10
Section 5IN11
Section 6IN12
Section 7IN13
Section 8IN14
Section 9IN15
+

Sections 10 through 16 can be used by reassigning inputs from the setup page. The board has 16 total inputs, so using more section switches may require disabling or moving other functions.

+

Override Section Switches ON

+

Use Override section switches ON when the operator does not have physical section switches installed.

+

When enabled:

+ +

When disabled:

+ +

Work Switch

+

The Work switch input is normally used for an implement lift switch, height switch, whisker switch, pressure switch, or other signal that tells the rate controller whether the implement is in work.

+

Typical use:

+

+Implement down / in work = +12-24 VDC to Work switch input = ON
+
+Implement up / out of work = input unpowered = OFF
+
+
+

When Work Switch is enabled in the Rate Controller app, it acts as a master permissive:

+ +

In the Rate Controller app, Work Switch must be enabled in the switch settings. If it is not enabled there, the app ignores the work switch signal and behaves as if work is always allowed.

+

Alarm Buzzer Output

+

The active 12 V alarm buzzer is installed directly on the SwitchBox5 PCB. It is

+

not an additional external part. The buzzer is driven from ESP32 Dev Board pin

+

GPIO27 through the onboard low-side MOSFET.

+

The buzzer receives board input voltage directly. Therefore:

+ +

GPIO27 does not need an external pull-up. The 100k pulldown keeps the buzzer off while the ESP32 is booting or reset.

+

Packet Behavior

+

The switchbox sends a six-byte packet to UDP port 29999.

+

Packet layout:

+
ByteMeaning
0Header byte 106
1Header byte 127
2Status bits
3Section bits 1-8
4Section bits 9-16
5Checksum
+

Status byte bits:

+
BitMeaning
0Rate 2
1Master on
2Master off
3Rate up
4Rate down
5Auto section
6Auto rate
7Work switch
+

Section byte behavior:

+ +

The checksum is the sum of bytes 0 through 4, stored in one byte.

+ +
    +
  1. Power the switchbox.
  2. +
  3. Connect to WiFi network CabSwitchbox.
  4. +
  5. Open http://192.168.0.71/.
  6. +
  7. Confirm the status page shows Input board ON.
  8. +
  9. If using Ethernet, set the Ethernet IP to match the rate controller network.
  10. +
  11. If section switches are not installed, check Override section switches ON.
  12. +
  13. Confirm each installed switch changes state in the Inputs status area.
  14. +
  15. Click Save and restart.
  16. +
  17. After restart, verify the rate controller responds to master and rate commands.
  18. +
+

Example Setups

+

Master And Rate Only, No Section Switches

+

Use this when only master and rate controls are installed.

+

Recommended settings:

+
SettingValue
Override section switches ONChecked
IN0Master on
IN1Master off
IN2Rate up
IN3Rate down
IN4-IN6As needed
IN7-IN15Section 1-9, or Disabled
+

Full Section Switchbox

+

Use this when physical section switches are installed.

+

Recommended settings:

+
SettingValue
Override section switches ONUnchecked
IN0-IN6Master/rate controls
IN7-IN15Section 1-9 by default
+

Maintained Master Switch

+

If using a maintained master switch instead of separate master on/off buttons:

+
    +
  1. Set one input action to Master.
  2. +
  3. Wire the maintained switch so it feeds fused +12-24 VDC from a 12V terminal to that input when ON.
  4. +
  5. Set unused Master on and Master off inputs to Disabled, or reassign them.
  6. +
+

When the maintained master input turns ON, the switchbox sends master on. When it turns OFF, the switchbox sends master off.

+

Troubleshooting

+

Cannot Open The Web Page

+ +

Input Board Shows OFF

+

The switchbox is not detecting the input board.

+

Check:

+ +

If Input board is OFF, switch input states will not be reliable.

+

Ethernet Shows OFF

+

Check:

+ +

The switchbox can still be configured by WiFi if Ethernet is unavailable.

+

Inputs Do Not Turn ON

+

For the switchbox5 board, each input needs +12-24 VDC on the INx terminal to turn ON.

+

Check:

+ +

Missing Section Switches Stop Operation

+

Enable Override section switches ON. This forces section switch bits ON without changing master or rate input behavior.

+

Rate Up Or Rate Down Sticks ON

+

Check whether the input is assigned to Rate up or Rate down and whether the physical switch is stuck active. The status page shows live input states.

+

Check whether the physical switch is stuck on, or whether +12-24 VDC is being backfed into that input.

+

WiFi Station Does Not Connect

+

Check:

+ +

The switchbox access point remains available even when station WiFi is enabled.

+

Notes For Firmware Updates

+

Configuration is stored in EEPROM. The current firmware uses config version 3.

+

Defaults are restored when:

+ +

The firmware includes migration for older switchbox config versions 1 and 2.

+ + diff --git a/SwitchBox/SwitchBox5/docs/generate_manual_pdf.py b/SwitchBox/SwitchBox5/docs/generate_manual_pdf.py new file mode 100644 index 0000000..142e04b --- /dev/null +++ b/SwitchBox/SwitchBox5/docs/generate_manual_pdf.py @@ -0,0 +1,289 @@ +from __future__ import annotations + +import html +import re +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent +MANUAL_MD = ROOT / "CAB_SWITCHBOX_MANUAL.md" +OUTPUT_HTML = ROOT / "CAB_SWITCHBOX_MANUAL_PRINT.html" + + +def slugify(text: str) -> str: + value = re.sub(r"[^a-zA-Z0-9]+", "-", text.strip().lower()).strip("-") + return value or "section" + + +def parse_inline(text: str) -> str: + escaped = html.escape(text) + escaped = re.sub(r"`([^`]+)`", r"\1", escaped) + escaped = re.sub(r"\*\*([^*]+)\*\*", r"\1", escaped) + return escaped + + +def table_to_html(lines: list[str]) -> str: + rows: list[list[str]] = [] + for line in lines: + cells = [cell.strip() for cell in line.strip().strip("|").split("|")] + rows.append(cells) + + out = ["
"] + if rows: + out.append("") + for cell in rows[0]: + out.append(f"") + out.append("") + + out.append("") + for row in rows[2:]: + out.append("") + for cell in row: + out.append(f"") + out.append("") + out.append("
{parse_inline(cell)}
{parse_inline(cell)}
") + return "".join(out) + + +def markdown_to_html(markdown: str) -> str: + lines = markdown.splitlines() + out: list[str] = [] + i = 0 + in_code = False + in_ul = False + in_ol = False + + while i < len(lines): + line = lines[i] + + if line.startswith("```"): + if in_ul: + out.append("") + in_ul = False + if in_ol: + out.append("") + in_ol = False + if not in_code: + out.append("
")
+                in_code = True
+            else:
+                out.append("
") + in_code = False + i += 1 + continue + + if in_code: + out.append(html.escape(line) + "\n") + i += 1 + continue + + if line.strip().startswith("|") and i + 1 < len(lines) and set(lines[i + 1].replace("|", "").strip()) <= {"-", ":", " "}: + if in_ul: + out.append("") + in_ul = False + if in_ol: + out.append("") + in_ol = False + table_lines = [line, lines[i + 1]] + i += 2 + while i < len(lines) and lines[i].strip().startswith("|"): + table_lines.append(lines[i]) + i += 1 + out.append(table_to_html(table_lines)) + continue + + stripped = line.strip() + if not stripped: + if in_ul: + out.append("") + in_ul = False + if in_ol: + out.append("") + in_ol = False + i += 1 + continue + + heading = re.match(r"^(#{1,6})\s+(.+)$", stripped) + if heading: + if in_ul: + out.append("") + in_ul = False + if in_ol: + out.append("") + in_ol = False + level = len(heading.group(1)) + text = heading.group(2) + out.append(f"{parse_inline(text)}") + i += 1 + continue + + ordered = re.match(r"^\d+\.\s+(.+)$", stripped) + if ordered: + if in_ul: + out.append("") + in_ul = False + if not in_ol: + out.append("
    ") + in_ol = True + out.append(f"
  1. {parse_inline(ordered.group(1))}
  2. ") + i += 1 + continue + + if stripped.startswith("- "): + if in_ol: + out.append("
") + in_ol = False + if not in_ul: + out.append("") + in_ul = False + if in_ol: + out.append("") + in_ol = False + out.append(f"

{parse_inline(stripped)}

") + i += 1 + + if in_ul: + out.append("") + if in_ol: + out.append("") + return "\n".join(out) + + +def build_html() -> str: + body = markdown_to_html(MANUAL_MD.read_text(encoding="utf-8")) + terminal_diagram = """ +
+

Board Power And Switch Feed

+
+ SwitchBox5 Rev 01 board terminal layout +
SwitchBox5-Terminals-PCB-01 (Rev 01). Connect board power to 12Vin and GND. Use the fused 12V terminals through switches to power the IN0-IN15 inputs.
+
+
+ """ + screenshots = """ +
+

Setup Page Screenshots

+
+ Cab Switchbox setup page desktop screenshot +
Desktop setup page with live status, decoded packet, network settings, and input configuration.
+
+
+ Cab Switchbox setup page mobile screenshot +
Mobile layout used when configuring the switchbox from a phone or tablet in the cab.
+
+
+ """ + body = body.replace("

", terminal_diagram + "

", 1) + body = body.replace("

", screenshots + "

", 1) + + return f""" + + + + Cab Switchbox Manual + + + +{body} + + +""" + + +def main() -> None: + OUTPUT_HTML.write_text(build_html(), encoding="utf-8") + print(OUTPUT_HTML) + + +if __name__ == "__main__": + main() diff --git a/SwitchBox/SwitchBox5/docs/manual-assets/BoardPNG.png b/SwitchBox/SwitchBox5/docs/manual-assets/BoardPNG.png new file mode 100644 index 0000000..47753de Binary files /dev/null and b/SwitchBox/SwitchBox5/docs/manual-assets/BoardPNG.png differ diff --git a/SwitchBox/SwitchBox5/docs/manual-assets/BoardPNG_R01.png b/SwitchBox/SwitchBox5/docs/manual-assets/BoardPNG_R01.png new file mode 100644 index 0000000..5c8748d Binary files /dev/null and b/SwitchBox/SwitchBox5/docs/manual-assets/BoardPNG_R01.png differ diff --git a/SwitchBox/SwitchBox5/docs/manual-assets/cab-switchbox-desktop.png b/SwitchBox/SwitchBox5/docs/manual-assets/cab-switchbox-desktop.png new file mode 100644 index 0000000..ff619f7 Binary files /dev/null and b/SwitchBox/SwitchBox5/docs/manual-assets/cab-switchbox-desktop.png differ diff --git a/SwitchBox/SwitchBox5/docs/manual-assets/cab-switchbox-mobile.png b/SwitchBox/SwitchBox5/docs/manual-assets/cab-switchbox-mobile.png new file mode 100644 index 0000000..44b8632 Binary files /dev/null and b/SwitchBox/SwitchBox5/docs/manual-assets/cab-switchbox-mobile.png differ diff --git a/SwitchBox/SwitchBox5/docs/manual-assets/switchbox5-power-inputs.svg b/SwitchBox/SwitchBox5/docs/manual-assets/switchbox5-power-inputs.svg new file mode 100644 index 0000000..ea4371c --- /dev/null +++ b/SwitchBox/SwitchBox5/docs/manual-assets/switchbox5-power-inputs.svg @@ -0,0 +1,92 @@ + + + + + + + SwitchBox5 Terminal Wiring + + + + + + + + + + + 12Vin + 12V + 12V + 12V + GND + GND + + + + + + + + + + + IN0 + IN1 + IN2 + IN3 + IN4 + IN5 + + + + + + + + + + + IN6 + IN7 + IN8 + IN9 + IN10 + IN11 + + + Power the board here + Supply +12-24 VDC to 12Vin + Supply negative to GND + Input fuse: 1 amp + + + Use fused 12V for switches + 12V terminals are fused outputs + from the board input power. + Run 12V through each switch, + then back to the desired INx. + + + + + + + + switch + + INPUT VOLTAGE = 12-24 VDC + SEND 12-24V TO INPUTS TO ACTIVATE + diff --git a/SwitchBox/SwitchBox5/docs/status-demo.html b/SwitchBox/SwitchBox5/docs/status-demo.html new file mode 100644 index 0000000..a17312c --- /dev/null +++ b/SwitchBox/SwitchBox5/docs/status-demo.html @@ -0,0 +1,161 @@ + + + + + Cab Switchbox Status Demo + + + +

Cab Switchbox Setup Demo

+
+
+

Status

+
+
+ +
+
+

Network

+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + + + + + + + + +
+ +
+

Inputs

+ + + + + +
+
+ + +
+
+ + + diff --git a/SwitchBox/SwitchBox5/firmware/ESP32_Cab_Switchbox/ESP32_Cab_Switchbox.ino b/SwitchBox/SwitchBox5/firmware/ESP32_Cab_Switchbox/ESP32_Cab_Switchbox.ino new file mode 100644 index 0000000..34e21f1 --- /dev/null +++ b/SwitchBox/SwitchBox5/firmware/ESP32_Cab_Switchbox/ESP32_Cab_Switchbox.ino @@ -0,0 +1,1118 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define INO_DESCRIPTION "ESP32_Cab_Switchbox" + +const uint16_t INO_ID = 24056; +const uint16_t EEPROM_SIZE = 512; +const uint32_t CONFIG_MAGIC = 0x53574258UL; // SWBX +const uint8_t CONFIG_VERSION = 3; +const uint8_t LEGACY_CONFIG_VERSION = 1; +const uint8_t SECTION_SWITCH_REQUIRED_CONFIG_VERSION = 2; + +const uint8_t MCP23017_ADDRESS = 0x20; +const uint8_t W5500_CS_PIN = 5; +const uint8_t BUZZER_PIN = 27; +const uint16_t RATE_CONTROLLER_PORT = 29999; +const uint16_t LOCAL_UDP_PORT = 28888; +const uint16_t WEB_PORT = 80; +const uint16_t AP_DNS_PORT = 53; +const char* MDNS_NAME = "cabswitchbox"; +const uint16_t BUZZER_TEST_MS = 2000; +const uint16_t ALARM_PACKET_TIMEOUT_MS = 3000; +const uint16_t BUZZER_VOLUME_FRAME_MS = 25; +const uint16_t BUZZER_REDUCED_ON_MS = 14; + +enum InputAction : uint8_t +{ + ACT_DISABLED = 0, + ACT_AUTO_MODE = 1, + ACT_MANUAL_MODE = 2, + ACT_OFF_MODE = 3, + ACT_MASTER_ON = 4, + ACT_MASTER_OFF = 5, + ACT_RATE_UP = 6, + ACT_RATE_DOWN = 7, + ACT_WORK_SWITCH = 8, + ACT_AUTO_SECTION = 9, + ACT_AUTO_RATE = 10, + ACT_RATE_2 = 11, + ACT_MASTER_SWITCH = 12, + ACT_SECTION_1 = 20, + ACT_SECTION_2 = 21, + ACT_SECTION_3 = 22, + ACT_SECTION_4 = 23, + ACT_SECTION_5 = 24, + ACT_SECTION_6 = 25, + ACT_SECTION_7 = 26, + ACT_SECTION_8 = 27, + ACT_SECTION_9 = 28, + ACT_SECTION_10 = 29, + ACT_SECTION_11 = 30, + ACT_SECTION_12 = 31, + ACT_SECTION_13 = 32, + ACT_SECTION_14 = 33, + ACT_SECTION_15 = 34, + ACT_SECTION_16 = 35 +}; + +enum CabMode : uint8_t +{ + MODE_OFF = 0, + MODE_MANUAL = 1, + MODE_AUTO = 2 +}; + +struct SwitchboxConfig +{ + uint32_t magic; + uint8_t version; + uint8_t ethIp[4]; + uint8_t wifiIp[4]; + uint8_t gateway[4]; + uint8_t subnet[4]; + bool wifiStationEnabled; + char ssid[32]; + char password[32]; + char apName[32]; + char apPassword[32]; + bool inputActiveLow; + uint16_t sendPeriodMs; + uint16_t pulseMs; + uint8_t inputAction[16]; + bool overrideSectionSwitchesOn; + uint8_t buzzerVolume; +}; + +SwitchboxConfig Cfg; + +EthernetUDP UdpEth; +EthernetServer EthServer(WEB_PORT); +WiFiUDP UdpWifi; +WebServer WifiServer(WEB_PORT); +DNSServer DnsServer; + +bool EthernetHardwareFound = false; +bool McpFound = false; +bool LastInputActive[16] = { false }; +bool InputActive[16] = { false }; +uint16_t RawMcpInputs = 0; +CabMode Mode = MODE_OFF; +uint32_t LastSendMs = 0; +uint32_t MasterOnPulseUntil = 0; +uint32_t MasterOffPulseUntil = 0; +uint32_t RateUpPulseUntil = 0; +uint32_t RateDownPulseUntil = 0; +uint32_t BuzzerTestUntil = 0; +uint32_t LastAlarmPacketMs = 0; +uint8_t AlarmStatus = 0; +uint8_t AlarmPattern = 0; +bool BuzzerOutputOn = false; + +uint8_t NormalizeBuzzerLevel(uint8_t raw) +{ + if (raw <= 2) return raw; + if (raw == 0) return 0; + if (raw < 67) return 1; + return 2; +} + +const char* BuzzerLevelName(uint8_t level) +{ + switch (NormalizeBuzzerLevel(level)) + { + case 0: return "Off"; + case 1: return "Reduced"; + default: return "Full"; + } +} + +String DefaultApName() +{ + uint64_t mac = ESP.getEfuseMac(); + uint16_t suffix = (uint16_t)(mac & 0xFFFF); + char name[32]; + snprintf(name, sizeof(name), "CabSwitchbox-%04X", suffix); + return String(name); +} + +void ApplyDefaultApNameIfNeeded() +{ + String current = String(Cfg.apName); + if (current.length() == 0 || current == "CabSwitchbox") + { + String apName = DefaultApName(); + apName.toCharArray(Cfg.apName, sizeof(Cfg.apName)); + } +} + +const char* ActionName(uint8_t action) +{ + switch (action) + { + case ACT_DISABLED: return "Disabled"; + case ACT_AUTO_MODE: return "Auto mode"; + case ACT_MANUAL_MODE: return "Manual mode"; + case ACT_OFF_MODE: return "Off mode"; + case ACT_MASTER_ON: return "Master on"; + case ACT_MASTER_OFF: return "Master off"; + case ACT_RATE_UP: return "Rate up"; + case ACT_RATE_DOWN: return "Rate down"; + case ACT_WORK_SWITCH: return "Work switch"; + case ACT_AUTO_SECTION: return "Manual section override"; + case ACT_AUTO_RATE: return "Manual rate override"; + case ACT_RATE_2: return "Rate 2"; + case ACT_MASTER_SWITCH: return "Master"; + case ACT_SECTION_1: return "Section 1"; + case ACT_SECTION_2: return "Section 2"; + case ACT_SECTION_3: return "Section 3"; + case ACT_SECTION_4: return "Section 4"; + case ACT_SECTION_5: return "Section 5"; + case ACT_SECTION_6: return "Section 6"; + case ACT_SECTION_7: return "Section 7"; + case ACT_SECTION_8: return "Section 8"; + case ACT_SECTION_9: return "Section 9"; + case ACT_SECTION_10: return "Section 10"; + case ACT_SECTION_11: return "Section 11"; + case ACT_SECTION_12: return "Section 12"; + case ACT_SECTION_13: return "Section 13"; + case ACT_SECTION_14: return "Section 14"; + case ACT_SECTION_15: return "Section 15"; + case ACT_SECTION_16: return "Section 16"; + default: return "Unknown"; + } +} + +void SetDefaultConfig() +{ + memset(&Cfg, 0, sizeof(Cfg)); + Cfg.magic = CONFIG_MAGIC; + Cfg.version = CONFIG_VERSION; + Cfg.ethIp[0] = 192; Cfg.ethIp[1] = 168; Cfg.ethIp[2] = 0; Cfg.ethIp[3] = 70; + Cfg.wifiIp[0] = 192; Cfg.wifiIp[1] = 168; Cfg.wifiIp[2] = 0; Cfg.wifiIp[3] = 71; + Cfg.gateway[0] = 192; Cfg.gateway[1] = 168; Cfg.gateway[2] = 0; Cfg.gateway[3] = 1; + Cfg.subnet[0] = 255; Cfg.subnet[1] = 255; Cfg.subnet[2] = 255; Cfg.subnet[3] = 0; + Cfg.wifiStationEnabled = false; + String apName = DefaultApName(); + apName.toCharArray(Cfg.apName, sizeof(Cfg.apName)); + strncpy(Cfg.apPassword, "", sizeof(Cfg.apPassword) - 1); + Cfg.inputActiveLow = true; + Cfg.overrideSectionSwitchesOn = false; + Cfg.sendPeriodMs = 100; + Cfg.pulseMs = 250; + Cfg.buzzerVolume = 60; + + Cfg.inputAction[0] = ACT_MASTER_ON; // IN0 + Cfg.inputAction[1] = ACT_MASTER_OFF; // IN1 + Cfg.inputAction[2] = ACT_RATE_UP; // IN2 + Cfg.inputAction[3] = ACT_RATE_DOWN; // IN3 + Cfg.inputAction[4] = ACT_AUTO_RATE; // IN4, asserted = manual rate override + Cfg.inputAction[5] = ACT_AUTO_SECTION; // IN5, asserted = manual section override + Cfg.inputAction[6] = ACT_RATE_2; // IN6 + Cfg.inputAction[7] = ACT_SECTION_1; // IN7 + Cfg.inputAction[8] = ACT_SECTION_2; // IN8 + Cfg.inputAction[9] = ACT_SECTION_3; // IN9 + Cfg.inputAction[10] = ACT_SECTION_4; // IN10 + Cfg.inputAction[11] = ACT_SECTION_5; // IN11 + Cfg.inputAction[12] = ACT_SECTION_6; // IN12 + Cfg.inputAction[13] = ACT_SECTION_7; // IN13 + Cfg.inputAction[14] = ACT_SECTION_8; // IN14 + Cfg.inputAction[15] = ACT_SECTION_9; // IN15 +} + +IPAddress ToIp(const uint8_t parts[4]) +{ + return IPAddress(parts[0], parts[1], parts[2], parts[3]); +} + +IPAddress BroadcastFor(const uint8_t parts[4]) +{ + return IPAddress(parts[0], parts[1], parts[2], 255); +} + +uint8_t Crc(byte data[], byte length) +{ + uint8_t result = 0; + for (byte i = 0; i < length; i++) result += data[i]; + return result; +} + +void LoadConfig() +{ + EEPROM.get(0, Cfg); + if (Cfg.magic == CONFIG_MAGIC && Cfg.version == LEGACY_CONFIG_VERSION) + { + Cfg.version = CONFIG_VERSION; + Cfg.overrideSectionSwitchesOn = false; + EEPROM.put(0, Cfg); + EEPROM.commit(); + } + else if (Cfg.magic == CONFIG_MAGIC && Cfg.version == SECTION_SWITCH_REQUIRED_CONFIG_VERSION) + { + Cfg.overrideSectionSwitchesOn = !Cfg.overrideSectionSwitchesOn; + Cfg.version = CONFIG_VERSION; + EEPROM.put(0, Cfg); + EEPROM.commit(); + } + else if (Cfg.magic != CONFIG_MAGIC || Cfg.version != CONFIG_VERSION) + { + SetDefaultConfig(); + EEPROM.put(0, Cfg); + EEPROM.commit(); + } + + if (Cfg.sendPeriodMs < 50 || Cfg.sendPeriodMs > 1000) Cfg.sendPeriodMs = 100; + if (Cfg.pulseMs < 50 || Cfg.pulseMs > 1000) Cfg.pulseMs = 250; + Cfg.buzzerVolume = NormalizeBuzzerLevel(Cfg.buzzerVolume); + Cfg.inputActiveLow = true; + ApplyDefaultApNameIfNeeded(); +} + +void SaveConfig() +{ + Cfg.magic = CONFIG_MAGIC; + Cfg.version = CONFIG_VERSION; + EEPROM.put(0, Cfg); + EEPROM.commit(); +} + +void SetupBuzzer() +{ + pinMode(BUZZER_PIN, OUTPUT); + digitalWrite(BUZZER_PIN, LOW); +} + +bool AlarmPacketIsLive() +{ + return LastAlarmPacketMs > 0 && (millis() - LastAlarmPacketMs) < ALARM_PACKET_TIMEOUT_MS; +} + +bool AlarmIsActive() +{ + return AlarmPacketIsLive() && (AlarmStatus & 0x01); +} + +bool AlarmPatternAllowsBeep() +{ + if (!AlarmIsActive()) return false; + + uint32_t phase = millis() % 1000; + if (AlarmPattern == 2 || (AlarmStatus & 0x02)) return phase < 150 || (phase >= 300 && phase < 450); + return phase < 350; +} + +void DriveBuzzer(bool requestOn) +{ + uint8_t level = NormalizeBuzzerLevel(Cfg.buzzerVolume); + bool outputOn = false; + + if (requestOn && level > 0) + { + if (level >= 2) + { + outputOn = true; + } + else + { + outputOn = (millis() % BUZZER_VOLUME_FRAME_MS) < BUZZER_REDUCED_ON_MS; + } + } + + if (outputOn != BuzzerOutputOn) + { + BuzzerOutputOn = outputOn; + digitalWrite(BUZZER_PIN, outputOn ? HIGH : LOW); + } +} + +void UpdateBuzzer() +{ + bool testOn = millis() < BuzzerTestUntil; + DriveBuzzer(testOn || AlarmPatternAllowsBeep()); +} + +void StartBuzzerTest() +{ + BuzzerTestUntil = millis() + BUZZER_TEST_MS; +} + +void ParseAlarmPacket(byte data[], uint16_t len) +{ + if (len < 5) return; + if (data[0] != 107 || data[1] != 127) return; + if (data[4] != Crc(data, 4)) return; + + AlarmStatus = data[2]; + AlarmPattern = data[3]; + LastAlarmPacketMs = millis(); +} + +void ReceiveAlarmPackets() +{ + byte data[16]; + + if (EthernetHardwareFound && Ethernet.linkStatus() == LinkON) + { + uint16_t len = UdpEth.parsePacket(); + if (len > 0) + { + if (len > sizeof(data)) len = sizeof(data); + UdpEth.read(data, len); + ParseAlarmPacket(data, len); + } + } + + uint16_t len = UdpWifi.parsePacket(); + if (len > 0) + { + if (len > sizeof(data)) len = sizeof(data); + UdpWifi.read(data, len); + ParseAlarmPacket(data, len); + } +} + +void SetupMcp23017() +{ + Wire.begin(); + + Wire.beginTransmission(MCP23017_ADDRESS); + McpFound = (Wire.endTransmission() == 0); + if (!McpFound) + { + Serial.println("MCP23017 not found at 0x20."); + return; + } + + Wire.beginTransmission(MCP23017_ADDRESS); + Wire.write(0x00); // IODIRA + Wire.write(0xFF); + Wire.write(0xFF); // IODIRB + Wire.endTransmission(); + + Wire.beginTransmission(MCP23017_ADDRESS); + Wire.write(0x0C); // GPPUA + Wire.write(0xFF); + Wire.write(0xFF); // GPPUB + Wire.endTransmission(); + + Serial.println("MCP23017 found at 0x20."); +} + +uint16_t ReadMcpInputs() +{ + if (!McpFound) return 0; + + Wire.beginTransmission(MCP23017_ADDRESS); + Wire.write(0x12); // GPIOA + if (Wire.endTransmission(false) != 0) return 0; + + if (Wire.requestFrom(MCP23017_ADDRESS, (uint8_t)2) != 2) return 0; + uint8_t gpioA = Wire.read(); + uint8_t gpioB = Wire.read(); + return (uint16_t)gpioA | ((uint16_t)gpioB << 8); +} + +void SetupEthernet() +{ + uint8_t mac[] = { 0xA8, 0x61, 0x0A, 0x70, 0x00, 0x01 }; + Ethernet.init(W5500_CS_PIN); + Ethernet.begin(mac, ToIp(Cfg.ethIp), ToIp(Cfg.gateway), ToIp(Cfg.gateway), ToIp(Cfg.subnet)); + EthernetHardwareFound = (Ethernet.hardwareStatus() != EthernetNoHardware); + + if (!EthernetHardwareFound) + { + Serial.println("W5500 not found; Ethernet disabled."); + return; + } + + UdpEth.begin(LOCAL_UDP_PORT); + EthServer.begin(); + Serial.print("Ethernet IP: "); + Serial.println(Ethernet.localIP()); +} + +void SetupWifi() +{ + WiFi.mode(WIFI_AP_STA); + WiFi.disconnect(true); + delay(100); + + WiFi.softAPConfig(ToIp(Cfg.wifiIp), ToIp(Cfg.wifiIp), ToIp(Cfg.subnet)); + if (strlen(Cfg.apPassword) >= 8) WiFi.softAP(Cfg.apName, Cfg.apPassword); + else WiFi.softAP(Cfg.apName); + DnsServer.start(AP_DNS_PORT, "*", ToIp(Cfg.wifiIp)); + + if (Cfg.wifiStationEnabled && strlen(Cfg.ssid) > 0) + { + WiFi.config(ToIp(Cfg.wifiIp), ToIp(Cfg.gateway), ToIp(Cfg.subnet)); + WiFi.begin(Cfg.ssid, Cfg.password); + } + + UdpWifi.begin(LOCAL_UDP_PORT); + WifiServer.on("/", HandleWifiRoot); + WifiServer.on("/manual", HandleWifiManual); + WifiServer.on("/save", HTTP_GET, HandleWifiSave); + WifiServer.on("/status", HandleWifiStatus); + WifiServer.on("/buzzer-test", HandleWifiBuzzerTest); + WifiServer.begin(); + if (MDNS.begin(MDNS_NAME)) + { + MDNS.addService("http", "tcp", WEB_PORT); + Serial.print("mDNS: http://"); + Serial.print(MDNS_NAME); + Serial.println(".local/"); + } + + Serial.print("WiFi setup IP: "); + Serial.println(WiFi.softAPIP()); +} + +void PulseMasterOn() +{ + MasterOnPulseUntil = millis() + Cfg.pulseMs; + MasterOffPulseUntil = 0; +} + +void PulseMasterOff() +{ + MasterOffPulseUntil = millis() + Cfg.pulseMs; + MasterOnPulseUntil = 0; +} + +void SetMode(CabMode mode) +{ + if (Mode == mode) return; + + Mode = mode; + if (Mode == MODE_OFF) PulseMasterOff(); + else PulseMasterOn(); +} + +void ApplyInputEdges() +{ + for (uint8_t i = 0; i < 16; i++) + { + if (Cfg.inputAction[i] == ACT_MASTER_SWITCH && InputActive[i] != LastInputActive[i]) + { + if (InputActive[i]) PulseMasterOn(); + else PulseMasterOff(); + continue; + } + + if (!InputActive[i] || LastInputActive[i]) continue; + + switch (Cfg.inputAction[i]) + { + case ACT_AUTO_MODE: + SetMode(MODE_AUTO); + break; + case ACT_MANUAL_MODE: + SetMode(MODE_MANUAL); + break; + case ACT_OFF_MODE: + SetMode(MODE_OFF); + break; + case ACT_MASTER_ON: + PulseMasterOn(); + break; + case ACT_MASTER_OFF: + PulseMasterOff(); + break; + case ACT_RATE_UP: + RateUpPulseUntil = millis() + Cfg.pulseMs; + RateDownPulseUntil = 0; + break; + case ACT_RATE_DOWN: + RateDownPulseUntil = millis() + Cfg.pulseMs; + RateUpPulseUntil = 0; + break; + } + } + + memcpy(LastInputActive, InputActive, sizeof(InputActive)); +} + +bool AnyActionActive(uint8_t action) +{ + for (uint8_t i = 0; i < 16; i++) + { + if (Cfg.inputAction[i] == action && InputActive[i]) return true; + } + return false; +} + +void ReadInputs() +{ + uint16_t raw = ReadMcpInputs(); + RawMcpInputs = raw; + for (uint8_t i = 0; i < 16; i++) + { + bool high = ((raw & (1 << i)) != 0); + InputActive[i] = !high; + } + ApplyInputEdges(); +} + +void BuildSwitchboxPacket(byte data[6]) +{ + uint32_t now = millis(); + byte status = 0; + byte sectionsLo = 0; + byte sectionsHi = 0; + + if (now < MasterOnPulseUntil) status |= 0b00000010; + if (now < MasterOffPulseUntil) status |= 0b00000100; + if (now < RateUpPulseUntil || AnyActionActive(ACT_RATE_UP)) status |= 0b00001000; + if (now < RateDownPulseUntil || AnyActionActive(ACT_RATE_DOWN)) status |= 0b00010000; + + if (AnyActionActive(ACT_RATE_2)) status |= 0b00000001; + if (Mode == MODE_AUTO || !AnyActionActive(ACT_AUTO_SECTION)) status |= 0b00100000; + if (Mode == MODE_AUTO || !AnyActionActive(ACT_AUTO_RATE)) status |= 0b01000000; + if (AnyActionActive(ACT_WORK_SWITCH)) status |= 0b10000000; + + for (uint8_t i = 0; i < 16; i++) + { + uint8_t action = Cfg.inputAction[i]; + if (action >= ACT_SECTION_1 && action <= ACT_SECTION_16 && (Cfg.overrideSectionSwitchesOn || InputActive[i])) + { + uint8_t section = action - ACT_SECTION_1; + if (section < 8) sectionsLo |= 1 << section; + else sectionsHi |= 1 << (section - 8); + } + } + + data[0] = 106; + data[1] = 127; + data[2] = status; + data[3] = sectionsLo; + data[4] = sectionsHi; + data[5] = Crc(data, 5); +} + +void SendSwitchboxPacket() +{ + if (millis() - LastSendMs < Cfg.sendPeriodMs) return; + LastSendMs = millis(); + + byte data[6]; + BuildSwitchboxPacket(data); + + if (EthernetHardwareFound && Ethernet.linkStatus() == LinkON) + { + UdpEth.beginPacket(BroadcastFor(Cfg.ethIp), RATE_CONTROLLER_PORT); + UdpEth.write(data, sizeof(data)); + UdpEth.endPacket(); + } + + UdpWifi.beginPacket(BroadcastFor(Cfg.wifiIp), RATE_CONTROLLER_PORT); + UdpWifi.write(data, sizeof(data)); + UdpWifi.endPacket(); +} + +void SetupWeb() +{ +} + +String IpFields(const char* name, const uint8_t ip[4]) +{ + String s; + for (uint8_t i = 0; i < 4; i++) + { + s += ""; + } + return s; +} + +String ActionOptions(uint8_t selected) +{ + const uint8_t actions[] = { + ACT_DISABLED, ACT_MASTER_ON, ACT_MASTER_OFF, + ACT_RATE_UP, ACT_RATE_DOWN, ACT_WORK_SWITCH, ACT_AUTO_SECTION, ACT_AUTO_RATE, ACT_RATE_2, + ACT_MASTER_SWITCH, + ACT_SECTION_1, ACT_SECTION_2, ACT_SECTION_3, ACT_SECTION_4, ACT_SECTION_5, ACT_SECTION_6, + ACT_SECTION_7, ACT_SECTION_8, ACT_SECTION_9, ACT_SECTION_10, ACT_SECTION_11, ACT_SECTION_12, + ACT_SECTION_13, ACT_SECTION_14, ACT_SECTION_15, ACT_SECTION_16 + }; + + String s; + for (uint8_t i = 0; i < sizeof(actions); i++) + { + uint8_t action = actions[i]; + s += "