Plug and play Hybrid Vehicle Control ECU man in the middle

Discussion in 'Gen 2 Prius Accessories & Modifications' started by turnip73, Jul 17, 2022.

  1. turnip73

    turnip73 Junior Member

    Joined:
    Jun 25, 2022
    11
    6
    0
    Location:
    Ireland
    Vehicle:
    2007 Prius
    Model:
    N/A

    I've designed a board that fits in the original Hybrid Vehicle Control ECU case and create a man in the middle setup for connecting for example the openinverter controller.

    I have boards available if someone want one.

    20220706_132316.jpg 20211127_213616.jpg
     
    SFO likes this.
  2. SFO

    SFO Senior Member

    Joined:
    Feb 7, 2017
    5,302
    4,246
    0
    Location:
    Northern California
    Vehicle:
    2007 Prius
    Model:
    N/A
    Welcome to PriusChat!!
    Impressive first post. What are you currently using your installed board for?
    What are some of the other things that people could use this board for?

    How much does a finished (populated) board cost, and are there DIY kits available?

    FYI : you're moderated until you've posted 5 times.
     
  3. turnip73

    turnip73 Junior Member

    Joined:
    Jun 25, 2022
    11
    6
    0
    Location:
    Ireland
    Vehicle:
    2007 Prius
    Model:
    N/A
    Thanks SFO!

    Running with an openinverter controller

    Run custom inverter controller, trouble shooting, training.

    Board design is available open source so you can print some your selves on jlcpcb or similar search for PriusHVConnector in github for gerber and bom files.
    wiki has details on what other parts need to build one.
    Please note that there are a few issues on the current board design (see issues in in github)

    To follow my progress with the openinverter controller search for "Toyota Prius gen2 plug and play" in openinverter forum and "Toyota prius gen2 with openinverter in manual mode" on youtube.
     
  4. turnip73

    turnip73 Junior Member

    Joined:
    Jun 25, 2022
    11
    6
    0
    Location:
    Ireland
    Vehicle:
    2007 Prius
    Model:
    N/A
    I'm trying to run the OEM Hybrid Vehicle Control ECU together with the openinverter controller but the OEM ECU opens the contactors when I start the openinverter controller (Initial test worked when only MUU, MVU, MWU (GUU, GVU and GWU when disconnected from the OEM ECU).
    Currently I have:
    • MG1 and MG2 disconnected from OEM ECU (MUU, MVU, MWU, GUU, GVU and GWU) and Engine Control Module disconnected
    • Encoder from MG2 to connectedto openinverter (MRF,MRFG,FMCS,MCSG,MSN and MSNG).
    • MCS,MCSG,MSN,MSNG from OEM Hybrid Vehicle Control ECU connected to GCS,GCSG,GSN,GSNG on the inverter (MRF,MRFG left disconnected)
    • GVIA, GVIB, MVIA, MVIB, GWIA, GWIB, MWIA, MWIB from OEM ECU connected to GVIB and GWIB on the inverter
      (I don't see any "power transfer" on the screen any more also no voltage on GVIB and GWIB from controller).
    • No codes other then P0A87 (MUU, MVU and MWU disconnected), P0A7A (GUU, GVU and GWU disconnected) and U0100 (Engine ECU disconnected).
    Anyone have any idea what might trigger the OEM Hybrid Vehicle Control ECU to open the contactors, what code to look out for on the CAN interface to trouble shoot or anything else I need to emulate to keep the OEM Hybrid Vehicle Control ECU happy?
     
  5. turnip73

    turnip73 Junior Member

    Joined:
    Jun 25, 2022
    11
    6
    0
    Location:
    Ireland
    Vehicle:
    2007 Prius
    Model:
    N/A
    Still haven't figured out issue above but it seems MFIV is tripping (reported by the openinverter controller).
    I now have access to Techstream to trouble shoot issue.
    Started work on setting up SOC spoofing etc following eaa-phev pages to keep the OEM ECU's happy (Search for Prius gen 2 SOC spoofing on youtube). I plan to use stm32-car or stm32-template for software and hardware.
    Anyone mind sharing how the forced EV mode/out of gas/forced stealth works? do you just shut off the fuel pump or is there some can messages involved?
     
  6. Another

    Another Senior Member

    Joined:
    Jun 22, 2021
    1,818
    515
    0
    Location:
    Naples, Florida
    Vehicle:
    2007 Prius
    Model:
    Touring
  7. turnip73

    turnip73 Junior Member

    Joined:
    Jun 25, 2022
    11
    6
    0
    Location:
    Ireland
    Vehicle:
    2007 Prius
    Model:
    N/A
  8. turnip73

    turnip73 Junior Member

    Joined:
    Jun 25, 2022
    11
    6
    0
    Location:
    Ireland
    Vehicle:
    2007 Prius
    Model:
    N/A
    SFO likes this.
  9. turnip73

    turnip73 Junior Member

    Joined:
    Jun 25, 2022
    11
    6
    0
    Location:
    Ireland
    Vehicle:
    2007 Prius
    Model:
    N/A
    Wrote a small Prius Battery ECU decoder using an ESP32 and a couple of 3.3V Can Boards before I get ahead of my self over sharing the Toyota hybrid batteries:

    // === ESP32 Prius CAN Parser ===
    // Monitors Prius Gen2 Battery ECU on 2 CAN buses

    #include <ACAN_ESP32.h>
    #include <mcp_can.h>
    #include <SPI.h>
    #include <WiFi.h>
    #include <ESPAsyncWebServer.h>

    AsyncWebServer server(80);

    // === Battery 1 (ESP32 CAN) ===
    float packVoltage1 = 0.0;
    float packCurrent1 = 0.0;
    int soc1 = 0;
    int ccl1 = 0, cdl1 = 0;
    int temp1a = 0, temp1b = 0;
    uint16_t faultCode1 = 0;
    uint8_t deltaSOC1 = 0, flags1 = 0;
    uint16_t calibX1 = 0, calibY1 = 0, calibZ1 = 0;
    float blockVoltages1[14] = {0};
    uint8_t isoTPBuf1[64];
    size_t isoTPLen1 = 0;

    // === Battery 2 (MCP2515 CAN) ===
    float packVoltage2 = 0.0;
    float packCurrent2 = 0.0;
    int soc2 = 0;
    int ccl2 = 0, cdl2 = 0;
    int temp2a = 0, temp2b = 0;
    uint16_t faultCode2 = 0;
    uint8_t deltaSOC2 = 0, flags2 = 0;
    uint16_t calibX2 = 0, calibY2 = 0, calibZ2 = 0;
    float blockVoltages2[14] = {0};
    uint8_t isoTPBuf2[64];
    size_t isoTPLen2 = 0;

    MCP_CAN can2(15);
    unsigned long lastTesterPing = 0;
    const unsigned long testerPingInterval = 3000; // 3 sec
    int pid = 1;

    void setup() {
    Serial.begin(115200);
    WiFi.softAP("PriusMonitor", "hybridpower");
    Serial.println(WiFi.softAPIP());

    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    String html = "<html><head><meta http-equiv='refresh' content='2'></head><body><h1>Prius BMS Data</h1>";
    html += "<h2>Battery 1</h2>";
    html += "<p>Voltage: " + String(packVoltage1) + " V</p>";
    html += "<p>Current: " + String(packCurrent1) + " A</p>";
    html += "<p>SOC: " + String(soc1/2.0) + " %</p>";
    html += "<p>Delta SOC: " + String(deltaSOC1/2.0) + " %</p>";
    html += "<p>CCL/CDL: " + String(ccl1) + "/" + String(cdl1) + " A</p>";
    html += "<p>Temps: " + String(temp1a) + ", " + String(temp1b) + "</p>";
    html += "<p>Flags: 0x" + String(flags1, HEX) + "</p>";
    html += "<p>Blocks:</p><ul>";
    for (int i=0; i<14; i++) html += "<li>Block "+String(i+1)+": "+String(blockVoltages1)+" V</li>";
    html += "</ul>";

    html += "<h2>Battery 2</h2>";
    html += "<p>Voltage: " + String(packVoltage2) + " V</p>";
    html += "<p>Current: " + String(packCurrent2) + " A</p>";
    html += "<p>SOC: " + String(soc2/2.0) + " %</p>";
    html += "<p>Delta SOC: " + String(deltaSOC2/2.0) + " %</p>";
    html += "<p>CCL/CDL: " + String(ccl2) + "/" + String(cdl2) + " A</p>";
    html += "<p>Temps: " + String(temp2a) + ", " + String(temp2b) + "</p>";
    html += "<p>Flags: 0x" + String(flags2, HEX) + "</p>";
    html += "<p>Blocks:</p><ul>";
    for (int i=0; i<14; i++) html += "<li>Block "+String(i+1)+": "+String(blockVoltages2)+" V</li>";
    html += "</ul>";
    html += "</body></html>";
    request->send(200, "text/html", html);
    });
    server.begin();

    ACAN_ESP32_Settings settings1(500000);
    settings1.mRxPin = GPIO_NUM_16;
    settings1.mTxPin = GPIO_NUM_17;
    if (ACAN_ESP32::can.begin(settings1) == 0) {
    Serial.println("Internal CAN OK");
    } else {
    Serial.println("Internal CAN FAILED");
    }

    SPI.begin();
    while (CAN_OK != can2.begin(MCP_ANY, CAN_500KBPS, MCP_16MHZ)) {
    Serial.println("MCP2515 init failed. Retrying...");
    delay(1000);
    }
    can2.setMode(MCP_NORMAL);
    Serial.println("MCP2515 CAN OK");
    }

    void loop() {
    CANMessage frame;
    if (ACAN_ESP32::can.receive(frame)) parseCAN(frame, 1);
    long unsigned int rxId; uint8_t len = 0, buf[8];
    if (can2.readMsgBuf(&rxId, &len, buf) == CAN_OK) {
    CANMessage m; m.id = rxId; m.len = len; memcpy(m.data, buf, len);
    parseCAN(m, 2);
    }

    if (millis() - lastTesterPing >= testerPingInterval) {
    lastTesterPing = millis();
    uint8_t req1[] = {0x02, 0x21, 0xCE,0,0,0,0,0};
    uint8_t req2[] = {0x02, 0x21, 0xCE,0,0,0,0,0};
    uint8_t req3[] = {0x02, 0x21, 0xCE,0,0,0,0,0};
    uint8_t req4[] = {0x02, 0x21, 0xCE,0,0,0,0,0};
    CANMessage tx; tx.id = 0x7E3; tx.len = 8;

    if (pid==1) memcpy(tx.data, req1, 8);
    if (pid==2) memcpy(tx.data, req2, 8);
    if (pid==3) memcpy(tx.data, req3, 8);
    if (pid==4) memcpy(tx.data, req4, 8);

    ACAN_ESP32::can.tryToSend(tx);
    can2.sendMsgBuf(0x7E3, 0, 8, tx.data);
    pid++; if (pid>4) pid=1;
    }
    }

    void parsePIDMap(uint8_t* data, size_t len) {
    if (len < 4) return;
    uint8_t base = data[2];
    Serial.printf("[PIDMAP] Base 0x%02X\n", base);
    for (int i=0; i<4; i++) {
    uint8_t b = data[3+i];
    for (int bit=7; bit>=0; bit--) {
    if (b & (1<<bit)) {
    uint8_t pid = base + (i*8 + (7-bit)) + 1;
    Serial.printf(" -> PID: 0x%02X\n", pid);
    }
    }
    }
    }

    void decodeBlocks(uint8_t* data, size_t len, int bus) {
    float* blocks = (bus == 1) ? blockVoltages1 : blockVoltages2;

    // We expect at least 2 bytes of header + 14 * 2 = 30 bytes of data
    if (len < 2 + 14 * 2) {
    Serial.printf("[BUS%d] decodeBlocks() called with insufficient data!\n", bus);
    return;
    }

    Serial.printf("[BUS%d] data:", bus);
    for (size_t i = 0; i < len; i++) {
    Serial.printf(" %02X", data);
    }
    Serial.println();

    for (int i = 0; i < 14; i++) {
    uint8_t D = data[2 + i * 2];
    uint8_t E = data[2 + i * 2 + 1];
    blocks = (2.56f * D) + (0.01f * E) - 327.68f;
    }

    Serial.printf("[BUS%d] Blocks:", bus);
    float sum = 0.0;
    for (int i = 0; i < 14; i++) {
    Serial.printf(" %.2f", blocks);
    sum += blocks;
    }
    Serial.printf("\n[BUS%d] Sum of blocks: %.2f V\n", bus, sum);
    }

    void parseCAN(const CANMessage &frame, int bus) {
    float &packVoltage = (bus==1) ? packVoltage1 : packVoltage2;
    float &packCurrent = (bus==1) ? packCurrent1 : packCurrent2;
    int &soc = (bus==1) ? soc1 : soc2;
    int &ccl = (bus==1) ? ccl1 : ccl2;
    int &cdl = (bus==1) ? cdl1 : cdl2;
    int &tempA = (bus==1) ? temp1a : temp2a;
    int &tempB = (bus==1) ? temp1b : temp2b;
    uint16_t &faultCode = (bus==1) ? faultCode1 : faultCode2;
    uint8_t &deltaSOC = (bus==1) ? deltaSOC1 : deltaSOC2;
    uint16_t &calibX = (bus==1) ? calibX1 : calibX2;
    uint16_t &calibY = (bus==1) ? calibY1 : calibY2;
    uint16_t &calibZ = (bus==1) ? calibZ1 : calibZ2;
    uint8_t &flags = (bus==1) ? flags1 : flags2;

    switch (frame.id) {
    case 0x03B: { int16_t raw = ((frame.data[0]&0x0F)<<8)|frame.data[1];
    if (raw & 0x800) raw -= 0x1000; packCurrent = raw*0.1;
    packVoltage = (frame.data[2]<<8)|frame.data[3]; break; }
    case 0x3CB: { cdl=frame.data[0]; ccl=frame.data[1]; deltaSOC=frame.data[2];
    soc=frame.data[3]; tempA=(int8_t)frame.data[4]; tempB=(int8_t)frame.data[5]; break; }
    case 0x3CD: { faultCode=(frame.data[0]<<8)|frame.data[1];
    packVoltage=(frame.data[2]<<8)|frame.data[3]; break; }
    case 0x3C9: { calibY=(frame.data[0]<<4)|(frame.data[1]>>4);
    calibZ=((frame.data[1]&0x0F)<<8)|frame.data[2];
    calibX=(frame.data[3]<<4)|(frame.data[4]>>4); break; }
    case 0x4D1: { flags = frame.data[7]; break; }
    }

    if (frame.id==0x7EB) {
    uint8_t* buf = (bus==1) ? isoTPBuf1 : isoTPBuf2;
    size_t &isoLen = (bus==1) ? isoTPLen1 : isoTPLen2;
    uint8_t pci = frame.data[0];
    if ((pci & 0xF0)==0x00) {
    isoLen = pci&0x0F; memcpy(buf, frame.data+1, isoLen);
    if (buf[1]==0x61 && (buf[2]&0xF0)==0x40) parsePIDMap(buf, isoLen);
    else decodeBlocks(buf+3, isoLen-3, bus);
    } else if ((pci&0xF0)==0x10) {
    isoLen=frame.data[1]; size_t cpy=frame.len-2;
    memcpy(buf, frame.data+2, cpy); isoLen=cpy;

    // Send Flow Control
    CANMessage fc;
    fc.id = 0x7E3;
    fc.len = 8;
    fc.data[0] = 0x30; // Flow Control
    fc.data[1] = 0x00; // No block size
    fc.data[2] = 0x05; // 5 ms
    for (int i = 3; i < 8; i++) fc.data = 0;
    if (bus == 1)
    ACAN_ESP32::can.tryToSend(fc);
    else
    can2.sendMsgBuf(fc.id, 0, fc.len, fc.data);
    } else if ((pci&0xF0)==0x20) {
    size_t cpy=frame.len-1; memcpy(buf+isoLen, frame.data+1, cpy);
    isoLen+=cpy; if (isoLen>=14) decodeBlocks(buf+3, isoLen-3, bus);
    }
    }
    }
     
  10. black_jmyntrn

    black_jmyntrn Senior Member

    Joined:
    Oct 23, 2020
    1,698
    400
    4
    Location:
    Los Angeles, CA
    Vehicle:
    2013 Prius Plug-in
    Model:
    Plug-in Advanced
    ohhhh my! :: going to grab a spare esp32 board ::