local proto = Proto("t937", "T-937/M Serial Control") local FUN_TABLE = { [0x01] = "O_CONNECT", [0x02] = "O_TEMPS", [0x03] = "O_READ", [0x06] = "O_ACK_WRITE", [0x08] = "C_SET_HEAT", [0x0d] = "C_SET_COOL", [0x10] = "C_STARTSTOP", [0x11] = "C_REQ_READ", [0x20] = "C_WRITE", [0xf1] = "C_ACK_CONNECT", [0xf2] = "C_ACK_READ", } proto.experts.checksum = ProtoExpert.new("t937.checksum", "T937 Checksum Error", expert.group.CHECKSUM, expert.severity.ERROR ) proto.fields.addr = ProtoField.uint16("t937.address", "Address", base.HEX) proto.fields.fun = ProtoField.uint8("t937.function", "Function", base.HEX, FUN_TABLE) proto.fields.len = ProtoField.uint8("t937.length", "Data Length", base.DEC) proto.fields.checksum = ProtoField.uint16("t937.checksum", "Message Checksum", base.HEX) proto.fields.conn_status = ProtoField.uint16("t937.conn_status", "Connection Status", base.HEX, { [0x0100] = "CONNECTING", [0x0200] = "CONNECTED", }) proto.fields.temp1 = ProtoField.uint16("t937.temp1", "Chamber Temp 1", base.UNIT_STRING, {"°C"}) proto.fields.temp2 = ProtoField.uint16("t937.temp2", "Chamber Temp 2", base.UNIT_STRING, {"°C"}) proto.fields.temp3 = ProtoField.uint16("t937.temp3", "Control Board Temp", base.UNIT_STRING, {"°C"}) proto.fields.pwm = ProtoField.uint8("t937.pwm", "PWM", base.HEX) proto.fields.pwm_mode = ProtoField.uint8("t937.pwm_mode", "PWM Mode", base.HEX, { [0x44] = "ON", [0x88] = "OFF", }) proto.fields.profile = ProtoField.uint8("t937.profile_num", "Profile Number", base.DEC) proto.fields.profile_dur = ProtoField.uint16("t937.profile_dur", "Profile Duration", base.UNIT_STRING, {"s"}) proto.fields.profile_temps = ProtoField.ubytes("t937.profile_temps", "Profile Temperatures") proto.fields.startstop = ProtoField.uint8("t937.startstop", "Start/Stop", base.HEX, { [0x11] = "START", [0x22] = "STOP", }) proto.fields.data = ProtoField.bytes("t937.data", "Raw Data") local function try_dissect(bytes, pinfo, tree) -- minimum len is header + checksum if bytes:len() < 4 + 2 then return end local raw_len = bytes:uint(3, 1) if bytes:len() < 4 + raw_len + 2 then return end local buffer = bytes:subset(0, 4+raw_len+2):tvb("T-937 Frame") local sub = tree:add(proto, buffer()) local i = 0 sub:add_packet_field(proto.fields.addr, buffer(0, 2), ENC_BIG_ENDIAN) local funf, fun, i = sub:add_packet_field(proto.fields.fun, buffer(2, 1), ENC_BIG_ENDIAN) sub:add_packet_field(proto.fields.len, buffer(3, 1), ENC_BIG_ENDIAN) local data = buffer(4, raw_len) sub:add_packet_field(proto.fields.data, data, ENC_BIG_ENDIAN) local csf, cs = sub:add_packet_field(proto.fields.checksum, buffer(4+raw_len), ENC_BIG_ENDIAN) local sum = 0 for i=0,4+raw_len-1 do sum = sum + buffer(i, 1):uint() end if sum ~= cs then csf:add_proto_expert_info(proto.experts.checksum, string.format( "expected 0x%04x, got 0x%04x", cs, sum )) end local fun_name = FUN_TABLE[fun] pinfo.cols.info:set("T937 " .. (fun_name or "")) if fun_name then sub = sub:add(data, fun_name) end if fun_name == "O_CONNECT" or fun_name == "C_ACK_CONNECT" then sub:add(proto.fields.conn_status, data()) elseif fun_name == "O_TEMPS" then sub:add(proto.fields.temp1, data(0,2)) sub:add(proto.fields.temp2, data(2,2)) sub:add(proto.fields.temp3, data(4,2)) elseif fun_name == "C_PWM_HEAT" or fun_name == "C_PWM_COOL" then sub:add(proto.fields.pwm, data(0,1)) sub:add(proto.fields.pwm_mode, data(1,1)) elseif fun_name == "C_STARTSTOP" or fun_name == "C_REQ_READ" then sub:add(proto.fields.profile, data(0,1), data(0,1):uint()+1) sub:add(proto.fields.startstop, data(1,1)) elseif fun_name == "C_WRITE" or fun_name == "O_READ" then local len = data(0,2):uint() * 3 sub:add(proto.fields.profile, data(2,1), data(2,1):uint()+1) sub:add(proto.fields.profile_dur, data(9,2), len) sub:add(proto.fields.profile_temps, data(4,2)) end return buffer:len() end local state function proto.init() state = { error = {}, rest = { [P2P_DIR_SENT] = ByteArray.new(), [P2P_DIR_RECV] = ByteArray.new(), }, buffers = {}, last_number = -1, } end function proto.dissector(_buf, pinfo, tree) local buffers = state.buffers[pinfo.number] if buffers then for _,buffer in ipairs(buffers) do try_dissect(buffer, pinfo, tree) end return true end local err = state.error[pinfo.p2p_dir] if err then if state.last_number == pinfo.number then error(err) else return end end if state.last_number >= pinfo.number then error(string.format( "dissection order not increasing: %d >= %d", state.last_number, pinfo.number )) end local rest = state.rest[pinfo.p2p_dir] rest:append(_buf:bytes()) buffers = {} while rest:len() > 0 do local ok, len = pcall(try_dissect, rest, pinfo, tree) if not ok then state.error[pinfo.p2p_dir] = len state.last_number = pinfo.number print(len) error(len) elseif not len then break end table.insert(buffers, rest:subset(0, len)) if len == rest:len() then rest = ByteArray.new() break end rest = rest:subset(len, rest:len() - len) end state.rest[pinfo.p2p_dir] = rest state.buffers[pinfo.number] = buffers state.last_number = pinfo.number return true end pcall(proto.register_heuristic, proto, "ch340.serial_data", proto.dissector) pcall(proto.register_heuristic, proto, "pl2303.serial_data", proto.dissector) DissectorTable.get("rtacser.data"):add_for_decode_as(proto)