firmware: migrate cargo controller to LilyGO T-Relay S3
This commit is contained in:
@@ -239,3 +239,30 @@ Important guardrails:
|
|||||||
## M9N GPS Module
|
## M9N GPS Module
|
||||||
|
|
||||||
The dashboard enclosure will include an M9N GPS module connected to the ESP32-S3 dashboard over UART. It will provide GPS fix status, satellite count, position, speed, and UTC time. Future work will use GPS time/location for automatic timezone and DST handling.
|
The dashboard enclosure will include an M9N GPS module connected to the ESP32-S3 dashboard over UART. It will provide GPS fix status, satellite count, position, speed, and UTC time. Future work will use GPS time/location for automatic timezone and DST handling.
|
||||||
|
|
||||||
|
|
||||||
|
### LilyGO T-Relay-S3 6-relay cargo controller
|
||||||
|
|
||||||
|
The cargo ESP now targets the LilyGO T-Relay-S3 6-relay board.
|
||||||
|
|
||||||
|
Relay control is handled through the onboard 74HC595 shift register, not direct ESP32 GPIO relay outputs.
|
||||||
|
|
||||||
|
| Function | GPIO |
|
||||||
|
| --- | --- |
|
||||||
|
| 74HC595 DATA | GPIO7 |
|
||||||
|
| 74HC595 CLOCK | GPIO5 |
|
||||||
|
| 74HC595 LATCH | GPIO6 |
|
||||||
|
| 74HC595 nOE | GPIO4 |
|
||||||
|
|
||||||
|
| Firmware ID | LilyGO relay channel | Shift-register output |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| relay_1 | CH1 | SR1 |
|
||||||
|
| relay_2 | CH2 | SR2 |
|
||||||
|
| relay_3 | CH3 | SR3 |
|
||||||
|
| relay_4 | CH4 | SR4 |
|
||||||
|
| relay_5 | CH5 | SR5 |
|
||||||
|
| relay_6 | CH6 | SR6 |
|
||||||
|
|
||||||
|
GPIO4 is reserved for relay output-enable on this board, so the DS18B20 OneWire bus is moved to GPIO14.
|
||||||
|
|
||||||
|
Avoid GPIO35-GPIO37 on the LilyGO T-Relay-S3 because they are used by Octal SPI flash.
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ Expected output object fields:
|
|||||||
Example 2-channel profile:
|
Example 2-channel profile:
|
||||||
|
|
||||||
{
|
{
|
||||||
"hardware_profile": "generic_esp32_2ch_relay",
|
"hardware_profile": "lilygo_t_relay_s3_6ch",
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{"id": 1, "name": "Fridge", "role": "fridge", "hardware_channel": 1},
|
{"id": 1, "name": "Fridge", "role": "fridge", "hardware_channel": 1},
|
||||||
{"id": 2, "name": "Starlink", "role": "starlink", "hardware_channel": 2}
|
{"id": 2, "name": "Starlink", "role": "starlink", "hardware_channel": 2}
|
||||||
@@ -190,7 +190,7 @@ The Cargo ESP firmware now exposes relay/output state as an output-count-agnosti
|
|||||||
|
|
||||||
The current active hardware profile remains:
|
The current active hardware profile remains:
|
||||||
|
|
||||||
generic_esp32_2ch_relay
|
lilygo_t_relay_s3_6ch
|
||||||
|
|
||||||
The API includes:
|
The API includes:
|
||||||
|
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ The Cargo ESP API now represents relays/outputs as a hardware-profile-aware list
|
|||||||
|
|
||||||
Current active hardware profile:
|
Current active hardware profile:
|
||||||
|
|
||||||
generic_esp32_2ch_relay
|
lilygo_t_relay_s3_6ch
|
||||||
|
|
||||||
Future planned profile:
|
Future planned profile:
|
||||||
|
|
||||||
|
|||||||
@@ -213,3 +213,13 @@ Rationale:
|
|||||||
- Improves perceived responsiveness without a major communication rewrite.
|
- Improves perceived responsiveness without a major communication rewrite.
|
||||||
- Supports future live gauges better than fixed-interval polling.
|
- Supports future live gauges better than fixed-interval polling.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## LilyGO T-Relay-S3 6-relay migration
|
||||||
|
|
||||||
|
- [x] Expand cargo relay model from 2 outputs to 6 outputs.
|
||||||
|
- [x] Replace direct GPIO relay writes with LilyGO 74HC595 shift-register writes.
|
||||||
|
- [x] Map relay_1 through relay_6 to CH1 through CH6.
|
||||||
|
- [x] Move DS18B20 OneWire default from GPIO4 to GPIO14 because GPIO4 is relay nOE.
|
||||||
|
- [ ] Bench test relay boot behavior with no vehicle loads connected.
|
||||||
|
- [ ] Confirm CH1-CH6 order with a meter or test lamp before wiring vehicle loads.
|
||||||
|
|||||||
@@ -7,68 +7,27 @@ AppConfig appConfig;
|
|||||||
|
|
||||||
static Preferences preferences;
|
static Preferences preferences;
|
||||||
|
|
||||||
|
static RelayConfig defaultRelayConfig(uint8_t channel, const char *name, const char *role) {
|
||||||
|
return {
|
||||||
|
"relay_" + String(channel),
|
||||||
|
name,
|
||||||
|
role,
|
||||||
|
channel,
|
||||||
|
RELAY_SHIFT_REGISTER_PIN,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
void loadDefaultConfig() {
|
void loadDefaultConfig() {
|
||||||
appConfig.deviceName = "Overland Controller";
|
appConfig.deviceName = "Overland Controller";
|
||||||
|
|
||||||
appConfig.relays[0] = {
|
appConfig.relays[0] = defaultRelayConfig(1, "Relay 1", "aux");
|
||||||
"relay_1",
|
appConfig.relays[1] = defaultRelayConfig(2, "Relay 2", "fridge");
|
||||||
"Relay 1",
|
appConfig.relays[2] = defaultRelayConfig(3, "Relay 3", "starlink");
|
||||||
"aux",
|
appConfig.relays[3] = defaultRelayConfig(4, "Relay 4", "aux");
|
||||||
1,
|
appConfig.relays[4] = defaultRelayConfig(5, "Relay 5", "aux");
|
||||||
RELAY_1_PIN,
|
appConfig.relays[5] = defaultRelayConfig(6, "Relay 6", "aux");
|
||||||
true,
|
|
||||||
true
|
|
||||||
};
|
|
||||||
|
|
||||||
appConfig.relays[1] = {
|
|
||||||
"relay_2",
|
|
||||||
"Relay 2",
|
|
||||||
"fridge",
|
|
||||||
2,
|
|
||||||
RELAY_2_PIN,
|
|
||||||
true,
|
|
||||||
true
|
|
||||||
};
|
|
||||||
|
|
||||||
appConfig.relays[2] = {
|
|
||||||
"relay_3",
|
|
||||||
"Relay 3",
|
|
||||||
"starlink",
|
|
||||||
3,
|
|
||||||
0,
|
|
||||||
true,
|
|
||||||
true
|
|
||||||
};
|
|
||||||
|
|
||||||
appConfig.relays[3] = {
|
|
||||||
"relay_4",
|
|
||||||
"Relay 4",
|
|
||||||
"aux",
|
|
||||||
4,
|
|
||||||
0,
|
|
||||||
true,
|
|
||||||
true
|
|
||||||
};
|
|
||||||
|
|
||||||
appConfig.relays[4] = {
|
|
||||||
"relay_5",
|
|
||||||
"Relay 5",
|
|
||||||
"aux",
|
|
||||||
5,
|
|
||||||
0,
|
|
||||||
true,
|
|
||||||
true
|
|
||||||
};
|
|
||||||
|
|
||||||
appConfig.relays[5] = {
|
|
||||||
"relay_6",
|
|
||||||
"Relay 6",
|
|
||||||
"aux",
|
|
||||||
6,
|
|
||||||
0,
|
|
||||||
true,
|
|
||||||
true
|
|
||||||
};
|
|
||||||
|
|
||||||
appConfig.tempSensorCount = 4;
|
appConfig.tempSensorCount = 4;
|
||||||
|
|
||||||
@@ -234,7 +193,7 @@ void printConfig() {
|
|||||||
Serial.print(appConfig.relays[i].role);
|
Serial.print(appConfig.relays[i].role);
|
||||||
Serial.print(" / channel ");
|
Serial.print(" / channel ");
|
||||||
Serial.print(appConfig.relays[i].hardwareChannel);
|
Serial.print(appConfig.relays[i].hardwareChannel);
|
||||||
Serial.print(" / GPIO ");
|
Serial.print(" / channel ");
|
||||||
Serial.print(appConfig.relays[i].pin);
|
Serial.print(appConfig.relays[i].pin);
|
||||||
Serial.print(" / available ");
|
Serial.print(" / available ");
|
||||||
Serial.print(appConfig.relays[i].available ? "true" : "false");
|
Serial.print(appConfig.relays[i].available ? "true" : "false");
|
||||||
|
|||||||
@@ -2,24 +2,25 @@
|
|||||||
|
|
||||||
#define DEVICE_NAME "Overland Controller"
|
#define DEVICE_NAME "Overland Controller"
|
||||||
|
|
||||||
// Relay Outputs
|
// LilyGO T-Relay S3 6-relay board.
|
||||||
#define RELAY_1_PIN 16
|
// Relays are driven through onboard 74HC595 shift register.
|
||||||
#define RELAY_2_PIN 17
|
#define LILYGO_T_RELAY_S3_DATA_PIN 7
|
||||||
|
#define LILYGO_T_RELAY_S3_CLOCK_PIN 5
|
||||||
|
#define LILYGO_T_RELAY_S3_LATCH_PIN 6
|
||||||
|
#define LILYGO_T_RELAY_S3_OE_PIN 4
|
||||||
|
#define LILYGO_T_RELAY_S3_RELAY_COUNT 6
|
||||||
|
|
||||||
// DS18B20 Bus
|
// Compatibility placeholder only. Relays are not direct GPIO outputs.
|
||||||
#define ONEWIRE_PIN 4
|
#define RELAY_SHIFT_REGISTER_PIN 255
|
||||||
|
|
||||||
|
// GPIO4 is used by the LilyGO relay shift-register OE pin.
|
||||||
|
#define ONEWIRE_PIN 14
|
||||||
|
|
||||||
// Ignition Sense
|
|
||||||
#define IGNITION_PIN 34
|
#define IGNITION_PIN 34
|
||||||
|
|
||||||
// Cargo OLED service/setup display
|
|
||||||
#define OLED_I2C_SDA_PIN 21
|
#define OLED_I2C_SDA_PIN 21
|
||||||
#define OLED_I2C_SCL_PIN 22
|
#define OLED_I2C_SCL_PIN 22
|
||||||
#define OLED_SETUP_BUTTON_PIN 25
|
#define OLED_SETUP_BUTTON_PIN 25
|
||||||
|
|
||||||
// Dashboard communication is WiFi/HTTP.
|
|
||||||
// Legacy UART dashboard support is retired from active hardware use.
|
|
||||||
#define DASHBOARD_UART_ENABLED 0
|
#define DASHBOARD_UART_ENABLED 0
|
||||||
#define DASHBOARD_UART_BAUD 115200
|
#define DASHBOARD_UART_BAUD 115200
|
||||||
|
|
||||||
// RS-485/MAX3485 is fallback only and not currently planned.
|
|
||||||
|
|||||||
@@ -5,20 +5,55 @@
|
|||||||
|
|
||||||
RelayState relays;
|
RelayState relays;
|
||||||
|
|
||||||
|
static uint8_t relayShiftRegisterState = 0;
|
||||||
|
|
||||||
|
static void writeRelayShiftRegister()
|
||||||
|
{
|
||||||
|
digitalWrite(LILYGO_T_RELAY_S3_LATCH_PIN, LOW);
|
||||||
|
shiftOut(
|
||||||
|
LILYGO_T_RELAY_S3_DATA_PIN,
|
||||||
|
LILYGO_T_RELAY_S3_CLOCK_PIN,
|
||||||
|
MSBFIRST,
|
||||||
|
relayShiftRegisterState
|
||||||
|
);
|
||||||
|
digitalWrite(LILYGO_T_RELAY_S3_LATCH_PIN, HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
void initRelays() {
|
void initRelays() {
|
||||||
for (int i = 0; i < MAX_RELAYS; i++) {
|
pinMode(LILYGO_T_RELAY_S3_DATA_PIN, OUTPUT);
|
||||||
if (appConfig.relays[i].available) {
|
pinMode(LILYGO_T_RELAY_S3_CLOCK_PIN, OUTPUT);
|
||||||
pinMode(appConfig.relays[i].pin, OUTPUT);
|
pinMode(LILYGO_T_RELAY_S3_LATCH_PIN, OUTPUT);
|
||||||
}
|
pinMode(LILYGO_T_RELAY_S3_OE_PIN, OUTPUT);
|
||||||
}
|
|
||||||
|
// Keep outputs disabled while clearing the shift register.
|
||||||
|
digitalWrite(LILYGO_T_RELAY_S3_OE_PIN, HIGH);
|
||||||
|
relayShiftRegisterState = 0;
|
||||||
|
writeRelayShiftRegister();
|
||||||
|
|
||||||
|
// Enable relay outputs after known-off state is written.
|
||||||
|
digitalWrite(LILYGO_T_RELAY_S3_OE_PIN, LOW);
|
||||||
|
|
||||||
updateRelayOutputs();
|
updateRelayOutputs();
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateRelayOutputs() {
|
void updateRelayOutputs() {
|
||||||
|
uint8_t nextState = 0;
|
||||||
|
|
||||||
for (int i = 0; i < MAX_RELAYS; i++) {
|
for (int i = 0; i < MAX_RELAYS; i++) {
|
||||||
if (appConfig.relays[i].available) {
|
if (!appConfig.relays[i].available) {
|
||||||
digitalWrite(appConfig.relays[i].pin, relays.state[i]);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t channel = appConfig.relays[i].hardwareChannel;
|
||||||
|
if (channel < 1 || channel > LILYGO_T_RELAY_S3_RELAY_COUNT) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relays.state[i]) {
|
||||||
|
nextState |= (1U << (channel - 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
relayShiftRegisterState = nextState;
|
||||||
|
writeRelayShiftRegister();
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -8,7 +8,7 @@
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
"role": "aux",
|
"role": "aux",
|
||||||
"hardware_channel": 1,
|
"hardware_channel": 1,
|
||||||
"hardware_profile": "generic_esp32_2ch_relay",
|
"hardware_profile": "lilygo_t_relay_s3_6ch",
|
||||||
"available": true
|
"available": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
"role": "fridge",
|
"role": "fridge",
|
||||||
"hardware_channel": 2,
|
"hardware_channel": 2,
|
||||||
"hardware_profile": "generic_esp32_2ch_relay",
|
"hardware_profile": "lilygo_t_relay_s3_6ch",
|
||||||
"available": true
|
"available": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -38,6 +38,6 @@
|
|||||||
"weather": false
|
"weather": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hardware_profile": "generic_esp32_2ch_relay",
|
"hardware_profile": "lilygo_t_relay_s3_6ch",
|
||||||
"output_count": 2
|
"output_count": 6
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -44,7 +44,7 @@
|
|||||||
"state": false,
|
"state": false,
|
||||||
"role": "aux",
|
"role": "aux",
|
||||||
"hardware_channel": 1,
|
"hardware_channel": 1,
|
||||||
"hardware_profile": "generic_esp32_2ch_relay",
|
"hardware_profile": "lilygo_t_relay_s3_6ch",
|
||||||
"available": true
|
"available": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
"state": true,
|
"state": true,
|
||||||
"role": "fridge",
|
"role": "fridge",
|
||||||
"hardware_channel": 2,
|
"hardware_channel": 2,
|
||||||
"hardware_profile": "generic_esp32_2ch_relay",
|
"hardware_profile": "lilygo_t_relay_s3_6ch",
|
||||||
"available": true
|
"available": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
"role": "aux",
|
"role": "aux",
|
||||||
"hardware_channel": 1,
|
"hardware_channel": 1,
|
||||||
"hardware_profile": "generic_esp32_2ch_relay",
|
"hardware_profile": "lilygo_t_relay_s3_6ch",
|
||||||
"available": true
|
"available": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -126,9 +126,9 @@
|
|||||||
"weather": false
|
"weather": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hardware_profile": "generic_esp32_2ch_relay",
|
"hardware_profile": "lilygo_t_relay_s3_6ch",
|
||||||
"output_count": 2
|
"output_count": 2
|
||||||
},
|
},
|
||||||
"hardware_profile": "generic_esp32_2ch_relay",
|
"hardware_profile": "lilygo_t_relay_s3_6ch",
|
||||||
"output_count": 2
|
"output_count": 6
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,8 +134,8 @@ def test_status_fixture_matches_dashboard_contract_shape():
|
|||||||
"online",
|
"online",
|
||||||
"temperature_f",
|
"temperature_f",
|
||||||
])
|
])
|
||||||
assert payload["hardware_profile"] == "generic_esp32_2ch_relay"
|
assert payload["hardware_profile"] == "lilygo_t_relay_s3_6ch"
|
||||||
assert payload["output_count"] == 2
|
assert payload["output_count"] == 6
|
||||||
assert_keys(payload["relays"][0], [
|
assert_keys(payload["relays"][0], [
|
||||||
"id",
|
"id",
|
||||||
"name",
|
"name",
|
||||||
@@ -180,8 +180,8 @@ def test_config_fixture_matches_current_config_response_shape():
|
|||||||
"temperature_sensor_count",
|
"temperature_sensor_count",
|
||||||
"temperature_sensors",
|
"temperature_sensors",
|
||||||
])
|
])
|
||||||
assert payload["hardware_profile"] == "generic_esp32_2ch_relay"
|
assert payload["hardware_profile"] == "lilygo_t_relay_s3_6ch"
|
||||||
assert payload["output_count"] == 2
|
assert payload["output_count"] == 6
|
||||||
assert_keys(payload["relays"][0], [
|
assert_keys(payload["relays"][0], [
|
||||||
"id",
|
"id",
|
||||||
"name",
|
"name",
|
||||||
|
|||||||
Reference in New Issue
Block a user