`IOStream` buffering pitfalls when communicating to a device file

I was talking to an ADS1115 ADC over I²C via /dev/i2c-1 on a Raspberry Pi 4.

My first attempt was using high-level IOStream APIs like write(io, bytes) and read(io, n), and it worked but only for the first value. Every value afterward is somehow saturated i.e. they’re all 0xFFFF.

const I2C_SLAVE::UInt16             = 0x0703
const ADS1115_ADDRESS::UInt8        = 0x48
const ADS1115_CONFIG_REG::UInt8     = 0x01
const ADS1115_CONVERSION_REG::UInt8 = 0x00

io = open("/dev/i2c-1", "r+")
_fd = fd(io)
ccall((:ioctl, "libc.so.6"), Cint, (Cint, Culong, Cint), _fd, I2C_SLAVE, ADS1115_ADDRESS)

write(io, UInt8[ADS1115_CONFIG_REG, 0b10100010, 0b00000011])
flush(io)
sleep(0.01)

write(io, UInt8[ADS1115_CONVERSION_REG])
flush(io)

while true
    data = read(io, 2)
    value = reinterpret(Int16, data[end:-1:begin])
    println(value)
    sleep(0.2)
end

# results:
# 
# 7328 (a random meaningful value)
# -1
# -1
# -1
# ...

I resolved the problem by ccalling the low-level read in libc instead; that is, I modified the loop to be

data = Array{UInt8}(undef, 2)
while true
    ccall((:read, "libc.so.6"), Cint, (Cint, Ptr{UInt8}, Csize_t), _fd, v, nbuf)
    value = reinterpret(Int16, data[end:-1:begin])
    println(value)
    sleep(0.2)
end

# results:
# 
# 7328
# 7329
# 7328
# ...

I suspect that it’s because IOStream is buffered but lack relevant knowledge to dig deeper. I prefer sticking to high-level APIs as much as possible so I wonder if anyone can tweak the first example to make it work.

P.S. I tried unsafe_read but ended up with no luck.