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