Here’s the summary
Companion computer
Read & Send Script
Make a script in the ~/companion/tools/
directory that:
- Connects to the top computer (requires QGC to be open)
- Gets sensor data (
float
) - Sends the data in a
named_value_float
message
e.g.
import time
from pymavlink import mavutil
def wait_conn(device):
""" Sends a ping to develop UDP communication and waits for a response. """
msg = None
global boot_time
while not msg:
# boot_time = time.time()
device.mav.ping_send(
int((time.time()-boot_time) * 1e6), # Unix time since boot (microseconds)
0, # Ping number
0, # Request ping of all systems
0 # Request ping of all components
)
msg = device.recv_match()
time.sleep(0.5)
if __name__ == '__main__':
boot_time = time.time()
# establish connection to top-side computer
direction = 'udpout'
device_ip = '192.168.2.1'
port = '14550'
params = [direction, device_ip, port]
print('connecting to {1} via {0} on port {2}'.format(*params))
computer = mavutil.mavlink_connection(':'.join(params), source_system=1)
print('waiting for confirmation...')
wait_conn(computer)
print('connection success!')
# connect to sensor and set up output
# TODO: connect to sensor
sensor_name = 'MySensor' # MUST be 9 characters or fewer
while 'reading data':
value = sensor.get_value() # TODO: implement something like this
computer.mav.named_value_float_send(
int((time.time() - boot_time) * 1e3), # Unix time since boot (milliseconds)
sensor_name,
value
)
For my ultrasonic thickness gauge case, I was reading in data with a serial connection and wanted a command-line option to only read and print the sensor data, without connecting to mavlink, so I could use ssh to check if the sensor was working. Code for that can be found here.
Run on startup
Modify ~/companion/.companion.rc
, adding a new screen
session with an appropriate name, to run right after telem.py
. If the read and send script doesn’t end (likely the case), have an &
to run in its own thread
e.g.
sudo -H -u pi screen -dm -S mavproxy $COMPANION_DIR/tools/telem.py
sudo -H -u pi screen -dm -S sensor $COMPANION_DIR/tools/sensor.py &
sudo -H -u pi screen -dm -S video $COMPANION_DIR/tools/streamer.py
Restart on telemetry restart
Modify ~/companion/scripts/restart-mavproxy.sh
to also quit and restart the custom screen session.
e.g.
#!/bin/bash
screen -X -S sensor quit
screen -X -S mavproxy quit
sudo -H -u pi screen -dm -S mavproxy $COMPANION_DIR/tools/telem.py
sudo -H -u pi screen -dm -S sensor $COMPANION_DIR/tools/sensor.py &
QGroundControl
Follow the instructions to get the code, but before building make the following modifications:
- In main ArduSub code file (
src/FirmwarePlugin/APM/ArduSubFirmwarePlugin.cc
)
// in void ArduSubFirmwarePlugin::_handleNamedValueFloat(mavlink_message_t* message)
} else if (name == "RollPitch") {
_infoFactGroup.getFact("rollPitchToggle")->setRawValue(value.value);
} else if (name == "MySensor") { // should be the same name as in your companion script
_infoFactGroup.getFact("mySensor")->setRawValue(value.value); //name for finding in QGC
}
}
// ...
const char* APMSubmarineFactGroup::_rollPitchToggleFactName = "rollPitchToggle";
const char* APMSubmarineFactGroup::_mySensorFactName = "mySensor";
const char* APMSubmarineFactGroup::_rangefinderDistanceFactName = "rangefinderDistance";
// ...
// in APMSubmarineFactGroup::APMSubmarineFactGroup(QObject* parent)
, _rollPitchToggleFact (0, _rollPitchToggleFactName, FactMetaData::valueTypeDouble)
, _mySensorFact (0, _mySensorFactName, FactMetaData::valueTypeDouble)
, _rangefinderDistanceFact (0, _rangefinderDistanceFactName, FactMetaData::valueTypeDouble)
{
// ...
_addFact(&_rollPitchToggleFact , _rollPitchToggleFactName);
_addFact(&_mySensorFact , _mySensorFactName);
_addFact(&_rangefinderDistanceFact, _rangefinderDistanceFactName);
// ...
_rollPitchToggleFact.setRawValue (2); // 2 shows "Unavailable" in older firmwares
_mySensorFact.setRawValue (std::numeric_limits<float>::quiet_NaN()); // display as --.-- when not connected
_rangefinderDistanceFact.setRawValue (std::numeric_limits<float>::quiet_NaN());
}
- In ArduSub header file (
src/FirmwarePlugin/APM/ArduSubFirmwarePlugin.h
)
// in class APMSubmarineFactGroup : public FactGroup
Q_PROPERTY(Fact* inputHold READ inputHold CONSTANT)
Q_PROPERTY(Fact* mySensor READ mySensor CONSTANT)
Q_PROPERTY(Fact* rangefinderDistance READ rangefinderDistance CONSTANT)
// ...
Fact* inputHold (void) { return &_inputHoldFact; }
Fact* mySensor (void) { return &_mySensorFact; }
Fact* rangefinderDistance (void) { return &_rangefinderDistanceFact; }
// ...
static const char* _rollPitchToggleFactName;
static const char* _mySensorFactName;
static const char* _rangefinderDistanceFactName;
// ...
Fact _rollPitchToggleFact;
Fact _mySensorFact;
Fact _rangefinderDistanceFact;
};
- In QGC name descriptions file (
src/Vehicle/SubmarineFact.json
)
{
"name": "rollPitchToggle",
"shortDesc": "Roll/Pitch Toggle",
"type": "int16",
"enumStrings": "Disabled,Enabled,Unavailable",
"enumValues": "0,1,2"
},
{
"name": "mySensor",
"shortDesc": "DisplayName",
"type": "float",
"decimalPlaces": 3,
"units": "mm"
}
]
}
then complete the QGC build through QtCreator and run.
Your new sensor should now be available through the QGC sensor display. If it appears but with --.--
as the value then it’s not currently sending any readings, so check that the screen session exists (through ssh, or the list of services at the top of 192.168.2.2:2770/system
).
Note:
There will be some time offset between the messages sent by the custom script and those sent by the pixhawk. If doing time-based alignment it’s best to ignore the time for the custom script messages and instead calculate a new time based on the message received before and after in the top-side mavlink stream. Alternatively it may be possible to change the custom script to first make a mavlink connection to the pixhawk, and replace boot_time
by the difference between time.time()
and the time of the received message, although I haven’t yet tried this and can’t guarantee it would work correctly.