OK, so I have a solution that kind of works to control the A/C compressor inverter. I took the messages from the TIS run and the Arduino program and converted it to esp32 that have a Wi-Fi module and made an interface similar to TIS. Roughly mapped messages with RPM (still haven't fully decoded the content in the messages). I will park this development for now. If someone want to pick up the thread, I'll be more than happy to help with development boards etc. The most important objective for me has been achieved that, that the inverter is active and can be turned off, thus hopefully making sure the gates are permanently low to be able to use the inverter for charging without modifications.
Code: Select all
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
// Pin Definitions
const int clockPin = 18; // SPI Clock Pin (active low)
const int dataInPin = 19; // Data Pin for receiving data
const int dataOutPin = 23; // Data Pin for sending response
const int chipSelectPin = 5; // Chip Select Pin
const int packetSize = 10; // Number of bytes in a packet
// Timing Definitions
const unsigned long clockCycleTime = 3200; // Clock cycle time in microseconds (3.2ms)
const unsigned long byteReceiveTime = 26000; // Time to receive one byte (26ms)
const unsigned long packetGapTime = 80000; // Time between packets (80ms)
// Fixed Response Data
byte responseData[packetSize] = {0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00};
int compressorTargetSpeed = 0; // Default target speed
// Wi-Fi Access Point Credentials
const char* ssid = "CompressorControl";
const char* password = "12345678";
// Web Server
AsyncWebServer server(80);
// Variables
byte receivedData[packetSize];
unsigned long lastPacketTime = 0;
bool receivingPacket = false;
void processReceivedData() {
// Modify response based on compressor target speed
if (compressorTargetSpeed == 100) {
byte defaultResponse[packetSize] = {0xFB, 0x3F, 0xFE, 0xFF, 0xC5, 0xFB, 0x3F, 0xFE, 0xFF, 0xC5};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 200) {
byte defaultResponse[packetSize] = {0xDE, 0x3F, 0xFE, 0xFF, 0xE0, 0xDE, 0x3F, 0xFE, 0xFF, 0xE0};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 300) {
byte defaultResponse[packetSize] = {0xF2, 0xDF, 0xFE, 0xFF, 0x2C, 0xF2, 0xDF, 0xFE, 0xFF, 0x2C};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 400) {
byte defaultResponse[packetSize] = {0xE1, 0x5F, 0xFE, 0xFF, 0xBF, 0xE1, 0x5F, 0xFE, 0xFF, 0xBF};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 500) {
byte defaultResponse[packetSize] = {0xD7, 0x5F, 0xFE, 0xFF, 0x99, 0xD7, 0x5F, 0xFE, 0xFF, 0x99};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 600) {
byte defaultResponse[packetSize] = {0xE1, 0x5F, 0xFE, 0xFF, 0xBF, 0xE1, 0x5F, 0xFE, 0xFF, 0xBF};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 700) {
byte defaultResponse[packetSize] = {0xC4, 0x5F, 0xFE, 0xFF, 0x86, 0xC4, 0x5F, 0xFE, 0xFF, 0x86};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 800) {
byte defaultResponse[packetSize] = {0xFD, 0x9F, 0xFE, 0xFF, 0x63, 0xFD, 0x9F, 0xFE, 0xFF, 0x63};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 900) {
byte defaultResponse[packetSize] = {0xEF, 0x1F, 0xFE, 0xFF, 0xF1, 0xEF, 0x1F, 0xFE, 0xFF, 0xF1};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 1000) {
byte defaultResponse[packetSize] = {0xC9, 0x1F, 0xFE, 0xFF, 0xCF, 0xC9, 0x1F, 0xFE, 0xFF, 0xCF};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 1100) {
byte defaultResponse[packetSize] = {0xD3, 0xEF, 0xFE, 0xFF, 0x3D, 0xD3, 0xEF, 0xFE, 0xFF, 0x3D};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 1200) {
byte defaultResponse[packetSize] = {0xE6, 0xEF, 0xFE, 0xFF, 0x04, 0xE6, 0xEF, 0xFE, 0xFF, 0x04};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 1300) {
byte defaultResponse[packetSize] = {0xF9, 0x6F, 0xFE, 0xFF, 0x97, 0xF9, 0x6F, 0xFE, 0xFF, 0x97};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 1400) {
byte defaultResponse[packetSize] = {0xDC, 0x6F, 0xFE, 0xFF, 0xB2, 0xDC, 0x6F, 0xFE, 0xFF, 0xB2};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 1500) {
byte defaultResponse[packetSize] = {0xF0, 0xAF, 0xFE, 0xFF, 0x5E, 0xF0, 0xAF, 0xFE, 0xFF, 0x5E};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 1600) {
byte defaultResponse[packetSize] = {0xD5, 0x2F, 0xFE, 0xFF, 0xFB, 0xD5, 0x2F, 0xFE, 0xFF, 0xFB};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 1700) {
byte defaultResponse[packetSize] = {0xC7, 0xCF, 0xFE, 0xFF, 0x15, 0xC7, 0xCF, 0xFE, 0xFF, 0x15};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 1800) {
byte defaultResponse[packetSize] = {0xFE, 0xCF, 0xFE, 0xFF, 0x30, 0xFE, 0xCF, 0xFE, 0xFF, 0x30};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 1900) {
byte defaultResponse[packetSize] = {0xCA, 0x4F, 0xFE, 0xFF, 0x9C, 0xCA, 0x4F, 0xFE, 0xFF, 0x9C};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 2000) {
byte defaultResponse[packetSize] = {0xF7, 0x8F, 0xFE, 0xFF, 0x79, 0xF7, 0x8F, 0xFE, 0xFF, 0x79};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 2100) {
byte defaultResponse[packetSize] = {0xE4, 0x8F, 0xFE, 0xFF, 0x66, 0xE4, 0x8F, 0xFE, 0xFF, 0x66};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 2200) {
byte defaultResponse[packetSize] = {0xC3, 0x0F, 0xFE, 0xFF, 0xD3, 0xC3, 0x0F, 0xFE, 0xFF, 0xD3};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 2300) {
byte defaultResponse[packetSize] = {0xFA, 0x0F, 0xFE, 0xFF, 0xF4, 0xFA, 0x0F, 0xFE, 0xFF, 0xF4};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 2400) {
byte defaultResponse[packetSize] = {0xE4, 0x8F, 0xFE, 0xFF, 0x66, 0xE4, 0x8F, 0xFE, 0xFF, 0x66};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 2500) {
byte defaultResponse[packetSize] = {0xF7, 0x8F, 0xFE, 0xFF, 0x79, 0xF7, 0x8F, 0xFE, 0xFF, 0x79};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 2600) {
byte defaultResponse[packetSize] = {0xED, 0x4F, 0xFE, 0xFF, 0xAB, 0xED, 0x4F, 0xFE, 0xFF, 0xAB};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 2700) {
byte defaultResponse[packetSize] = {0xD8, 0xCF, 0xFE, 0xFF, 0x0E, 0xD8, 0xCF, 0xFE, 0xFF, 0x0E};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 2800) {
byte defaultResponse[packetSize] = {0xFE, 0xCF, 0xFE, 0xFF, 0x30, 0xFE, 0xCF, 0xFE, 0xFF, 0x30};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 2900) {
byte defaultResponse[packetSize] = {0xE2, 0x2F, 0xFE, 0xFF, 0xC2, 0xE2, 0x2F, 0xFE, 0xFF, 0xC2};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 3000) {
byte defaultResponse[packetSize] = {0xCE, 0xAF, 0xFE, 0xFF, 0x68, 0xCE, 0xAF, 0xFE, 0xFF, 0x68};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 3100) {
byte defaultResponse[packetSize] = {0xDC, 0x6F, 0xFE, 0xFF, 0xB2, 0xDC, 0x6F, 0xFE, 0xFF, 0xB2};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 3200) {
byte defaultResponse[packetSize] = {0xF9, 0x6F, 0xFE, 0xFF, 0x97, 0xF9, 0x6F, 0xFE, 0xFF, 0x97};
memcpy(responseData, defaultResponse, packetSize);
}
else if (compressorTargetSpeed == 3300) {
byte defaultResponse[packetSize] = {0xE6, 0xEF, 0xFE, 0xFF, 0x04, 0xE6, 0xEF, 0xFE, 0xFF, 0x04};
memcpy(responseData, defaultResponse, packetSize);
}
else {
byte defaultResponse[packetSize] = {0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00};
memcpy(responseData, defaultResponse, packetSize);
}
}
void receiveAndRespondPacket() {
unsigned long startTime = micros();
for (int byteIndex = 0; byteIndex < packetSize; byteIndex++) {
byte receivedByte = 0;
for (int bitIndex = 0; bitIndex < 8; bitIndex++) {
while (digitalRead(clockPin) == LOW) { delayMicroseconds(1); }
bool responseBit = (responseData[byteIndex] >> (7 - bitIndex)) & 1;
digitalWrite(dataOutPin, responseBit ? LOW : HIGH);
delayMicroseconds(clockCycleTime / 2);
receivedByte <<= 1;
if (digitalRead(dataInPin) == HIGH) receivedByte |= 1;
while (digitalRead(clockPin) == HIGH) { delayMicroseconds(1); }
}
receivedData[byteIndex] = receivedByte;
while (micros() - startTime < byteReceiveTime * (byteIndex + 1)) { delayMicroseconds(1); }
if (byteIndex < (packetSize - 1)) digitalWrite(dataOutPin, LOW);
}
receivingPacket = false;
lastPacketTime = micros();
Serial.print("Received: ");
for (int i = 0; i < packetSize; i++) {
Serial.printf("%02X ", receivedData[i]);
}
Serial.println();
}
void updateSpeed(int change) {
compressorTargetSpeed += change;
if (compressorTargetSpeed < 0) compressorTargetSpeed = 0;
if (compressorTargetSpeed > 7500) compressorTargetSpeed = 7500;
processReceivedData();
}
void setup() {
Serial.begin(115200);
pinMode(clockPin, INPUT);
pinMode(dataInPin, INPUT);
pinMode(dataOutPin, OUTPUT);
pinMode(chipSelectPin, OUTPUT);
digitalWrite(dataOutPin, LOW);
digitalWrite(chipSelectPin, HIGH);
WiFi.softAP(ssid, password);
Serial.printf("Wi-Fi started. Connect to: %s\n", WiFi.softAPIP().toString().c_str());
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
String html = "<html><head><title>Compressor Control</title>";
html += "<meta name='viewport' content='width=device-width, initial-scale=1'>";
html += "<style>body { font-family: Arial; text-align: center; } ";
html += "button { font-size: 20px; padding: 10px 20px; margin: 10px; } ";
html += "</style></head><body>";
html += "<h2>Compressor Target Speed</h2>";
html += "<h1 id='speed'>" + String(compressorTargetSpeed) + " RPM</h1>";
html += "<a href='/decrease'><button>< Decrease</button></a>";
html += "<a href='/increase'><button>Increase ></button></a>";
html += "<script> setInterval(() => { fetch('/speed').then(res => res.text()).then(data => document.getElementById('speed').innerText = data + ' RPM'); }, 1000); </script>";
html += "</body></html>";
request->send(200, "text/html", html);
});
server.on("/increase", HTTP_GET, [](AsyncWebServerRequest *request) {
updateSpeed(100);
request->redirect("/");
});
server.on("/decrease", HTTP_GET, [](AsyncWebServerRequest *request) {
updateSpeed(-100);
request->redirect("/");
});
server.on("/speed", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", String(compressorTargetSpeed));
});
server.begin();
}
void loop() {
unsigned long currentTime = micros();
if (!receivingPacket && (currentTime - lastPacketTime >= packetGapTime) && digitalRead(clockPin) == HIGH) {
receivingPacket = true;
lastPacketTime = currentTime;
receiveAndRespondPacket();
}
}