QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
Actuators.cc
Go to the documentation of this file.
1#include "Actuators.h"
2#include "MAVLinkLib.h"
3#include "GeometryImage.h"
4#include "ParameterManager.h"
5#include "Vehicle.h"
7
8#include <QtCore/QString>
9#include <QtCore/QJsonArray>
10#include <QtCore/QJsonObject>
11
12#include <algorithm>
13
14QGC_LOGGING_CATEGORY(ActuatorsLog, "Vehicle.Actuators.Actuators")
15
16using namespace ActuatorOutputs;
17
18Actuators::Actuators(QObject* parent, Vehicle* vehicle)
19 : QObject(parent), _actuatorTest(vehicle), _mixer(vehicle->parameterManager()),
20 _motorAssignment(nullptr, vehicle, _actuatorOutputs), _vehicle(vehicle)
21{
23 connect(&_mixer, &Mixer::Mixers::geometryParamChanged, this, &Actuators::updateGeometryImage);
24 qRegisterMetaType<Actuators*>("Actuators*");
27 connect(&_motorAssignment, &MotorAssignment::onAbort, this, [this]() { highlightActuators(false); });
28}
29
30void Actuators::imageClicked(QSizeF displaySize, float x, float y)
31{
33 QPointF clickPosition{ x, y };
34 int motorIndex = provider->getHighlightedMotorIndexAtPos(displaySize, clickPosition);
35 qCDebug(ActuatorsLog) << "Image clicked: position:" << clickPosition << "displaySize:" << displaySize << "motor index:" << motorIndex;
36
37 if (_motorAssignment.active()) {
38 QList<ActuatorGeometry>& actuators = provider->actuators();
39 bool found = false;
40 for (auto& actuator : actuators) {
41 if (actuator.type == ActuatorGeometry::Type::Motor && actuator.index == motorIndex) {
42 actuator.renderOptions.highlight = false;
43 found = true;
44 }
45 }
46 updateGeometryImage();
47
48 if (found) {
49 // call this outside of the loop as it might lead to an actuator refresh
50 _motorAssignment.selectMotor(motorIndex);
51 }
52 }
53}
54
56{
57 if (index >= _actuatorOutputs->count() || index < 0) {
58 index = 0;
59 }
60 _selectedActuatorOutput = index;
62}
64{
65 if (_actuatorOutputs->count() == 0) {
66 return nullptr;
67 }
68 return _actuatorOutputs->value<ActuatorOutputs::ActuatorOutput*>(_selectedActuatorOutput);
69}
70
71void Actuators::updateGeometryImage()
72{
74
75 QList<ActuatorGeometry>& actuators = provider->actuators();
76 QList<ActuatorGeometry> previousActuators = actuators;
77
78 // collect the actuators
79 actuators.clear();
80 for (int mixerGroupIdx = 0; mixerGroupIdx < _mixer.groups()->count(); ++mixerGroupIdx) {
81 Mixer::MixerConfigGroup* mixerGroup = _mixer.groups()->value<Mixer::MixerConfigGroup*>(mixerGroupIdx);
82 for (int mixerChannelIdx = 0; mixerChannelIdx < mixerGroup->channels()->count(); ++mixerChannelIdx) {
83 const Mixer::MixerChannel* mixerChannel = mixerGroup->channels()->value<Mixer::MixerChannel*>(mixerChannelIdx);
84 ActuatorGeometry geometry{};
85 if (mixerChannel->getGeometry(_mixer.actuatorTypes(), mixerGroup->group(), geometry)) {
86 actuators.append(geometry);
87 qCDebug(ActuatorsLog) << "Airframe actuator:" << geometry.index << "pos:" << geometry.position;
88 }
89 }
90 }
91
92 // restore render options if actuators did not change
93 if (previousActuators.size() == actuators.size()) {
94 for (int i = 0; i < actuators.size(); ++i) {
95 if (previousActuators[i].type == actuators[i].type && previousActuators[i].index == actuators[i].index) {
96 actuators[i].renderOptions = previousActuators[i].renderOptions;
97 }
98 }
99 }
100
101 _imageRefreshFlag = !_imageRefreshFlag;
103
104 _motorAssignmentEnabled = provider->numMotors() > 0;
106}
107
109{
110 return _mixer.configuredType() == "multirotor";
111}
112
113void Actuators::load(const QString &json_file, const QJsonDocument &metadata)
114{
115 _initError.clear();
116
117 if (metadata.isNull()) {
118 _initError = QStringLiteral("Could not load metadata file: %1").arg(json_file);
119 _jsonMetadata = {};
120 qCWarning(ActuatorsLog) << _initError;
121 return;
122 }
123
124 _jsonMetadata = metadata;
125}
126
128{
129 if (_init) {
130 return;
131 }
132
133 if (!_vehicle->parameterManager()->parametersReady()) {
134 qWarning() << "Incorrect calling order, parameters not yet ready";
135 }
136
137 if (!parseJson(_jsonMetadata)) {
138 return;
139 }
140 _jsonMetadata = {};
141
142 // Remove groups that have no enable param and none of the function params is available
143 for (int groupIdx = 0; groupIdx < _actuatorOutputs->count(); groupIdx++) {
144 ActuatorOutput* group = qobject_cast<ActuatorOutput*>(_actuatorOutputs->get(groupIdx));
145 if (!group->enableParam() && !group->hasExistingOutputFunctionParams()) {
146 qCDebug(ActuatorsLog) << "Removing actuator group w/o function parameters at" << groupIdx;
147 _actuatorOutputs->removeAt(groupIdx);
148 delete group;
149 --groupIdx;
150 }
151 }
152
155}
156
158{
159 qCDebug(ActuatorsLog) << "Param update";
160
161 _mixer.update();
162
163 // gather all enabled functions
164 QList<int> allFunctions;
165 for (int groupIdx = 0; groupIdx < _actuatorOutputs->count(); groupIdx++) {
166 ActuatorOutput* group = qobject_cast<ActuatorOutput*>(_actuatorOutputs->get(groupIdx));
167 group->clearNotes();
168 QList<Fact*> groupFunctions;
169 group->getAllChannelFunctions(groupFunctions);
170 for (const auto& groupFunction : groupFunctions) {
171 int function = groupFunction->rawValue().toInt();
172 if (function != 0) { // disabled
173 allFunctions.append(function);
174 }
175
176 // update notes for configured functions
177 const auto iter = _mixer.functions().find(function);
178 if (iter != _mixer.functions().end() && iter->note != "") {
179 if (iter->noteCondition.evaluate()) {
180 qCDebug(ActuatorsLog) << "Showing Note:" << iter->note;
181 group->addNote(iter->note);
182 }
183 }
184 }
185
186 // update channel visibility
187 for (int subbroupIdx = 0; subbroupIdx < group->subgroups()->count(); subbroupIdx++) {
188 ActuatorOutputSubgroup* subgroup = qobject_cast<ActuatorOutputSubgroup*>(group->subgroups()->get(subbroupIdx));
189 for (int channelIdx = 0; channelIdx < subgroup->channelConfigs()->count(); channelIdx++) {
190 ChannelConfig* channel = qobject_cast<ChannelConfig*>(subgroup->channelConfigs()->get(channelIdx));
191 channel->reevaluate();
192 }
193 }
194 }
195
196 std::sort(allFunctions.begin(), allFunctions.end());
197
198 // create list of actuators from configured functions
199 QList<ActuatorTesting::Actuator*> actuators;
200 QSet<int> uniqueConfiguredFunctions;
201 const Mixer::ActuatorTypes &actuatorTypes = _mixer.actuatorTypes();
202 for (int function : allFunctions) {
203 if (uniqueConfiguredFunctions.find(function) != uniqueConfiguredFunctions.end()) { // only add once
204 continue;
205 }
206 uniqueConfiguredFunctions.insert(function);
207 QString label = _mixer.getSpecificLabelForFunction(function);
208
209 // check if we should exclude the function from testing
210 bool excludeFromActuatorTesting = false;
211 const auto iter = _mixer.functions().find(function);
212 if (iter != _mixer.functions().end()) {
213 excludeFromActuatorTesting = iter->excludeFromActuatorTesting;
214 }
215
216 // find actuator
217 if (!excludeFromActuatorTesting) {
218 bool found = false;
219 for (const auto& actuatorTypeName : actuatorTypes.keys()) {
220 const Mixer::ActuatorType& actuatorType = actuatorTypes[actuatorTypeName];
221 if (function >= actuatorType.functionMin && function <= actuatorType.functionMax) {
222 bool isMotor = ActuatorGeometry::typeFromStr(actuatorTypeName) == ActuatorGeometry::Type::Motor;
223 actuators.append(
224 new ActuatorTesting::Actuator(&_actuatorTest, label, actuatorType.values.min, actuatorType.values.max,
225 actuatorType.values.defaultVal, function, isMotor));
226 found = true;
227 break;
228 }
229 }
230 if (!found && actuatorTypes.find("DEFAULT") != actuatorTypes.end()) {
231 const Mixer::ActuatorType& actuatorType = actuatorTypes["DEFAULT"];
232 actuators.append(
233 new ActuatorTesting::Actuator(&_actuatorTest, label, actuatorType.values.min, actuatorType.values.max,
234 actuatorType.values.defaultVal, function, false));
235 }
236 }
237 }
238 _actuatorTest.updateFunctions(actuators);
239
240 // check if there are required functions, but not set on any output
241 QSet<int> requiredFunctions = _mixer.getFunctions(true);
242 _hasUnsetRequiredFunctions = false;
243 for (int requiredFunction : requiredFunctions) {
244 if (uniqueConfiguredFunctions.find(requiredFunction) == uniqueConfiguredFunctions.end()) {
245 _hasUnsetRequiredFunctions = true;
246 }
247 }
249
250 updateFunctionMetadata();
251
252 updateActuatorActions();
253
254 updateGeometryImage();
255}
256
257void Actuators::updateFunctionMetadata()
258{
259 // Update the function parameter metadata:
260 // - remove the mixer functions that are unused with the current configration (e.g. if 4 motors -> remove motors 5-N)
261 // - use the specific labels
262 QSet<int> usedMixerFunctions = _mixer.getFunctions(false);
263
264 QMap<int, QString> usedMixerLabels;
265 for (int usedMixerFunction : usedMixerFunctions) {
266 usedMixerLabels[usedMixerFunction] = _mixer.getSpecificLabelForFunction(usedMixerFunction);
267 }
268
269 if (_usedMixerLabels == usedMixerLabels) {
270 // no update required
271 return;
272 }
273 _usedMixerLabels = usedMixerLabels;
274
275 // Get the unused mixer functions
276 QSet<int> removedMixerFunctions;
277 for(Mixer::ActuatorTypes::const_iterator iter = _mixer.actuatorTypes().constBegin();
278 iter != _mixer.actuatorTypes().constEnd(); ++iter) {
279 if (iter.key() == "DEFAULT")
280 continue;
281
282 for (int i = iter.value().functionMin; i <= iter.value().functionMax; ++i) {
283 if (!usedMixerFunctions.contains(i)) {
284 removedMixerFunctions.insert(i);
285 }
286 }
287 }
288
289 // Now update all function facts (we need to treat them individually, as some might have extra functions)
290 for (int groupIdx = 0; groupIdx < _actuatorOutputs->count(); groupIdx++) {
291 ActuatorOutput* group = qobject_cast<ActuatorOutput*>(_actuatorOutputs->get(groupIdx));
292
293 group->forEachOutputFunction([&]([[maybe_unused]] ActuatorOutputSubgroup* subgroup, ChannelConfigInstance*, Fact* fact) {
294 QStringList enumStrings = fact->enumStrings();
295 if (!enumStrings.empty()) {
296 QVariantList enumValues = fact->enumValues();
297
298 // Replace or add
299 for (int usedMixerFunction : usedMixerFunctions) {
300 QString label = usedMixerLabels[usedMixerFunction];
301 int index = enumValues.indexOf(usedMixerFunction);
302 if (index == -1) {
303 // Insert at the right place
304 bool inserted = false;
305 for (index = 0; index < enumValues.count() && !inserted; ++index) {
306 if (enumValues[index].toInt() > usedMixerFunction) {
307 enumValues.insert(index, usedMixerFunction);
308 enumStrings.insert(index, label);
309 inserted = true;
310 }
311 }
312 if (!inserted) {
313 enumValues.append(usedMixerFunction);
314 enumStrings.append(label);
315 }
316 } else {
317 enumStrings[index] = label;
318 }
319 }
320
321 // Remove
322 for (int removedMixerFunction : removedMixerFunctions) {
323 int index = enumValues.indexOf(removedMixerFunction);
324 if (index != -1) {
325 enumValues.removeAt(index);
326 enumStrings.removeAt(index);
327 }
328 }
329
330 fact->setEnumInfo(enumStrings, enumValues);
331 }
332 });
333 }
334}
335
336void Actuators::updateActuatorActions()
337{
338 _actuatorActions->clearAndDeleteContents();
339 QSet<int> addedFunctions;
340 for (int groupIdx = 0; groupIdx < _actuatorOutputs->count(); groupIdx++) {
341 ActuatorOutput* group = qobject_cast<ActuatorOutput*>(_actuatorOutputs->get(groupIdx));
342
344 int outputFunctionVal = fact->rawValue().toInt();
345 if (outputFunctionVal != 0 && !addedFunctions.contains(outputFunctionVal)) {
346 auto outputFunctionIter = _mixer.functions().find(outputFunctionVal);
347 if (outputFunctionIter != _mixer.functions().end()) {
348 const Mixer::Mixers::OutputFunction& outputFunction = outputFunctionIter.value();
349 for (const auto& action : subgroup->actions()) {
350 if (!action.condition.evaluate()) {
351 continue;
352 }
353 if (!action.actuatorTypes.empty() && action.actuatorTypes.find(outputFunction.actuatorType) == action.actuatorTypes.end()) {
354 continue;
355 }
356
357 // add the action
358 auto actuatorAction = new ActuatorActions::Action(this, action, outputFunction.label, outputFunctionVal, _vehicle);
359 ActuatorActions::ActionGroup* actionGroup = nullptr;
360 // try to find the group
361 for (int actionGroupIdx = 0; actionGroupIdx < _actuatorActions->count(); actionGroupIdx++) {
362 ActuatorActions::ActionGroup* curActionGroup =
363 qobject_cast<ActuatorActions::ActionGroup*>(_actuatorActions->get(actionGroupIdx));
364 if (curActionGroup->type() == action.type) {
365 actionGroup = curActionGroup;
366 break;
367 }
368 }
369
370 if (!actionGroup) {
371 QString groupLabel = action.typeToLabel();
372 actionGroup = new ActuatorActions::ActionGroup(this, groupLabel, action.type);
373 _actuatorActions->append(actionGroup);
374 }
375 actionGroup->addAction(actuatorAction);
376 addedFunctions.insert(outputFunctionVal);
377 }
378 }
379 }
380 });
381 }
382
383 emit actuatorActionsChanged();
384}
385
386bool Actuators::parseJson(const QJsonDocument &json)
387{
388 _actuatorOutputs->clearAndDeleteContents();
389
390 QJsonObject obj = json.object();
391 QJsonValue outputsJson = obj.value("outputs_v1");
392 QJsonValue functionsJson = obj.value("functions_v1");
393 QJsonValue mixerJson = obj.value("mixer_v1");
394 if (outputsJson.isNull() || functionsJson.isNull() || mixerJson.isNull()) {
395 QStringList missing;
396 if (outputsJson.isNull()) missing << "outputs_v1";
397 if (functionsJson.isNull()) missing << "functions_v1";
398 if (mixerJson.isNull()) missing << "mixer_v1";
399 if (_initError.isEmpty()) {
400 _initError = QStringLiteral("Missing required JSON sections: %1").arg(missing.join(", "));
401 }
402 qCWarning(ActuatorsLog) << _initError;
403 return false;
404 }
405
406 // parse outputs
407 auto makeConditionFromValue = [this](const QJsonValue& val, const char* key) {
408 return Condition(val[key].toString(""), _vehicle->parameterManager(), key);
409 };
410
411 QJsonArray outputs = outputsJson.toArray();
412 for (const auto &&outputJson : outputs) {
413 QJsonValue output = outputJson.toObject();
414 QString label = output["label"].toString();
415
416 qCDebug(ActuatorsLog) << "Actuator group:" << label;
417
418 Condition groupVisibilityCondition = makeConditionFromValue(output, "show-subgroups-if");
419 subscribeFact(groupVisibilityCondition.fact());
420
421 ActuatorOutput* currentActuatorOutput = new ActuatorOutput(this, label, groupVisibilityCondition);
422 _actuatorOutputs->append(currentActuatorOutput);
423
424 auto parseParam = [&currentActuatorOutput, this](const QJsonValue &parameter) {
425 Parameter param{};
426 param.parse(parameter);
427 QString functionStr = parameter["function"].toString("");
428 qCDebug(ActuatorsLog) << "param:" << param.name << "label:" << param.label << "function:" << functionStr;
429 ConfigParameter::Function function = ConfigParameter::Function::Unspecified;
430 if (functionStr == "enable") {
431 function = ConfigParameter::Function::Enable;
432 } else if (functionStr == "primary") {
433 function = ConfigParameter::Function::Primary;
434 } else if (functionStr != "") {
435 qCWarning(ActuatorsLog) << "Unknown function " << functionStr << "for param" << param.name;
436 }
437 return new ConfigParameter(currentActuatorOutput, getFact(param.name), param.label, function);
438 };
439
440 QJsonArray parameters = output["parameters"].toArray();
441 for (const auto&& parameterJson : parameters) {
442 currentActuatorOutput->addConfigParam(parseParam(parameterJson.toObject()));
443 }
444
445 QJsonArray subgroups = output["subgroups"].toArray();
446 for (const auto&& subgroupJson : subgroups) {
447 QJsonValue subgroup = subgroupJson.toObject();
448 QString subgroupLabel = subgroup["label"].toString();
449 ActuatorOutputSubgroup* actuatorSubgroup = new ActuatorOutputSubgroup(this, subgroupLabel);
450 currentActuatorOutput->addSubgroup(actuatorSubgroup);
451
452 QJsonValue supportedActions = subgroup["supported-actions"];
453 if (!supportedActions.isNull()) {
454 QJsonObject supportedActionsObj = supportedActions.toObject();
455 for (const auto& actionName : supportedActionsObj.keys()) {
456 QJsonObject actionObj = supportedActionsObj.value(actionName).toObject();
458 bool knownAction = true;
459 if (actionName == "beep") {
461 } else if (actionName == "3d-mode-on") {
463 } else if (actionName == "3d-mode-off") {
465 } else if (actionName == "set-spin-direction1") {
467 } else if (actionName == "set-spin-direction2") {
469 } else {
470 knownAction = false;
471 qCWarning(ActuatorsLog) << "Unknown 'supported-actions':" << actionName;
472 }
473 if (knownAction) {
474 QJsonArray actuatorTypesArr = actionObj["actuator-types"].toArray();
475 for (const auto&& type : actuatorTypesArr) {
476 action.actuatorTypes.insert(type.toString());
477 }
478 action.condition = makeConditionFromValue(actionObj, "supported-if");
479 subscribeFact(action.condition.fact());
480 actuatorSubgroup->addAction(action);
481 }
482 }
483 }
484
485 QJsonArray subgroupParameters = subgroup["parameters"].toArray();
486 for (const auto&& parameterJson : subgroupParameters) {
487 actuatorSubgroup->addConfigParam(parseParam(parameterJson.toObject()));
488 }
489
490 QJsonArray channelParameters = subgroup["per-channel-parameters"].toArray();
491 for (const auto&& channelParametersJson : channelParameters) {
492 QJsonValue channelParameter = channelParametersJson.toObject();
493 Parameter param;
494 param.parse(channelParameter);
495
496 ChannelConfig::Function function = ChannelConfig::Function::Unspecified;
497 QString functionStr = channelParameter["function"].toString("");
498 if (functionStr == "function") {
499 function = ChannelConfig::Function::OutputFunction;
500 } else if (functionStr == "disarmed") {
501 function = ChannelConfig::Function::Disarmed;
502 } else if (functionStr == "min") {
503 function = ChannelConfig::Function::Minimum;
504 } else if (functionStr == "max") {
505 function = ChannelConfig::Function::Maximum;
506 } else if (functionStr == "failsafe") {
507 function = ChannelConfig::Function::Failsafe;
508 } else if (functionStr != "") {
509 qCWarning(ActuatorsLog) << "Unknown 'function':" << functionStr;
510 }
511
512 Condition visibilityCondition = makeConditionFromValue(channelParameter, "show-if");
513 subscribeFact(visibilityCondition.fact());
514
515 qCDebug(ActuatorsLog) << "per-channel-param:" << param.label << "param:" << param.name;
516 actuatorSubgroup->addChannelConfig(new ChannelConfig(this, param, function, visibilityCondition));
517 }
518
519 QJsonArray channels = subgroup["channels"].toArray();
520 for (const auto&& channelJson : channels) {
521 QJsonValue channel = channelJson.toObject();
522 QString channelLabel = channel["label"].toString();
523 int paramIndex = channel["param-index"].toInt();
524 qCDebug(ActuatorsLog) << "channel label:" << channelLabel << "param-index" << paramIndex;
525 actuatorSubgroup->addChannel(
526 new ActuatorOutputChannel(this, channelLabel, paramIndex, *actuatorSubgroup->channelConfigs(),
527 _vehicle->parameterManager(), [this](Fact* fact) { subscribeFact(fact); }));
528 }
529 }
530 }
531
532 _showUi = makeConditionFromValue(QJsonValue(obj), "show-ui-if");
533
534 // parse functions
535 QMap<int, Mixer::Mixers::OutputFunction> outputFunctions;
536 QJsonObject functions = functionsJson.toObject();
537 for (const auto& functionKey : functions.keys()) {
538 bool ok;
539 int key = functionKey.toInt(&ok);
540 if (ok) {
541 QJsonObject functionObj = functions.value(functionKey).toObject();
542 QString label = functionObj["label"].toString();
543 if (label != "") {
544 QJsonObject noteObj = functionObj["note"].toObject();
545 QString note = noteObj["text"].toString();
546 QString condition = noteObj["condition"].toString();
547 QString noteCondition = functionObj["label"].toString();
548 bool exclude = functionObj["exclude-from-actuator-testing"].toBool(false);
549 Condition conditionObj{condition, _vehicle->parameterManager()};
550 subscribeFact(conditionObj.fact());
551 outputFunctions[key] = Mixer::Mixers::OutputFunction{label, conditionObj, note, exclude};
552 }
553 }
554 }
555 qCDebug(ActuatorsLog) << "functions:" << outputFunctions;
556
557 Mixer::ActuatorTypes actuatorTypes;
558 // parse mixer
559 QJsonObject actuatorTypesJson = mixerJson.toObject().value("actuator-types").toObject();
560 for (const auto& actuatorTypeName : actuatorTypesJson.keys()) {
561 QJsonValue actuatorTypeVal = actuatorTypesJson.value(actuatorTypeName).toObject();
562 Mixer::ActuatorType actuatorType{};
563 actuatorType.functionMin = actuatorTypeVal["function-min"].toInt();
564 actuatorType.functionMax = actuatorTypeVal["function-max"].toInt();
565 actuatorType.labelIndexOffset = actuatorTypeVal["label-index-offset"].toInt(0);
566 QJsonValue values = actuatorTypeVal["values"].toObject();
567 actuatorType.values.min = values["min"].toDouble();
568 actuatorType.values.max = values["max"].toDouble();
569 actuatorType.values.defaultVal = values["default"].toDouble();
570 if (values["default-is-nan"].toBool()) {
571 actuatorType.values.defaultVal = NAN;
572 }
573 actuatorType.values.reversible = values["reversible"].toBool();
574
575 QJsonArray perItemParametersJson = actuatorTypeVal["per-item-parameters"].toArray();
576 for (const auto&& perItemParameterJson : perItemParametersJson) {
577 QJsonValue perItemParameter = perItemParameterJson.toObject();
578 Parameter param{};
579 param.parse(perItemParameter);
580 actuatorType.perItemParams.append(param);
581 }
582 actuatorTypes[actuatorTypeName] = actuatorType;
583 }
584
585 // fill in the actuator types
586 auto actuatorTypeIter = actuatorTypes.constBegin();
587 while (actuatorTypeIter != actuatorTypes.constEnd()) {
588 if (actuatorTypeIter.key() != "DEFAULT") {
589 for (int function = actuatorTypeIter.value().functionMin; function <= actuatorTypeIter.value().functionMax; ++function) {
590 auto functionIter = outputFunctions.find(function);
591 if (functionIter != outputFunctions.end()) {
592 functionIter->actuatorType = actuatorTypeIter.key();
593 }
594 }
595 }
596 ++actuatorTypeIter;
597 }
598
599 Mixer::MixerOptions mixerOptions{};
600 QJsonValue mixerConfigJson = mixerJson.toObject().value("config");
601 QJsonArray mixerConfigJsonArr = mixerConfigJson.toArray();
602 for (const auto&& mixerConfigJsonValue : mixerConfigJsonArr) {
603 QJsonValue mixerConfig = mixerConfigJsonValue.toObject();
604 Mixer::MixerOption option{};
605 option.option = mixerConfig["option"].toString();
606 option.type = mixerConfig["type"].toString();
607 option.title = mixerConfig["title"].toString();
608 option.helpUrl = mixerConfig["help-url"].toString();
609 QJsonArray actuatorsJson = mixerConfig["actuators"].toArray();
610 for (const auto&& actuatorJson : actuatorsJson) {
611 QJsonValue actuatorJsonVal = actuatorJson.toObject();
613 actuator.groupLabel = actuatorJsonVal["group-label"].toString();
614 if (actuatorJsonVal["count"].isString()) {
615 actuator.count = actuatorJsonVal["count"].toString();
616 } else {
617 actuator.fixedCount = actuatorJsonVal["count"].toInt();
618 }
619 actuator.actuatorType = actuatorJsonVal["actuator-type"].toString();
620 actuator.required = actuatorJsonVal["required"].toBool(false);
621 QJsonArray parametersJson = actuatorJsonVal["parameters"].toArray();
622 for (const auto&& parameterJson : parametersJson) {
623 QJsonValue parameter = parameterJson.toObject();
624 Parameter mixerParameter{};
625 mixerParameter.parse(parameter);
626 actuator.parameters.append(mixerParameter);
627 }
628
629 QJsonArray perItemParametersJson = actuatorJsonVal["per-item-parameters"].toArray();
630 for (const auto&& parameterJson : perItemParametersJson) {
631 QJsonValue parameter = parameterJson.toObject();
632 Mixer::MixerParameter mixerParameter{};
633 mixerParameter.param.parse(parameter);
634 mixerParameter.identifier = parameter["identifier"].toString();
635 QString function = parameter["function"].toString();
636 if (function == "posx") {
637 mixerParameter.function = Mixer::Function::PositionX;
638 } else if (function == "posy") {
639 mixerParameter.function = Mixer::Function::PositionY;
640 } else if (function == "posz") {
641 mixerParameter.function = Mixer::Function::PositionZ;
642 } else if (function == "spin-dir") {
643 mixerParameter.function = Mixer::Function::SpinDirection;
644 } else if (function == "axisx") {
645 mixerParameter.function = Mixer::Function::AxisX;
646 } else if (function == "axisy") {
647 mixerParameter.function = Mixer::Function::AxisY;
648 } else if (function == "axisz") {
649 mixerParameter.function = Mixer::Function::AxisZ;
650 } else if (function == "type") {
651 mixerParameter.function = Mixer::Function::Type;
652 } else if (function != "") {
653 qCWarning(ActuatorsLog) << "Unknown param function:" << function;
654 }
655 // check if not configurable: in that case we expect a list of values
656 bool invalid = false;
657 if (mixerParameter.param.name == "") {
658 QJsonArray valuesJson = parameter["value"].toArray();
659 for (const auto&& valueJson : valuesJson) {
660 mixerParameter.values.append(valueJson.toDouble());
661 }
662
663 if (actuator.fixedCount != mixerParameter.values.size() && mixerParameter.values.size() != 1) {
664 invalid = true;
665 qCWarning(ActuatorsLog) << "Invalid mixer param config:" << actuator.fixedCount << "," << mixerParameter.values.size();
666 }
667 }
668 if (!invalid) {
669 actuator.perItemParameters.append(mixerParameter);
670 }
671 }
672
673 if (actuatorJsonVal["item-label-prefix"].isString()) {
674 actuator.itemLabelPrefix.append(actuatorJsonVal["item-label-prefix"].toString());
675 } else {
676 QJsonArray itemLabelPrefixJson = actuatorJsonVal["item-label-prefix"].toArray();
677 for (const auto&& itemLabelPrefix : itemLabelPrefixJson) {
678 actuator.itemLabelPrefix.append(itemLabelPrefix.toString());
679 }
680 if (actuator.fixedCount != actuator.itemLabelPrefix.size() && actuator.itemLabelPrefix.size() > 1) {
681 qCWarning(ActuatorsLog) << "Invalid mixer config (item-label-prefix):" << actuator.fixedCount << ","
682 << actuator.itemLabelPrefix.size();
683 }
684 }
685
686 option.actuators.append(actuator);
687 }
688 mixerOptions.append(option);
689 }
690
691 QList<Mixer::Rule> rules;
692 QJsonValue mixerRulesJson = mixerJson.toObject().value("rules");
693 QJsonArray mixerRulesJsonArr = mixerRulesJson.toArray();
694 for (const auto&& mixerRuleJson : mixerRulesJsonArr) {
695 QJsonValue mixerRule = mixerRuleJson.toObject();
696 Mixer::Rule rule{};
697 rule.selectIdentifier = mixerRule["select-identifier"].toString();
698
699 QJsonArray identifiersJson = mixerRule["apply-identifiers"].toArray();
700 for (const auto&& identifierJson : identifiersJson) {
701 rule.applyIdentifiers.append(identifierJson.toString());
702 }
703
704 QJsonObject itemsJson = mixerRule["items"].toObject();
705 for (const auto& itemKey : itemsJson.keys()) {
706 bool ok;
707 int key = itemKey.toInt(&ok);
708 if (ok) {
709 QJsonArray itemsArr = itemsJson.value(itemKey).toArray();
710 QList<Mixer::Rule::RuleItem> items{};
711 for (const auto&& itemJson : itemsArr) {
712 QJsonObject itemObj = itemJson.toObject();
713
715 if (itemObj.contains("min")) {
716 item.hasMin = true;
717 item.min = itemObj["min"].toDouble();
718 }
719 if (itemObj.contains("max")) {
720 item.hasMax = true;
721 item.max = itemObj["max"].toDouble();
722 }
723 if (itemObj.contains("default")) {
724 item.hasDefault = true;
725 item.defaultVal = itemObj["default"].toDouble();
726 }
727 item.hidden = itemObj["hidden"].toBool(false);
728 item.disabled = itemObj["disabled"].toBool(false);
729 items.append(item);
730 }
731 if (items.size() == rule.applyIdentifiers.size()) {
732 rule.items[key] = items;
733 } else {
734 qCWarning(ActuatorsLog) << "Rules: unexpected num items in " << itemsArr << "expected:" << rule.applyIdentifiers.size();
735 }
736 }
737 }
738 rules.append(rule);
739 }
740
741 _mixer.reset(actuatorTypes, mixerOptions, outputFunctions, rules);
742 _init = true;
743 return true;
744}
745
746Fact* Actuators::getFact(const QString& paramName)
747{
749 qCDebug(ActuatorsLog) << "Mixer: Param does not exist:" << paramName;
750 return nullptr;
751 }
753 subscribeFact(fact);
754 return fact;
755}
756
757void Actuators::subscribeFact(Fact* fact)
758{
759 if (fact && !_subscribedFacts.contains(fact)) {
761 _subscribedFacts.insert(fact);
762 }
763}
764
766{
767 return _init && _showUi.evaluate();
768}
769
771{
773 int numMotors = provider->numMotors();
774
775 // get the minimum function for motors
776 bool ret = false;
777 auto iter = _mixer.actuatorTypes().find("motor");
778 if (iter == _mixer.actuatorTypes().end()) {
779 qWarning() << "Actuator type 'motor' not found";
780 } else {
781 ret = _motorAssignment.initAssignment(_selectedActuatorOutput, iter->functionMin, numMotors);
782 }
783 return ret;
784}
785
786void Actuators::highlightActuators(bool highlight)
787{
789 QList<ActuatorGeometry>& actuators = provider->actuators();
790 for (auto& actuator : actuators) {
791 if (actuator.type == ActuatorGeometry::Type::Motor) {
792 actuator.renderOptions.highlight = highlight;
793 }
794 }
795 updateGeometryImage();
796}
797
799{
800 highlightActuators(true);
801 _motorAssignment.start();
802}
804{
805 _motorAssignment.abort();
806}
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void addConfigParam(ConfigParameter *param)
void addChannel(ActuatorOutputChannel *channel)
void addChannelConfig(ChannelConfig *channelConfig)
void addAction(const ActuatorActions::Config &action)
QmlObjectListModel * subgroups()
ConfigParameter * enableParam() const
void forEachOutputFunction(std::function< void(ActuatorOutputSubgroup *, ChannelConfigInstance *, Fact *)> callback) const
void getAllChannelFunctions(QList< Fact * > &allFunctions) const
void addConfigParam(ConfigParameter *param)
void addNote(const QString &note)
void addSubgroup(ActuatorOutputSubgroup *subgroup)
Function
Describes the meaning of the parameter.
void updateFunctions(const QList< Actuator * > &actuators)
void actuatorOutputsChanged()
Q_INVOKABLE bool initMotorAssignment()
Definition Actuators.cc:770
void hasUnsetRequiredFunctionsChanged()
Q_INVOKABLE void abortMotorAssignment()
Definition Actuators.cc:803
bool showUi() const
Definition Actuators.cc:765
bool isMultirotor() const
Definition Actuators.cc:108
void imageRefreshFlagChanged()
void motorAssignmentMessageChanged()
Q_INVOKABLE void selectActuatorOutput(int index)
Definition Actuators.cc:55
Q_INVOKABLE void startMotorAssignment()
Definition Actuators.cc:798
void init()
Definition Actuators.cc:127
Q_INVOKABLE void imageClicked(QSizeF displaySize, float x, float y)
Definition Actuators.cc:30
void motorAssignmentActiveChanged()
ActuatorOutputs::ActuatorOutput * selectedActuatorOutput() const
Definition Actuators.cc:63
void motorAssignmentEnabledChanged()
void load(const QString &json_file, const QJsonDocument &metadata)
Definition Actuators.cc:113
void parametersChanged()
Definition Actuators.cc:157
void selectedActuatorOutputChanged()
bool evaluate() const
Definition Common.cc:176
Fact * fact() const
Definition Common.h:101
A Fact is used to hold a single value within the system.
Definition Fact.h:17
void setEnumInfo(const QStringList &strings, const QVariantList &values)
Generally this is done during parsing. But if you know what you are doing, you can.
Definition Fact.cc:302
void rawValueChanged(const QVariant &value)
QStringList enumStrings() const
Definition Fact.cc:282
QString label() const
Definition Fact.cc:523
QVariant rawValue() const
Value after translation.
Definition Fact.h:85
static VehicleGeometryImageProvider * instance()
QList< ActuatorGeometry > & actuators()
int getHighlightedMotorIndexAtPos(const QSizeF &displaySize, const QPointF &position)
bool getGeometry(const ActuatorTypes &actuatorTypes, const MixerOption::ActuatorGroup &group, ActuatorGeometry &geometry) const
Definition Mixer.cc:316
const MixerOption::ActuatorGroup & group() const
Definition Mixer.h:334
QmlObjectListModel * channels()
Definition Mixer.h:325
const ActuatorTypes & actuatorTypes() const
Definition Mixer.h:378
QString configuredType() const
Definition Mixer.cc:603
QmlObjectListModel * groups()
Definition Mixer.h:373
void update()
Definition Mixer.cc:406
const QMap< int, OutputFunction > & functions() const
Definition Mixer.h:396
void reset(const ActuatorTypes &actuatorTypes, const MixerOptions &mixerOptions, const QMap< int, OutputFunction > &functions, const Rules &rules)
Definition Mixer.cc:390
QString getSpecificLabelForFunction(int function) const
Definition Mixer.cc:542
void geometryParamChanged()
void paramChanged()
QSet< int > getFunctions(bool requiredOnly) const
Definition Mixer.cc:585
void activeChanged()
bool active() const
void selectMotor(int motorIndex)
bool initAssignment(int selectedActuatorIdx, int firstMotorsFunction, int numMotors)
void messageChanged()
bool parameterExists(int componentId, const QString &paramName) const
Fact * getParameter(int componentId, const QString &paramName)
bool parametersReady() const
static constexpr int defaultComponentId
void append(QObject *object)
Caller maintains responsibility for object ownership and deletion.
T value(int index) const
Q_INVOKABLE QObject * get(int index)
QObject * removeAt(int index)
int count() const override final
void clearAndDeleteContents() override final
Clears the list and calls deleteLater on each entry.
ParameterManager * parameterManager()
Definition Vehicle.h:577
@ SpinDirection
CCW = true or 1.
QList< MixerOption > MixerOptions
Definition Mixer.h:76
QMap< QString, ActuatorType > ActuatorTypes
key is the group name, where 'DEFAULT' is the default group
Definition Mixer.h:52
@ set3DModeOn
motors: enable 3D mode (reversible)
@ setSpinDirection2
motors: set spin direction 2
@ set3DModeOff
motors: disable 3D mode (reversible)
@ setSpinDirection1
motors: set spin direction 1
static Type typeFromStr(const QString &type)
Definition Common.cc:244
QString option
Definition Mixer.h:69
Parameter param
Definition Mixer.h:27
QString selectIdentifier
Definition Mixer.h:90
void parse(const QJsonValue &jsonValue)
Definition Common.cc:8
QString label
Definition Common.h:21
QString name
vehicle parameter name, this may have an index in the form '${i}'
Definition Common.h:22