I started playing with the accelerometer and magnetometer today. I've got a text only bubble-level working that could be the basis of a graphical bubble level. We can reset the horizontal (or any base surface) with another script. I'll share the scripts first and then describe how I got the useful numbers from the sensors.
EDIT (21 July 2021) - Updated order of offset and scaling based on @chidochidochido 's findings. The offset was zero on my pyra so there is no change in the results.
The pyra has a Bosch BNO055 chip. I don't know how to change settings on the sensor chip from user space yet so I'm just processing values from /sys/bus/iio/devices/iio\:device1/ here.
To use the bash scripts, unzip the attachment to your home directory. You can then execute the script from a terminal with:
The first script shows how to find the BNO055 driver output in the /sys folder and how to read and scale all the values. The units look to be set to [m/s^2], [rad/s], and micro tesla by default, but this can be configured on the chip.
The next script reports two numbers that are between -1.0 and +1.0. These are like the X, Y coordinate of a bubble in a bubble level. When the bubble is level, they are both zero.
The last script is used to reset the bubble level to horizontal or vertical as you like. Put the pyra on a reference surface then run the script.
EDIT (21 July 2021) - Updated order of offset and scaling based on @chidochidochido 's findings. The offset was zero on my pyra so there is no change in the results.
The pyra has a Bosch BNO055 chip. I don't know how to change settings on the sensor chip from user space yet so I'm just processing values from /sys/bus/iio/devices/iio\:device1/ here.
To use the bash scripts, unzip the attachment to your home directory. You can then execute the script from a terminal with:
Bash:
cd ~/bno055
watch -n0.2 ./pyra-bno055
./pyra-bubble-reset
watch -n0.2 ./pyra-bubble
The first script shows how to find the BNO055 driver output in the /sys folder and how to read and scale all the values. The units look to be set to [m/s^2], [rad/s], and micro tesla by default, but this can be configured on the chip.
Bash:
me@pyra:~/bno055$ ./pyra-bno055
Acceleration -0.15 -0.18 -9.88
Rotation 0.01 -0.02 -0.01
Magnetometer 121.34 15.33 122.83
Bash:
#!/usr/bin/bash
# Find the SYSFS path the ino055 sensor chip
SYSNAME=
for d in "/sys/bus/iio/devices/iio*/name" ; do
SYSNAME=$(grep -l bno055 $d)
if [ -f $SYSNAME ]; then break; fi
done
if [ ! -f $SYSNAME ] ; then
echo "bno055 sensor chip not found in /sys/bus/iio/devices"
exit 1
fi
SYS=$(dirname $SYSNAME)
# Read the acceleration
ascale=$(cat $SYS/in_accel_scale)
ax0=$(cat $SYS/in_accel_x_offset)
ay0=$(cat $SYS/in_accel_y_offset)
az0=$(cat $SYS/in_accel_z_offset)
axr=$(cat $SYS/in_accel_x_raw)
ayr=$(cat $SYS/in_accel_y_raw)
azr=$(cat $SYS/in_accel_z_raw)
accel_x=$(awk "BEGIN{ printf(\"%.2f\",($axr+$ax0)*$ascale) }")
accel_y=$(awk "BEGIN{ printf(\"%.2f\",($ayr+$ay0)*$ascale) }")
accel_z=$(awk "BEGIN{ printf(\"%.2f\",($azr+$az0)*$ascale) }")
# Read the angular velocity
avscale=$(cat $SYS/in_anglvel_scale)
avx0=$(cat $SYS/in_anglvel_x_offset)
avy0=$(cat $SYS/in_anglvel_y_offset)
avz0=$(cat $SYS/in_anglvel_z_offset)
avxr=$(cat $SYS/in_anglvel_x_raw)
avyr=$(cat $SYS/in_anglvel_y_raw)
avzr=$(cat $SYS/in_anglvel_z_raw)
avx=$(awk "BEGIN{ printf(\"%.2f\",($avxr+$avx0)*$avscale) }")
avy=$(awk "BEGIN{ printf(\"%.2f\",($avyr+$avy0)*$avscale) }")
avz=$(awk "BEGIN{ printf(\"%.2f\",($avzr+$avz0)*$avscale) }")
# Read the magnetometer
mscale=$(cat $SYS/in_magn_scale)
mx0=$(cat $SYS/in_magn_x_offset)
my0=$(cat $SYS/in_magn_y_offset)
mz0=$(cat $SYS/in_magn_z_offset)
mxr=$(cat $SYS/in_magn_x_raw)
myr=$(cat $SYS/in_magn_y_raw)
mzr=$(cat $SYS/in_magn_z_raw)
mx=$(awk "BEGIN{ printf(\"%.2f\",($mxr+$mx0)*$mscale) }")
my=$(awk "BEGIN{ printf(\"%.2f\",($myr+$my0)*$mscale) }")
mz=$(awk "BEGIN{ printf(\"%.2f\",($mzr+$mz0)*$mscale) }")
echo "Acceleration $accel_x $accel_y $accel_z"
echo "Rotation $avx $avy $avz"
echo "Magnetometer $mx $my $mz"
The next script reports two numbers that are between -1.0 and +1.0. These are like the X, Y coordinate of a bubble in a bubble level. When the bubble is level, they are both zero.
Bash:
#!/usr/bin/bash
# A bubble level using the pyra's bno055 accelerometer
# Call pyra-bubble-reset to reset the bubble offset
MATRIX="${HOME}/.bno055-bubble"
# Find the SYSFS path the ino055 sensor chip
SYSNAME=
for d in "/sys/bus/iio/devices/iio*/name" ; do
SYSNAME=$(grep -l bno055 $d)
if [ -f $SYSNAME ]; then break; fi
done
if [ ! -f $SYSNAME ] ; then
echo "bno055 sensor chip not found in /sys/bus/iio/devices"
exit 1
fi
SYS=$(dirname $SYSNAME)
# Read the acceleration
ascale=$(cat $SYS/in_accel_scale)
ax0=$(cat $SYS/in_accel_x_offset)
ay0=$(cat $SYS/in_accel_y_offset)
az0=$(cat $SYS/in_accel_z_offset)
axr=$(cat $SYS/in_accel_x_raw)
ayr=$(cat $SYS/in_accel_y_raw)
azr=$(cat $SYS/in_accel_z_raw)
a[0]=$(awk "BEGIN{ printf(\"%.2f\",($axr+$ax0)*$ascale) }")
a[1]=$(awk "BEGIN{ printf(\"%.2f\",($ayr+$ay0)*$ascale) }")
a[2]=$(awk "BEGIN{ printf(\"%.2f\",($azr+$az0)*$ascale) }")
# Read the rotation matrix, or use the default
R[00]=1 ; R[01]=0 ; R[02]=0
R[10]=0 ; R[11]=1 ; R[12]=0
R[20]=0 ; R[21]=0 ; R[22]=1
if [ -f ${MATRIX} ] ; then
row=0
while IFS= read -r line; do
if [ $row -eq 0 ]; then
R[00]=$(echo $line | cut -d ' ' -f 1)
R[01]=$(echo $line | cut -d ' ' -f 2)
R[02]=$(echo $line | cut -d ' ' -f 3)
elif [ $row -eq 1 ]; then
R[10]=$(echo $line | cut -d ' ' -f 1)
R[11]=$(echo $line | cut -d ' ' -f 2)
R[12]=$(echo $line | cut -d ' ' -f 3)
elif [ $row -eq 2 ]; then
R[20]=$(echo $line | cut -d ' ' -f 1)
R[21]=$(echo $line | cut -d ' ' -f 2)
R[22]=$(echo $line | cut -d ' ' -f 3)
fi
row=$((row+1))
done < ${MATRIX}
fi
#echo ${R[00]} ${R[01]} ${R[02]}
#echo ${R[10]} ${R[11]} ${R[12]}
#echo ${R[20]} ${R[21]} ${R[22]}
# Rotate the acceleration vector
a[0]=$(awk "BEGIN{ printf(\"%.2f\",${a[0]}*${R[00]} + ${a[1]}*${R[01]} + ${a[2]}*${R[02]} ) }")
a[1]=$(awk "BEGIN{ printf(\"%.2f\",${a[0]}*${R[10]} + ${a[1]}*${R[11]} + ${a[2]}*${R[12]} ) }")
a[2]=$(awk "BEGIN{ printf(\"%.2f\",${a[0]}*${R[20]} + ${a[1]}*${R[21]} + ${a[2]}*${R[22]} ) }")
# Usage: normalize v[@]
# Result stored in normal[0], normal[1], normal[2]
function normalize
{
# Declare first parameter as indexed array
declare -a v1=("${!1}")
mag=$(awk "BEGIN{ printf(\"%f\", sqrt(${v1[0]}*${v1[0]} + ${v1[1]}*${v1[1]} + ${v1[2]}*${v1[2]}) ) }")
normal[0]=$(awk "BEGIN{ printf(\"%.2f\", ${v1[0]}/$mag ) }")
normal[1]=$(awk "BEGIN{ printf(\"%.2f\", ${v1[1]}/$mag ) }")
normal[2]=$(awk "BEGIN{ printf(\"%.2f\", ${v1[2]}/$mag ) }")
}
normalize a[@]
echo ${normal[0]} ${normal[1]}
The last script is used to reset the bubble level to horizontal or vertical as you like. Put the pyra on a reference surface then run the script.
Bash:
#!/usr/bin/bash
# Measures the direction of down and creates a rotation matrix
# that can be used to correct the down direction later
MATRIX="${HOME}/.bno055-bubble"
# Find the SYSFS path the ino055 sensor chip
SYSNAME=
for d in "/sys/bus/iio/devices/iio*/name" ; do
SYSNAME=$(grep -l bno055 $d)
if [ -f $SYSNAME ]; then break; fi
done
if [ ! -f $SYSNAME ] ; then
echo "bno055 sensor chip not found in /sys/bus/iio/devices"
exit 1
fi
SYS=$(dirname $SYSNAME)
# Read the acceleration
ascale=$(cat $SYS/in_accel_scale)
ax0=$(cat $SYS/in_accel_x_offset)
ay0=$(cat $SYS/in_accel_y_offset)
az0=$(cat $SYS/in_accel_z_offset)
axr=$(cat $SYS/in_accel_x_raw)
ayr=$(cat $SYS/in_accel_y_raw)
azr=$(cat $SYS/in_accel_z_raw)
a[0]=$(awk "BEGIN{ printf(\"%.2f\",($axr+$ax0)*$ascale) }")
a[1]=$(awk "BEGIN{ printf(\"%.2f\",($ayr+$ay0)*$ascale) }")
a[2]=$(awk "BEGIN{ printf(\"%.2f\",($azr+$az0)*$ascale) }")
# Usage: normalize v[@]
# Result stored in normal[0], normal[1], normal[2]
function normalize
{
# Declare first parameter as indexed array
declare -a v1=("${!1}")
mag=$(awk "BEGIN{ printf(\"%f\", sqrt(${v1[0]}*${v1[0]} + ${v1[1]}*${v1[1]} + ${v1[2]}*${v1[2]}) ) }")
normal[0]=$(awk "BEGIN{ printf(\"%f\", ${v1[0]}/$mag ) }")
normal[1]=$(awk "BEGIN{ printf(\"%f\", ${v1[1]}/$mag ) }")
normal[2]=$(awk "BEGIN{ printf(\"%f\", ${v1[2]}/$mag ) }")
}
# Usage: normalize v1[@] v2[@]
# Result stored in dot[0], dot[1], dot[2]
function dotProduct
{
declare -a v1=("${!1}")
declare -a v2=("${!2}")
dot=$(awk "BEGIN{ printf(\"%f\", ${v1[0]}*${v2[0]} + ${v1[1]}*${v2[1]} + ${v1[2]}*${v2[2]}) }")
}
# Usage: normalize v1[@] v2[@]
# Result stored in cross[0], cross[1], cross[2]
function crossProduct
{
declare -a v1=("${!1}")
declare -a v2=("${!2}")
cross[0]=$(awk "BEGIN{ a=${v1[1]}*${v2[2]}; b=${v1[2]}*${v2[1]}; printf(\"%f\", a - b) }")
cross[1]=$(awk "BEGIN{ a=${v1[0]}*${v2[2]}; b=${v1[2]}*${v2[0]}; printf(\"%f\", b - a) }")
cross[2]=$(awk "BEGIN{ a=${v1[0]}*${v2[1]}; b=${v1[1]}*${v2[0]}; printf(\"%f\", a - b) }")
}
function rotateAlign
{
k=$(awk "BEGIN{ printf(\"%f\", 1.0 / ( 1.0 + ${dot} ) )}")
R[00]=$(awk "BEGIN{ printf(\"%f\", ${cross[0]} * ${cross[0]} * ${k} + ${dot} )}")
R[01]=$(awk "BEGIN{ printf(\"%f\", ${cross[0]} * ${cross[1]} * ${k} + ${cross[2]} )}")
R[02]=$(awk "BEGIN{ printf(\"%f\", ${cross[0]} * ${cross[2]} * ${k} - ${cross[1]} )}")
R[10]=$(awk "BEGIN{ printf(\"%f\", ${cross[1]} * ${cross[0]} * ${k} - ${cross[2]} )}")
R[11]=$(awk "BEGIN{ printf(\"%f\", ${cross[1]} * ${cross[1]} * ${k} + ${dot} )}")
R[12]=$(awk "BEGIN{ printf(\"%f\", ${cross[1]} * ${cross[2]} * ${k} + ${cross[0]} )}")
R[20]=$(awk "BEGIN{ printf(\"%f\", ${cross[2]} * ${cross[0]} * ${k} + ${cross[1]} )}")
R[21]=$(awk "BEGIN{ printf(\"%f\", ${cross[2]} * ${cross[1]} * ${k} - ${cross[0]} )}")
R[22]=$(awk "BEGIN{ printf(\"%f\", ${cross[2]} * ${cross[2]} * ${k} + ${dot} )}")
}
# Calculate the rotation matrix needed to make the vector a point upwards
up[0]=0
up[1]=0
up[2]=-1
normalize a[@]
dotProduct up[@] normal[@]
crossProduct up[@] normal[@]
rotateAlign
#echo "magnitude: $mag"
#echo "normal: ${normal[0]} ${normal[1]} ${normal[2]}"
#echo "dot: $dot"
#echo "cross: ${cross[0]} ${cross[1]} ${cross[2]}"
echo
echo Saving matrix to ${MATRIX}
echo ${R[00]} ${R[01]} ${R[02]} > ${MATRIX}
echo ${R[10]} ${R[11]} ${R[12]} >> ${MATRIX}
echo ${R[20]} ${R[21]} ${R[22]} >> ${MATRIX}
cat ${MATRIX}
Attachments
Last edited: