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