Сгенерируйте код для детектора маркера маршрута

В этом примере показано, как протестировать основанный на монокуляре-камерой детектор маркера маршрута и сгенерировать Код С++ для приложений реального времени на предварительно созданной 3D сцене из Нереального Engine® ведущая среда симуляции. Этот пример подтверждает алгоритм детектора маркера маршрута с помощью метрик и проверяет сгенерированный Код С++ при помощи программного обеспечения в симуляции цикла.

Введение

Детектор маркера маршрута является основным компонентом восприятия автоматизированного ведущего приложения. Детектор анализирует изображения дорог, полученных с помощью монокулярного датчика камеры, и возвращает информацию об искривлении и типе маркировки каждого маршрута. Можно спроектировать и симулировать алгоритм детектора маркера маршрута с помощью MATLAB® или Simulink® и оценить его точность с помощью известной основной истины. Можно сделать генерацию Кода С++, чтобы интегрировать детектор к внешней программной среде и развернуться к транспортному средству. Выполняющая генерация кода и верификация модели Simulink гарантируют функциональную эквивалентность между симуляцией и реализацией в реальном времени.

Для получения информации о том, как спроектировать детектор маркера маршрута, смотрите, что Детектор Маркера Маршрута Проекта Использует Нереальный пример Среды симуляции Engine. В этом примере показано, как протестировать детектор маркера маршрута в 3D среде симуляции и сгенерировать Код С++ для реализации в реальном времени. В этом примере вы будете:

  1. Исследуйте и симулируйте модель детектора маркера маршрута.

  2. Оцените точность алгоритма детектора маркера маршрута путем сравнения основной истине.

  3. Сгенерируйте Код С++ из модели алгоритма.

  4. Проверьте реализацию с программным обеспечением в цикле (SIL) симуляция.

  5. Оцените время выполнения и выполните анализ покрытия кода. Чтобы выполнить анализ покрытия кода, необходимо использовать Simulink Coverage™.

Этот пример тестирует алгоритм детектора маркера маршрута на 3D среде симуляции, которая использует Нереальный Engine® от Epic Games®. Нереальный Engine ведущая среда симуляции требует Windows® 64-битная платформа.

if ~ispc
    error(['Unreal driving simulation environment is only supported on Microsoft', char(174), ' Windows', char(174), '.']);
end

Настройте файлы в качестве примера и откройте рабочую копию файлов проекта в качестве примера. MATLAB® копирует файлы в папку в качестве примера так, чтобы можно было отредактировать их.

addpath(fullfile(matlabroot, 'toolbox', 'driving', 'drivingdemos'));
helperDrivingProjectSetup("LaneDetector.zip", "workDir",pwd);

Чтобы гарантировать воспроизводимость результатов симуляции, установите случайный seed.

rng(0);

Исследуйте модель

Система детектора маркера маршрута в этом примере имеет испытательный стенд детектора маркера маршрута и образец модели.

  • Тестовая модель: тестовая модель симулирует и тестирует поведение алгоритма детектора маркера маршрута в разомкнутом контуре.

  • Образец модели: блок Lane Marker Detector в тестовой модели вызывает LaneMarkerDetector образец модели. Образец модели реализует алгоритм обнаружения маркера маршрута и генерирует Код С++ алгоритма. Этот образец модели может быть интегрирован с системами с обратной связью.

Откройте тестовую модель.

open_system('LaneMarkerDetectorTestBench');

Открытие этой модели запускает helperSLLaneMarkerDetectorSetup скрипт, который инициализирует дорожный сценарий с помощью drivingScenario объект в базовом рабочем пространстве. Это также конфигурирует параметры детектора маркера маршрута, параметры модели транспортного средства и сигналы шины Simulink, требуемые для определения вводов и выводов для LaneMarkerDetectorTestBench модель. Тестовая модель содержит эти подсистемы:

  1. Симуляция 3D Сценарий: Задает сцену, транспортные средства и датчик камеры, используемый для симуляции.

  2. Детектор Маркера маршрута: Реализует алгоритм детектора маркера маршрута.

  3. Метрическая Оценка: Оценивает поведение алгоритма детектора маркера маршрута с помощью метрик, которые включают истинные положительные стороны, ложные положительные стороны и ложные отрицательные стороны.

  4. Визуализация: Отображает систему координат, полученную датчиком камеры, и накладывает обнаружения маршрута во время симуляции.

Симуляция 3D подсистема Сценария конфигурирует дорожную сеть, устанавливает положения транспортного средства и синтезирует датчики. Это похоже на Симуляцию 3D подсистема Сценария в Магистральном примере Следующего Маршрута. Однако Симуляция 3D подсистема Сценария, используемая в этом примере, не имеет радарного датчика. Откройте Симуляцию 3D подсистема Сценария.

open_system('LaneMarkerDetectorTestBench/Simulation 3D Scenario');

Детектор Маркера маршрута является образцом модели, который обнаруживает контуры маршрута в системах координат. Откройте образец модели Детектора Маркера Маршрута.

open_system('LaneMarkerDetector');

Блок Lane Marker Detector использует Систему object™, HelperLaneDetectorWrapper, это конфигурирует и реализует алгоритм обнаружения маркера маршрута. Блок берет системы координат, полученные датчиком камеры, как введено, и выводит обнаруженные контуры маршрута при помощи LaneSensor Шина Simulink.

Метрическая подсистема Оценки оценивает точность результатов обнаружения с помощью информации об основной истине. Откройте Метрическую подсистему Оценки.

open_system('LaneMarkerDetectorTestBench/Metrics Assessment');

Метрическая подсистема Оценки сравнивает обнаруженные контуры маршрута и контуры маршрута основной истины при помощи evaluateLaneBoundaries функция. Функция вычисляет боковое расстояние между обнаруженными контурами маршрута и достоверными данными. Если вычисленное расстояние в конкретном пороге, то обнаруженный контур рассматривается как допустимое соответствие (верный положительный), и соответствующее состояние маршрута установлено в 1. В противном случае функция оценивает, является ли контур ложным отрицанием или ложный положительный и затем устанавливает состояние маршрута на 0. Подсистема соединяет выходные параметры left_lane_status и right_lane_status к лампам в инструментальной панели. Лампы идут green когда состояние маршрута будет равняться 1, и пойдите red когда состояние маршрута 0. Выходные параметры left_lane_metrics и right_lane_metrics 1 3 массивы, содержащие соответствия маршрута (истинные положительные стороны), промахи (ложные отрицательные стороны) и ложные положительные стороны для левых и правых маршрутов соответственно. Средний левый маршрут и правильные отклонения маршрута, вычисленные из обнаруженных контуров маршрута, возвращены при выходных параметрах left_lane_distance и right_lane_distance соответственно. Модель использует эти значения отклонения, чтобы проверить точность алгоритма во время симуляции с помощью Проверки Статическая Область значений (Simulink) блоки: Проверьте Левое Отклонение Маршрута и Проверьте Правильное Отклонение Маршрута.

Симулируйте модель

Сконфигурируйте LaneMarkerDetectorTestBench модель, чтобы симулировать в scenario_LD_02_Curve_SixVehicles сценарий. Этот сценарий содержит шесть транспортных средств, включая автомобиль, оборудованный датчиком, и задает их траектории.

helperSLLaneMarkerDetectorSetup("scenario_LD_02_Curve_SixVehicles");

Симулируйте тестовую модель. Используйте окно визуализации и лампы состояния на инструментальной панели, чтобы просмотреть результаты обнаружения, в то время как симуляция запускается.

sim('LaneMarkerDetectorTestBench');

Можно также визуализировать контуры маршрута основной истины путем включения EnableTruthDisplay параметр маски в блоке Visualization.

Оцените точность алгоритма

Анализируйте результаты обнаружения и подтвердите общую производительность алгоритма.

В процессе моделирования, выходные параметры модели три параметра, которые характеризуют контур маршрута: искривление, угол рыскания и боковое смещение. Модель регистрирует эти параметры для обнаруженных контуров маршрута и основной истины к переменной logsout базового рабочего пространства. Можно построить значения в logsout при помощи helperLaneBoundaryParams функция.

helperPlotLaneBoundaryParams(logsout);
      

Из графиков можно вывести отклонения между основной истиной и обнаруженными контурами маршрута. Большое отклонение между графиками указывает, что обнаруженный контур маршрута значительно вдали от основной истины.

Можно также проверить эффективность алгоритма детектора маркера маршрута путем проверки и графического вывода метрик отдельно для левого маршрута и правильного маршрута. Модель также регистрирует результаты обнаружения для левых и правых маршрутов, вычисленных Метрической подсистемой Оценки к переменной logsout базового рабочего пространства. Метрическая подсистема Оценки выходные параметры:

  1. Оставленные метрики маршрута: Возвращенный как массив, который содержит количество соответствий (истинные положительные стороны), промахи (ложные отрицательные стороны) и ложные положительные стороны, вычисленные из обнаружений для левого маршрута дороги.

  2. Оставленное состояние маршрута: Возвращенный как TRUE или FALSE. Значение верно для соответствий в оставленном маршруте и лжи для промахов и ложных положительных сторон в левом маршруте.

  3. Оставленное расстояние маршрута: скаляр, задающий среднее значение расстояний между обнаруженными левыми граничными точками маршрута и основной истиной для левого маршрута.

  4. Правильные метрики маршрута: Возвращенный как массив, который содержит количество соответствий (истинные положительные стороны), промахи (ложные отрицательные стороны) и ложные положительные стороны, вычисленные из обнаружений для правильного маршрута дороги.

  5. Правильное состояние маршрута: Возвращенный как TRUE или FALSE. Значение верно для соответствий в правильном маршруте и лжи для промахов и ложных положительных сторон в правильном маршруте.

  6. Правильное расстояние маршрута: скаляр, задающий среднее значение расстояний между обнаруженными правильными граничными точками маршрута и основной истиной для правильного маршрута.

Постройте результаты обнаружения для левых и правых маршрутов при помощи helperPlotLaneMetrics скрипт:

[numLaneMatches, numLaneMisses, numFalsePositives] = helperPlotLaneMetrics(logsout);

Из графиков можно вывести, что весь обнаруженный левый маршрут и правильные контуры маршрута соответствуют с основной истиной и что нет никаких ложных положительных сторон или ложных отрицательных сторон.

В процессе моделирования модель записывает выход датчика камеры к forwardFacingCamera.mp4. Можно наложить результаты отклонения на маршрутах, обнаруженных в системах координат и записи на видеофайл при помощи helperPlotLaneDetectionResults функция.

hVideoViewer = helperPlotLaneDetectionResults(...
logsout, "forwardFacingCamera.mp4" , scenario, camera,scenarioFcnName,...
"RecordVideo", true,"RecordVideoFileName", scenarioFcnName,...
"OpenRecordedVideoInVideoViewer", true,"VideoViewerJumpToTime", 9.3);

Можно также вычислить полную точность и чувствительность алгоритма обнаружения маркера маршрута как указано ниже:

Точность: Вычислите как процент истинных положительных сторон в общем количестве обнаруженных контуров маршрута.

precision = (numLaneMatches./(numLaneMatches+numFalsePositives))*100;
disp(precision);
   100

Чувствительность: Вычислите как процент истинных положительных сторон в общем количестве фактических контуров маршрута.

sensitivity = (numLaneMatches./(numLaneMatches+numLaneMisses))*100;
disp(sensitivity);
   100

Точность и значения чувствительности устойчивого детектора маркера маршрута должны быть близко к 100 для различных сценариев тестирования и условий испытания.

Сгенерируйте код С++

Можно теперь сгенерировать Код С++ для алгоритма, применить общую оптимизацию и сгенерировать отчет упростить исследование сгенерированного кода.

Сконфигурируйте LaneMarkerDetector модель, чтобы сгенерировать Код С++ для реализации в реальном времени алгоритма. Установите параметры модели, чтобы включить генерацию кода и отобразить значения настройки.

Установите и просмотрите параметры модели, чтобы включить генерацию Кода С++.

helperSetModelParametersForCodeGeneration('LaneMarkerDetector');
save_system('LaneMarkerDetector');
 
 Model  configuration parameters: 
 
                 Parameter                      Value                                                              Description                                                      
    ___________________________________    _______________    ______________________________________________________________________________________________________________________

    {'SystemTargetFile'               }    {'ert.tlc'    }    {'Code Generation>System target file'                                                                                }
    {'TargetLang'                     }    {'C++'        }    {'Code Generation>Language'                                                                                          }
    {'SolverType'                     }    {'Fixed-step' }    {'Solver>Type'                                                                                                       }
    {'FixedStep'                      }    {'auto'       }    {'Solver>Fixed-step size (fundamental sample time)'                                                                  }
    {'EnableMultiTasking'             }    {'on'         }    {'Solver>Treat each discrete rate as a separate task'                                                                }
    {'ProdLongLongMode'               }    {'on'         }    {'Hardware Implementation>Support long long'                                                                         }
    {'BlockReduction'                 }    {'on'         }    {'Simulation Target>Block reduction'                                                                                 }
    {'MATLABDynamicMemAlloc'          }    {'on'         }    {'Simulation Target>Simulation Target>Dynamic memory allocation in MATLAB functions'                                 }
    {'OptimizeBlockIOStorage'         }    {'on'         }    {'Simulation Target>Signal storage reuse'                                                                            }
    {'InlineInvariantSignals'         }    {'on'         }    {'Simulation Target>Inline invariant signals'                                                                        }
    {'BuildConfiguration'             }    {'Faster Runs'}    {'Code Generation>Build configuration'                                                                               }
    {'RTWVerbose'                     }    {'off'        }    {'Code Generation>Verbose build'                                                                                     }
    {'CombineSignalStateStructs'      }    {'on'         }    {'Code Generation>Interface>Combine signal/state structures'                                                         }
    {'SupportVariableSizeSignals'     }    {'on'         }    {'Code Generation>Interface>Support variable-size signals'                                                           }
    {'CodeInterfacePackaging'         }    {'C++ class'  }    {'Code Generation>Interface>Code interface packaging'                                                                }
    {'GenerateExternalIOAccessMethods'}    {'Method'     }    {'Code Generation>Interface>Data Member Visibility>External I/O access'                                              }
    {'EfficientFloat2IntCast'         }    {'on'         }    {'Code Generation>Optimization>Remove code from floating-point to integer conversions that wraps out-of-range values'}
    {'ZeroExternalMemoryAtStartup'    }    {'off'        }    {'Code Generation>Optimization>Remove root level I/O zero initialization (inverse logic)'                            }
    {'CustomSymbolStrGlobalVar'       }    {'$N$M'       }    {'Code Generation>Symbols>Global variables'                                                                          }
    {'CustomSymbolStrType'            }    {'$N$M_T'     }    {'Code Generation>Symbols>Global types'                                                                              }
    {'CustomSymbolStrField'           }    {'$N$M'       }    {'Code Generation>Symbols>Field name of global types'                                                                }
    {'CustomSymbolStrFcn'             }    {'APV_$N$M$F' }    {'Code Generation>Symbols>Subsystem methods'                                                                         }
    {'CustomSymbolStrTmpVar'          }    {'$N$M'       }    {'Code Generation>Symbols>Local temporary variables'                                                                 }
    {'CustomSymbolStrMacro'           }    {'$N$M'       }    {'Code Generation>Symbols>Constant macros'                                                                           }

Сгенерируйте код и рассмотрите отчет генерации кода от образца модели.

slbuild('LaneMarkerDetector');
### Starting build procedure for: LaneMarkerDetector
### Successful completion of build procedure for: LaneMarkerDetector

Build Summary

Top model targets built:

Model               Action                       Rebuild Reason                                    
===================================================================================================
LaneMarkerDetector  Code generated and compiled  Code generation information file does not exist.  

1 of 1 models built (0 models already up to date)
Build duration: 0h 1m 35.615s

Используйте отчет генерации кода исследовать сгенерированный код. Чтобы узнать больше об отчете генерации кода, см. Отчеты для Генерации кода (Embedded Coder). Используйте ссылку Отчета Интерфейса Кода в Отчете Генерации кода исследовать эти сгенерированные методы:

  • LaneMarkerDetector_initialize: Вызовите однажды на инициализации.

  • LaneMarkerDetector_step: Вызывайте периодически каждый шаг, чтобы выполнить алгоритм обнаружения маркера маршрута.

  • LaneMarkerDetector_terminate: Вызовите однажды после завершения.

Дополнительные методы для получения и установки интерфейса сигнала объявляются в LaneMarkerDetector.h и заданный в LaneMarkerDetector.cpp.

Оцените функциональность кода

После генерации Кода С++ для детектора маркера маршрута можно теперь оценить программное обеспечение использования функциональности кода в цикле (SIL) симуляция. Это обеспечивает раннее понимание поведения развертываемого приложения. Чтобы узнать больше о SIL симуляции, обратитесь к SIL симуляциям и PIL симуляциям (Embedded Coder).

SIL симуляция позволяет вам проверить, что скомпилированный сгенерированный код на хосте функционально эквивалентен режиму normal mode.

Сконфигурируйте алгоритм и параметры тестовой модели, чтобы поддержать SIL симуляцию и логарифмическое выполнение профильная информация.

helperSetModelParametersForSIL('LaneMarkerDetector');
helperSetModelParametersForSIL('LaneMarkerDetectorTestBench');
 
LaneMarkerDetector configuration parameters:
 
               Parameter                       Value                                    Description                         
    ________________________________    ____________________    ____________________________________________________________

    {'SystemTargetFile'            }    {'ert.tlc'         }    {'Code Generation>System target file'                      }
    {'TargetLang'                  }    {'C++'             }    {'Code Generation>Language'                                }
    {'CodeExecutionProfiling'      }    {'on'              }    {'Code Generation>Verification>Measure task execution time'}
    {'CodeProfilingSaveOptions'    }    {'AllData'         }    {'Code Generation>Verification>Save options'               }
    {'CodeExecutionProfileVariable'}    {'executionProfile'}    {'Code Generation>Verification>Workspace variable'         }

 
LaneMarkerDetectorTestBench configuration parameters:
 
               Parameter                       Value                                    Description                         
    ________________________________    ____________________    ____________________________________________________________

    {'SystemTargetFile'            }    {'ert.tlc'         }    {'Code Generation>System target file'                      }
    {'TargetLang'                  }    {'C++'             }    {'Code Generation>Language'                                }
    {'CodeExecutionProfiling'      }    {'on'              }    {'Code Generation>Verification>Measure task execution time'}
    {'CodeProfilingSaveOptions'    }    {'AllData'         }    {'Code Generation>Verification>Save options'               }
    {'CodeExecutionProfileVariable'}    {'executionProfile'}    {'Code Generation>Verification>Workspace variable'         }

Сконфигурируйте тестовую модель, чтобы симулировать Детектор Маркера Маршрута в программном обеспечении в режиме (SIL) цикла.

set_param('LaneMarkerDetectorTestBench/Lane Marker Detector','SimulationMode','Software-in-the-loop (SIL)');
sim('LaneMarkerDetectorTestBench');
### Starting build procedure for: LaneMarkerDetector
### Generated code for 'LaneMarkerDetector' is up to date because no structural, parameter or code replacement library changes were found.
### Successful completion of build procedure for: LaneMarkerDetector

Build Summary

0 of 1 models built (1 models already up to date)
Build duration: 0h 0m 1.988s
### Preparing to start SIL simulation ...
Building with 'MinGW64 Compiler (C)'.
MEX completed successfully.
### Starting SIL simulation for component: LaneMarkerDetector
### Application stopped
### Stopping SIL simulation for component: LaneMarkerDetector

Можно сравнить выходные параметры от нормального режима симуляции и программного обеспечения в цикле (SIL) режим симуляции. Можно проверить, находятся ли различия между этими запусками в пределах допуска при помощи следующего кода. Постройте различия обнаруженных параметров контура маршрута между нормальным режимом симуляции и режимом SIL симуляции.

runIDs = Simulink.sdi.getAllRunIDs;
normalSimRunID = runIDs(end - 1);
SilSimRunID = runIDs(end);
diffResult = Simulink.sdi.compareRuns(normalSimRunID ,SilSimRunID);

Постройте различия между параметрами контура маршрута, вычисленными из режима SIL и режима normal mode.

helperPlotDiffSignals(diffResult);

Заметьте, что различиями между значениями параметров контура маршрута между режимом normal mode симуляции и режимом SIL симуляции является приблизительно нуль. Однако существуют незначительные различия из-за различного закругления методов, используемых различными компиляторами.

Оцените время выполнения и покрытие кода

Во время программного обеспечения в цикле (SIL) симуляция регистрируйте метрики времени выполнения для сгенерированного кода на хосте - компьютере к переменной executionProfile в базовом рабочем пространстве MATLAB. Эти времена могут быть ранним индикатором для эффективности сгенерированного кода. Для точных измерений времени выполнения профилируйте сгенерированный код, когда он будет интегрирован во внешнюю среду или при использовании с процессором в цикле (PIL) симуляция. Чтобы узнать больше о профилировании SIL, обратитесь к Профилированию Выполнения кода с SIL и PIL (Embedded Coder).

Постройте время выполнения, потраченное для LaneMarkerDetector_step функция с помощью helperPlotExecutionProfile функция.

helperPlotExecutionProfile(executionProfile);

Заметьте, что можно вывести среднее время, потраченное на систему координат для детектора маркера маршрута из этого графика. Для получения дополнительной информации о генерации профилей выполнения и анализе их во время SIL симуляции, обратитесь ко Времени выполнения, Профилируя для SIL и PIL (Embedded Coder).

Если у вас есть лицензия Simulink Coverage™, можно также выполнить анализ покрытия кода для сгенерированного кода, чтобы измерить полноту тестирования. Можно использовать данные о недостающем покрытии, чтобы найти разрывы в тестировании, недостающих требованиях или непредусмотренной функциональности. Сконфигурируйте настройки покрытия и симулируйте тестовую модель, чтобы сгенерировать отчет анализа покрытия. Найдите сгенерированный отчет CoverageResults/LaneMarkerDetector.html в рабочей директории.

if(license('test','Simulink_Coverage'))
    helperCoverageSettings('LaneMarkerDetectorTestBench');
    cvDataObj = cvsim('LaneMarkerDetectorTestBench');
    cvhtml('CoverageResults/LaneMarkerDetector',cvDataObj);
end
### Starting build procedure for: LaneMarkerDetector
### Generated code for 'LaneMarkerDetector' is up to date because no structural, parameter or code replacement library changes were found.
### Successful completion of build procedure for: LaneMarkerDetector

Build Summary

Top model targets built:

Model               Action         Rebuild Reason                           
============================================================================
LaneMarkerDetector  Code compiled  Compilation artifacts were out of date.  

1 of 1 models built (0 models already up to date)
Build duration: 0h 0m 35.859s
### Preparing to start SIL simulation ...
Building with 'MinGW64 Compiler (C)'.
MEX completed successfully.
### Starting SIL simulation for component: LaneMarkerDetector
### Stopping SIL simulation for component: LaneMarkerDetector
### Completed code coverage analysis

Можно найти Decision Coverage, покрытие операторов и функциональные результаты покрытия при симуляции сгенерированного кода для этого сценария тестирования, scenario_LD_02_Curve_SixVehicles. Можно протестировать эту модель с различными сценариями, чтобы получить полный охват сгенерированного кода. Для получения дополнительной информации о том, как анализировать результаты покрытия во время программного обеспечения в симуляции цикла, отошлите Покрытие кода для Моделей в программном обеспечении в цикле (SIL) Режим и Процессор в цикле (PIL) Режим (Embedded Coder)

Исследуйте дополнительные сценарии

Этот пример предоставляет дополнительные сценарии, которые можно использовать с LaneMarkerDetectorTestBench модель:

  scenario_LF_01_Straight_RightLane
  scenario_LF_02_Straight_LeftLane
  scenario_LF_03_Curve_LeftLane
  scenario_LF_04_Curve_RightLane
  scenario_LD_01_Curve_ThreeVehicles
  scenario_LD_02_Curve_SixVehicles [Default]
  • Используйте сценарии с префиксным scenario_LF в имени файла, чтобы протестировать алгоритм обнаружения маркера маршрута без преграды другими транспортными средствами. Транспортные средства все еще существуют в сценарии, но расположены таким образом, что они не в видимой области значений автомобиля, оборудованного датчиком.

  • Используйте сценарии с префиксным scenario_LD в имени файла, чтобы протестировать алгоритм обнаружения маркера маршрута, в то время как другие транспортные средства на дороге в видимой области значений автомобиля, оборудованного датчиком.

При разработке и тестировании алгоритма обнаружения маркера маршрута в разомкнутом контуре, полезно начаться со сценария, который имеет только автомобиль, оборудованный датчиком. Чтобы сконфигурировать и рабочую область модели для такого сценария, используйте следующий код.

helperSLLaneMarkerDetectorSetup("scenario_LF_04_Curve_RightLane");

Можно использовать эту модель, чтобы интегрировать сцены RoadRunner™ в ведущие сценарии для симуляции и тестирования.

Смотрите также

Функции

Блоки

Похожие темы