I am working on interfacing Arduino with Julia. There is a particular experiment of reading the values from a sensor connected to Arduino. For this, I have written a function in Julia 1.6.0. This function reads the values coming from the serial port. These values are strings like @\x02. So, I need to convert this string (let us say, s) into an integer value. For this, I am using the parse function, as given below:
num = parse(Int, s) # Convert String to integer
println(num)
While executing this, I get an error as shown below:
LoadError: ArgumentError: invalid base 10 digit ‘@’ in “@\x02”
While finding its solution on the Internet, I came across the built-in functions in Python, as given on Free Bootcamp Prep | Flatiron School. By following the functions given on this website, I could get the desired value from the string in Python, as shown below:
>>> lit = b'@\x02'
>>> n = int.from_bytes(lit, 'little')
>>> n
576
Could you please help me with implementing the same in Julia? Is there any built-in function in Julia that can help in converting from string (of bytes) to integer?
Strings in both Python (str) and Julia (String) encode text in Unicode. It’s not the same as the byte sequences you’re working with. The b'@\x02' in your Python example makes a bytes object, not a string. Julia also has bytes literals (docs here).
I did a bit of reading and I can get 576 by going through an IOBuffer:
lit = b"@\x02"
iolit = IOBuffer(lit)
n = read(iolit, Int16) # 576
# cannot read anymore from iolit, already at end
close(iolit)
I don’t think making and closing an IOBuffer for every couple of bytes like this is the right answer, but at least this shows it’s feasible. I also don’t know how to take the byte order into account (the argument 'little' in the Python example).
Thanks, @Benny, for your response. I have been able to get the value now. However, I have a function that receives the strings and then returns the equivalent integer value. This function is as given below.
function analogRead(file_des::SerialPorts.SerialPort , pin_no::Int64)
str = "A"*string(Char(48+pin_no)) #"An" for analog value on pin n
write(file_des,str)
sleep(0.1) # Delay next step by 100 milliseconds
n = bytesavailable(file_des) # Get number of bytes in input buffer
s = read(file_des, n) # Read n bytes from SerialPort
#######################################
# Need to add the code to get the integer num from s
#######################################
return num # Return the equivalent integer
end
In the above function, when I get the string in s, I need to make it a byte literal and then convert it into an integer value. In other words, I need to add a command in this function, which takes s (assuming that s = “@\x02”) and returns the byte literal of this s (b"@\x02"). This means I need to know how I can add b in this string. I have tried the star(*) operator to concatenate b (referring to byte) and the string s. However, it creates another string “b@\x02”, whereas we need b"@\x02".
Could you please help me with how to obtain a byte literal from the string?
I got this working by rewriting the function as given below:
function analogRead(file_des::SerialPorts.SerialPort , pin_no::Int64)
str = "A"*string(Char(48+pin_no)) #"An" for analog value on pin n
write(file_des,str)
sleep(0.1) # Delay next step by 100 milliseconds
n = bytesavailable(file_des) # Get number of bytes in input buffer
s = read(file_des, n) # Read n bytes from SerialPort
iolit = IOBuffer(s)
num = read(iolit, Int16)
# println(sizeof(num))
# println(num[1])
# @printf("value = %d", Int(num))
close(iolit)
# typeof(s)
# k = parse(Int,s) # Convert String to integer
return num # Return the integer
end
Using reinterpret should be faster than going through IOBuffers:
# Generate random `Int16`s
julia> nums = rand(Int16, 2)
2-element Vector{Int16}:
10792
-18596
# Convert to bytes, this is your actual input
julia> bytes = collect(reinterpret(UInt8, nums))
4-element Vector{UInt8}:
0x28
0x2a
0x5c
0xb7
# Reintepret as `Int16`, you'll get back the same numbers we generated above
julia> reinterpret(Int16, bytes)
2-element reinterpret(Int16, ::Vector{UInt8}):
10792
-18596
Or, if you want to start from a byte string:
# your original example
julia> reinterpret(Int16, b"@\x02")
1-element reinterpret(Int16, ::Base.CodeUnits{UInt8, String}):
576
# Generate a new random byte string
julia> input = String(rand(UInt8, 8))
"\xb2\Ub2aab\xca3 "
# Reinterpret as `Int16`
julia> reinterpret(Int16, codeunits(input))
4-element reinterpret(Int16, ::Base.CodeUnits{UInt8, String}):
-3406
-21838
-13653
8243
Benchmarks:
julia> using BenchmarkTools
julia> f(s) = reinterpret(Int16, s)
f (generic function with 1 method)
julia> function g(s)
io = IOBuffer(s)
n = read(io, Int16)
close(io)
return n
end
g (generic function with 1 method)
julia> @btime f($(b"@\x02"))
3.542 ns (0 allocations: 0 bytes)
1-element reinterpret(Int16, ::Base.CodeUnits{UInt8, String}):
576
julia> @btime g($(b"@\x02"))
10.278 ns (1 allocation: 64 bytes)
576
Note that reinterpret is completely non-allocating and you don’t need to read numbers one-by-one
I suggest removing the Solution mark from my earlier response because, as I pointed out (and demonstrated by @giordano) , making and closing IOBuffers like that every time you read bytes from SerialPort wastes memory and hurts performance. It’s better if you leave this question open so that people who have worked on similar projects know to give you better answers.
@giordano answer actually looks like a great replacement for Python’s int.from_bytes, can even be used to switch between other types.
It actually looks like SerialPort is already some sort of IO object because it has read and write, so making IOBuffer seems redundant.
Literals syntax like b"" is just a convenient way to write a type of object in code, it’s not a b concatenated to a String object. It’s like how Int8[1,2,3] makes an Array{Int8, 1}; it’s not an Int8 concatenated to [1,2,3].