Thanks, @tomas_hora. I've just gone ahead and written my own in ST, based off the custom serial protocol example. I'm not sure if that sample actually works for serial communications, but for TCP I had to split Case 3 into 2 cases:
3:
if io.getportstatus(handle) = 0 then
state := 4;
end_if;
4:
received := io.readport(handle, adr dataRecv[0], 100);
if received > 0 THEN
output_string := bytes_to_string(adr dataRecv[0], received);
output_length := len(output_string);
command_out := command_in;
last_output_time := getutctime();
state:=5;
END_IF;
Not doing this seemed to result in the read buffer being reset before the bytes could be read.