From 0ee8f7b797f418debae4ce43fa7419060e1e0785 Mon Sep 17 00:00:00 2001 From: Geoff Phillips Date: Wed, 2 Aug 2023 13:41:38 +0200 Subject: [PATCH] Release v1.0.7 --- .github/template/fwe-build/action.yml | 2 +- .github/workflows/ci.yml | 35 +- .pre-commit-config.yaml | 17 +- CHANGELOG.md | 57 +- CMakeLists.txt | 25 +- README.md | 96 +- THIRD-PARTY-LICENSES | 62 +- cmake/ddsidls.cmake | 70 - cmake/doxygen.cmake | 2 +- configuration/static-config.json | 4 +- docs/AWS-IoTFleetWiseOffboarding.md | 14 +- .../edge-agent-dev-guide-nxp-s32g.md | 34 +- .../edge-agent-dev-guide-renesas-rcar-s4.md | 31 +- docs/dev-guide/edge-agent-dev-guide.md | 471 +- docs/iwave-g26-tutorial/iwave-g26-tutorial.md | 163 +- docs/metrics.md | 71 +- docs/rpi-tutorial/raspberry-pi-tutorial.md | 137 +- interfaces/fastdds/idl/Camera.idl | 44 - .../examples/persistencyMetadataFormat.json | 15 + .../schemas/persistencyMetadataFormat.json | 39 + .../cloudToEdge/collection_schemes.proto | 66 +- .../staticConfiguration.json | 41 +- .../schemas/edgeToCloud/vehicle_data.proto | 2 +- src/datamanagement/CMakeLists.txt | 1 + src/datamanagement/custom/CMakeLists.txt | 12 + src/datamanagement/custom/README.md | 21 +- .../example/aaosvhal/include/AaosVhalSource.h | 78 + .../example/aaosvhal/src/AaosVhalSource.cpp | 76 + .../aaosvhal/test/AaosVhalSourceTest.cpp | 75 + .../externalgps/include/ExternalGpsSource.h | 2 - .../externalgps/src/ExternalGpsSource.cpp | 18 +- .../test/ExternalGpsSourceTest.cpp | 29 +- .../custom/example/iwavegps/README.md | 2 +- .../example/iwavegps/src/IWaveGpsSource.cpp | 21 +- .../iwavegps/test/IWaveGpsSourceTest.cpp | 14 +- .../custom/generic/include/CustomDataSource.h | 10 +- .../custom/generic/src/CustomDataSource.cpp | 8 + .../datacollection/CMakeLists.txt | 21 +- .../include/CollectionSchemeIngestion.h | 7 - .../include/DataCollectionSender.h | 101 - .../include/ICollectionScheme.h | 44 - .../src/CollectionSchemeIngestion.cpp | 44 +- .../src/DataCollectionSender.cpp | 225 - .../test/DataCollectionSenderTest.cpp | 181 - .../datadecoding/CMakeLists.txt | 3 +- .../datadecoding/include/IDecoderDictionary.h | 2 +- .../datadecoding/include/IDecoderManifest.h | 1 - .../datadecoding/include/OBDDataDecoder.h | 4 +- .../datainspection/CMakeLists.txt | 11 +- .../include/CollectionInspectionEngine.h | 8 - .../include/DataOverDDSModule.h | 131 - .../src/CollectionInspectionEngine.cpp | 33 +- .../src/dds/DataOverDDSModule.cpp | 301 - .../datainspection/src/diag/OBDOverCANECU.cpp | 2 +- .../src/diag/OBDOverCANModule.cpp | 4 +- .../src/vehicledatasource/CANDataConsumer.cpp | 2 +- .../test/DataOverDDSModuleTest.cpp | 647 -- .../test/OBDOverCANModuleTest.cpp | 8 +- src/datamanagement/datamanager/CMakeLists.txt | 1 + .../include/CollectionSchemeManager.h | 17 +- .../IActiveCollectionSchemesListener.h | 38 + .../include/ICollectionSchemeManager.h | 2 +- .../src/CollectionSchemeManager.cpp | 33 +- .../src/DecoderDictionaryExtractor.cpp | 150 +- .../src/InspectionMatrixExtractor.cpp | 33 +- .../test/CheckinAndPersistencyTest.cpp | 15 +- .../test/DecoderDictionaryExtractorTest.cpp | 35 +- .../test/InspectionMatrixExtractorTest.cpp | 16 +- .../datamanager/test/SchemaTest.cpp | 319 +- .../include/CollectionSchemeManagerMock.h | 18 +- .../include/CollectionSchemeManagerTest.h | 29 +- src/datamanagement/datasender/CMakeLists.txt | 96 + .../datasender/include/DataSenderManager.h | 116 + .../include/DataSenderManagerWorkerThread.h | 100 + .../include/DataSenderProtoWriter.h} | 20 +- .../datasender/src/DataSenderManager.cpp | 255 + .../src/DataSenderManagerWorkerThread.cpp | 249 + .../src/DataSenderProtoWriter.cpp} | 32 +- .../datasender/test/.clang-tidy | 2 + .../test/DataSenderProtoWriterTest.cpp} | 48 +- .../test/dm-collection-scheme-example.json | 0 .../datasender/test/valgrind.supp | 32 + src/datamanagement/types/CMakeLists.txt | 1 - .../include/CollectionInspectionAPITypes.h | 30 +- src/datamanagement/types/include/Geohash.h | 1 + .../types/include/SensorTypes.h | 21 - src/executionmanagement/CMakeLists.txt | 3 +- .../include/IoTFleetWiseEngine.h | 98 +- .../src/IoTFleetWiseEngine.cpp | 608 +- .../src/android_shared_library.cpp | 50 +- src/executionmanagement/src/main.cpp | 14 +- .../test/IoTFleetWiseEngineTest.cpp | 27 +- .../test/em-example-config-inline-creds.json | 16 +- .../test/em-example-config.json | 16 +- .../api/include/IConnectionTypes.h | 11 +- .../api/include/IConnectivityChannel.h | 44 + .../api/include/IConnectivityModule.h | 39 + .../api/include/ISender.h | 19 + .../bootstrap/include/AwsSDKMemoryManager.h | 23 +- .../aws/bootstrap/src/AwsSDKMemoryManager.cpp | 23 +- .../implementation/aws/iotcpp/CMakeLists.txt | 15 + .../aws/iotcpp/include/AwsGGChannel.h | 175 + .../iotcpp/include/AwsGGConnectivityModule.h | 96 + .../aws/iotcpp/include/AwsIotChannel.h | 69 +- .../iotcpp/include/AwsIotConnectivityModule.h | 102 +- .../aws/iotcpp/include/PayloadManager.h | 59 +- .../aws/iotcpp/src/AwsGGChannel.cpp | 405 + .../iotcpp/src/AwsGGConnectivityModule.cpp | 83 + .../aws/iotcpp/src/AwsIotChannel.cpp | 167 +- .../iotcpp/src/AwsIotConnectivityModule.cpp | 79 +- .../aws/iotcpp/src/PayloadManager.cpp | 203 +- .../test/src/AwsIotConnectivityModuleTest.cpp | 335 +- .../iotcpp/test/src/PayloadManagerTest.cpp | 155 +- .../iotcpp/test/src/RemoteProfilerTest.cpp | 11 + src/platform/linux/CMakeLists.txt | 9 +- .../include/CacheAndPersist.h | 249 +- .../include/ICacheAndPersist.h | 108 - .../src/CacheAndPersist.cpp | 598 +- .../test/CacheAndPersistTest.cpp | 348 +- .../resourcemanagement/include/CPUUsageInfo.h | 1 + .../threadingmanagement/include/Signal.h | 1 + src/testingsupport/include/Testing.h | 15 + src/vehiclenetwork/CMakeLists.txt | 35 - .../datatypes/OBDDataTypesUnitTestOnly.h | 16 +- .../include/dds/CameraDataPublisher.h | 95 - .../include/dds/CameraDataSubscriber.h | 109 - src/vehiclenetwork/include/dds/DDSDataTypes.h | 87 - .../include/dds/IDDSPublisher.h | 139 - .../include/dds/IDDSSubscriber.h | 137 - .../include/dds/SensorDataListener.h | 39 - .../src/CameraDataPublisher.cpp | 228 - .../src/CameraDataSubscriber.cpp | 269 - .../test/CameraDataPublisherTest.cpp | 266 - .../test/CameraDataSubscriberTest.cpp | 542 -- .../test/CameraSubscriberTestPNG.png | Bin 9488 -> 0 bytes tools/android-app/README.md | 254 +- tools/android-app/app/build.gradle | 4 +- .../app/src/main/AndroidManifest.xml | 32 + .../com/aws/iotfleetwise/AboutActivity.java | 15 +- .../aws/iotfleetwise/BluetoothActivity.java | 24 +- .../ConfigureVehicleActivity.java | 15 +- .../main/java/com/aws/iotfleetwise/Fwe.java | 17 + .../com/aws/iotfleetwise/FweApplication.java | 199 +- .../com/aws/iotfleetwise/MainActivity.java | 183 +- .../layout/activity_bluetooth_device_list.xml | 6 - .../res/layout/activity_configure_vehicle.xml | 6 - .../app/src/main/res/values/strings.xml | 4 +- tools/android-app/cloud/.gitignore | 3 + tools/android-app/cloud/aaosVhalDecoders.json | 8514 +++++++++++++++++ tools/android-app/cloud/aaosVhalNodes.json | 4264 +++++++++ tools/android-app/cloud/campaign-android.json | 186 + tools/android-app/cloud/gen-aaos-vhal-info.py | 113 + .../android-app/cloud/network-interfaces.json | 9 + tools/android-app/cloud/provision.sh | 67 +- tools/android-app/cloud/setup-iotfleetwise.sh | 41 +- tools/android-app/settings.gradle | 2 +- tools/build-fwe-cross-android.sh | 18 +- tools/cansim/canigen.py | 11 +- tools/cfn-templates/README.md | 5 - tools/cfn-templates/fwdemo.yml | 36 +- tools/cfn-templates/fwdev.yml | 34 +- tools/cfn-templates/fwremoteprofiler.yml | 4 +- tools/cloud/.gitignore | 1 + tools/cloud/clean-up.sh | 110 +- tools/cloud/demo.sh | 272 +- tools/cloud/obd-nodes.json | 8 +- tools/configure-fwe.sh | 95 +- tools/container/README.md | 6 +- tools/deploy/fwe@.service | 2 +- tools/deploy/start-and-enable-fwe.sh | 22 +- tools/greengrassV2/.gitignore | 1 + tools/greengrassV2/README.md | 75 + .../com.amazon.aws.IoTFleetWise-1.0.0.json | 31 + .../com.amazon.aws.IoTFleetWise-2.0.0.json | 48 + tools/install-cansim.sh | 13 + tools/install-deps-cross-android.sh | 27 +- tools/install-deps-cross-arm64.sh | 81 +- tools/install-deps-cross-armhf.sh | 81 +- tools/install-deps-native.sh | 93 +- tools/install-deps-versions.sh | 9 +- tools/provision.sh | 22 +- 181 files changed, 19759 insertions(+), 6894 deletions(-) delete mode 100644 cmake/ddsidls.cmake delete mode 100644 interfaces/fastdds/idl/Camera.idl create mode 100644 interfaces/persistency/examples/persistencyMetadataFormat.json create mode 100644 interfaces/persistency/schemas/persistencyMetadataFormat.json create mode 100644 src/datamanagement/custom/example/aaosvhal/include/AaosVhalSource.h create mode 100644 src/datamanagement/custom/example/aaosvhal/src/AaosVhalSource.cpp create mode 100644 src/datamanagement/custom/example/aaosvhal/test/AaosVhalSourceTest.cpp delete mode 100644 src/datamanagement/datacollection/include/DataCollectionSender.h delete mode 100644 src/datamanagement/datacollection/src/DataCollectionSender.cpp delete mode 100644 src/datamanagement/datacollection/test/DataCollectionSenderTest.cpp delete mode 100644 src/datamanagement/datainspection/include/DataOverDDSModule.h delete mode 100644 src/datamanagement/datainspection/src/dds/DataOverDDSModule.cpp delete mode 100644 src/datamanagement/datainspection/test/DataOverDDSModuleTest.cpp create mode 100644 src/datamanagement/datamanager/include/IActiveCollectionSchemesListener.h create mode 100644 src/datamanagement/datasender/CMakeLists.txt create mode 100644 src/datamanagement/datasender/include/DataSenderManager.h create mode 100644 src/datamanagement/datasender/include/DataSenderManagerWorkerThread.h rename src/datamanagement/{datacollection/include/DataCollectionProtoWriter.h => datasender/include/DataSenderProtoWriter.h} (83%) create mode 100644 src/datamanagement/datasender/src/DataSenderManager.cpp create mode 100644 src/datamanagement/datasender/src/DataSenderManagerWorkerThread.cpp rename src/datamanagement/{datacollection/src/DataCollectionProtoWriter.cpp => datasender/src/DataSenderProtoWriter.cpp} (80%) create mode 100644 src/datamanagement/datasender/test/.clang-tidy rename src/datamanagement/{datacollection/test/DataCollectionProtoWriterTest.cpp => datasender/test/DataSenderProtoWriterTest.cpp} (80%) rename src/datamanagement/{datacollection => datasender}/test/dm-collection-scheme-example.json (100%) create mode 100644 src/datamanagement/datasender/test/valgrind.supp delete mode 100644 src/datamanagement/types/include/SensorTypes.h create mode 100644 src/offboardconnectivity/api/include/IConnectivityChannel.h create mode 100644 src/offboardconnectivity/api/include/IConnectivityModule.h create mode 100644 src/offboardconnectivity/implementation/aws/iotcpp/include/AwsGGChannel.h create mode 100644 src/offboardconnectivity/implementation/aws/iotcpp/include/AwsGGConnectivityModule.h create mode 100644 src/offboardconnectivity/implementation/aws/iotcpp/src/AwsGGChannel.cpp create mode 100644 src/offboardconnectivity/implementation/aws/iotcpp/src/AwsGGConnectivityModule.cpp delete mode 100644 src/platform/linux/persistencymanagement/include/ICacheAndPersist.h delete mode 100644 src/vehiclenetwork/include/dds/CameraDataPublisher.h delete mode 100644 src/vehiclenetwork/include/dds/CameraDataSubscriber.h delete mode 100644 src/vehiclenetwork/include/dds/DDSDataTypes.h delete mode 100644 src/vehiclenetwork/include/dds/IDDSPublisher.h delete mode 100644 src/vehiclenetwork/include/dds/IDDSSubscriber.h delete mode 100644 src/vehiclenetwork/include/dds/SensorDataListener.h delete mode 100644 src/vehiclenetwork/src/CameraDataPublisher.cpp delete mode 100644 src/vehiclenetwork/src/CameraDataSubscriber.cpp delete mode 100644 src/vehiclenetwork/test/CameraDataPublisherTest.cpp delete mode 100644 src/vehiclenetwork/test/CameraDataSubscriberTest.cpp delete mode 100644 src/vehiclenetwork/test/CameraSubscriberTestPNG.png create mode 100644 tools/android-app/cloud/aaosVhalDecoders.json create mode 100644 tools/android-app/cloud/aaosVhalNodes.json create mode 100644 tools/android-app/cloud/gen-aaos-vhal-info.py delete mode 100644 tools/cfn-templates/README.md create mode 100644 tools/greengrassV2/.gitignore create mode 100644 tools/greengrassV2/README.md create mode 100644 tools/greengrassV2/recipes/com.amazon.aws.IoTFleetWise-1.0.0.json create mode 100644 tools/greengrassV2/recipes/com.amazon.aws.IoTFleetWise-2.0.0.json diff --git a/.github/template/fwe-build/action.yml b/.github/template/fwe-build/action.yml index 39714f90..c90c6063 100644 --- a/.github/template/fwe-build/action.yml +++ b/.github/template/fwe-build/action.yml @@ -1,7 +1,7 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -name: Build AWS IoT FleetWise Edge +name: Build FWE inputs: build-arch: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c52af9d..9f8eb575 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,12 +72,13 @@ jobs: upload-arch: "android" cache-paths: /usr/local/aarch64-linux-android:/usr/local/armv7a-linux-androideabi:/usr/local/x86_64-linux-gnu dist-files: + build/x86_64/src/executionmanagement/libaws-iot-fleetwise-edge.so:x86_64 build/armeabi-v7a/src/executionmanagement/libaws-iot-fleetwise-edge.so:armeabi-v7a build/arm64-v8a/src/executionmanagement/libaws-iot-fleetwise-edge.so:arm64-v8a - name: build-app run: | mkdir -p tools/android-app/app/src/main/jniLibs - cp -r build/dist/arm64-v8a build/dist/armeabi-v7a tools/android-app/app/src/main/jniLibs + cp -r build/dist/x86_64 build/dist/arm64-v8a build/dist/armeabi-v7a tools/android-app/app/src/main/jniLibs cp THIRD-PARTY-LICENSES tools/android-app/app/src/main/assets if [ "${GITHUB_REPOSITORY}" == "aws/aws-iot-fleetwise-edge" ]; then curl -o tools/android-app/app/src/main/res/mipmap-xhdpi/ic_launcher.webp \ @@ -87,12 +88,12 @@ jobs: unset ANDROID_SDK_ROOT ANDROID_HOME=/usr/local/android_sdk ./gradlew assembleRelease - uses: aws-actions/configure-aws-credentials@v2 - if: github.repository == 'aws/aws-iot-fleetwise-edge' && github.ref_type == 'tag' + if: github.repository == 'aws/aws-iot-fleetwise-edge' && github.event_name == 'push' with: role-to-assume: ${{ secrets.ANDROID_SIGNING_ROLE }} aws-region: us-east-1 - name: sign-app - if: github.repository == 'aws/aws-iot-fleetwise-edge' && github.ref_type == 'tag' + if: github.repository == 'aws/aws-iot-fleetwise-edge' && github.event_name == 'push' run: | source tools/install-deps-versions.sh SIGNING_INFO=`aws secretsmanager get-secret-value --region us-east-1 --secret-id AndroidAppKeyStore | jq -r .SecretString` @@ -109,7 +110,7 @@ jobs: app/build/outputs/apk/release/app-release-unsigned-aligned.apk shred -u ~/android-signing.jks - name: upload-artifacts - if: github.repository == 'aws/aws-iot-fleetwise-edge' && github.ref_type == 'tag' + if: github.repository == 'aws/aws-iot-fleetwise-edge' && github.event_name == 'push' uses: actions/upload-artifact@v3 with: name: build-android-app @@ -164,3 +165,29 @@ jobs: platforms: linux/amd64,linux/arm64,linux/arm/v7 cache-from: type=gha cache-to: type=gha,mode=max + + copy-cfn-templates: + runs-on: ubuntu-20.04 + if: + github.repository == 'aws/aws-iot-fleetwise-edge' && (github.ref_type == 'tag' || + (github.event_name == 'push' && github.ref == 'refs/heads/main')) + steps: + - uses: actions/checkout@v3 + - uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: ${{ secrets.CFN_TEMPLATES_COPY_ROLE }} + aws-region: us-east-1 + - name: copy-to-s3 + run: | + SRC_PATH="tools/cfn-templates/" + if [ "${GITHUB_REF_TYPE}" == "tag" ]; then + RELEASE_VERSION="${GITHUB_REF/refs\/tags\//}" + DEST_URL="s3://aws-iot-fleetwise/${RELEASE_VERSION}/cfn-templates/" + aws s3 cp --recursive --acl "authenticated-read" --exclude README.md ${SRC_PATH} ${DEST_URL} + fi + if [ "${GITHUB_EVENT_NAME}" == "push" ] && [ "${GITHUB_REF}" == "refs/heads/main" ]; then + LATEST_URL="s3://aws-iot-fleetwise/latest/" + aws s3 rm --recursive ${LATEST_URL} + DEST_URL="${LATEST_URL}cfn-templates/" + aws s3 cp --recursive --acl "authenticated-read" --exclude README.md ${SRC_PATH} ${DEST_URL} + fi diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0c36e239..f077b3b1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: check-case-conflict - id: check-json - id: check-yaml - exclude: cfn-templates|public-ecr + exclude: cfn-templates - id: check-executables-have-shebangs - id: check-shebang-scripts-are-executable - id: check-merge-conflict @@ -88,7 +88,20 @@ repos: interfaces/protobuf/schemas/edgeConfiguration/staticConfiguration.json --document-file "$x"; done' -- language: python - files: "configuration/static-config.json" + files: "configuration/static-config.json|tools/android-app/app/src/main/assets/config-0.json" + types: [json] + additional_dependencies: ["json-spec==0.10.1"] + verbose: true + - id: validate-persistency-metadata + name: validate-persistency-metadata + # pre-commit intentionally pass all files as args to the command. + # Since the json command only takes one file at once we need some shell script to split the args. + entry: + bash -c 'for x in "$@"; do echo "Validating file $x"; json validate --schema-file + interfaces/persistency/schemas/persistencyMetadataFormat.json --document-file "$x"; done' + -- + language: python + files: "interfaces/persistency/examples/persistencyMetadataFormat.json" types: [json] additional_dependencies: ["json-spec==0.10.1"] verbose: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 90f2c95a..5bbbf62c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Change Log +## v1.0.7 (2023-08-01) + +Features: + +- Add Android Automotive (AAOS) support. +- Add experimental Greengrass V2 support + +Bug fixes: + +- Fix always saving data to disk when offline, even when configured not to in the campaign. +- Fix possible NullPointerException in Android app. + +Improvements: + +- Refactor persistent file handling, which now saves files under a subfolder called + `FWE_Persistency` in the directory configured in the config file by `persistencyPath`. Now a + separate file is saved for each payload file to be uploaded. An extra file `PayloadMetadata.json` + is created containing metadata for these files, the schema for which can be found + [here](./interfaces/persistency/schemas/persistencyMetadataFormat.json). +- Reduce Android app `minSdk` to 21 (Android 5.0). +- Update AWS C++ SDK to v1.11.111. + +Deprecation: + +- Remove the experimental camera feature (`-DFWE_FEATURE_CAMERA`). This is unsupported and it was + not being maintained. + ## v1.0.6 (2023-06-12) Features: @@ -10,7 +37,7 @@ Improvements: - Change from `arn` to `sync_id` for campaign_arn and document_arns, the `sync_id` being the ARN followed by the timestamp of the last update. The change is backwards compatible with older - versions of the edge agent. + versions of FWE. - Ubuntu package mirror from system used, rather than `ports.ubuntu.com`. - Add root CA and inline credentials support to static config file. - Add extra metrics for AWS SDK heap usage, used signal buffer, MQTT messages sent out. @@ -33,7 +60,7 @@ Improvements: - Add documentation on [how to use edge specific metrics](docs/metrics.md). - Change from `arn` to `sync_id` for all decoder manifest Protobuf fields, the `sync_id` being the ARN followed by the timestamp of the last update. The change is backwards compatible with older - versions of the edge agent. + versions of FWE. - Improve MISRA C++ 2008, and AUTOSAR C++ compliance. - Updated CloudFormation templates to use [IMDSv2](https://aws.amazon.com/blogs/security/defense-in-depth-open-firewalls-reverse-proxies-ssrf-vulnerabilities-ec2-instance-metadata-service/). @@ -84,9 +111,9 @@ Improvements: - Logs now show time in ISO 8601 format and UTC. - Added optional config `logColor` for controlling ANSI colors in the logs. Valid values: `Auto`, - `Yes`, `No`. Default value is `Auto`, which will make the agent try to detect whether stdout can + `Yes`, `No`. Default value is `Auto`, which will make FWE try to detect whether stdout can interpret the ANSI color escape sequences. -- A containerized version of the edge agent is available from AWS ECR Public Gallery: +- A containerized version of FWE is available from AWS ECR Public Gallery: https://gallery.ecr.aws/aws-iot-fleetwise-edge/aws-iot-fleetwise-edge. - Improve CERT-CPP compliance. - Improve quick start guide and demo script. @@ -101,8 +128,8 @@ Bugfixes: - Use `std::condition_variable::wait_until` instead of `wait_for` to avoid the [bug](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=41861) when `wait_for` uses system time. - Fix extended id not working with cloud. -- Handle `SIGTERM` signal. Now when stopping the agent with `systemctl` or `kill` without additional - args, it should gracefully shutdown. +- Handle `SIGTERM` signal. Now when stopping FWE with `systemctl` or `kill` without additional args, + it should gracefully shutdown. - Fix bug in canigen.py when signal offset is greater than zero. Improvements: @@ -160,12 +187,12 @@ Bugfixes: Improvements: -- Remove the html version of developer guide. -- Remove source code in S3 bucket. The S3 bucket will only be used to host quick demo cloud - formation. -- Remove convertToPeculiarFloat function from DataCollectionProtoWriter. -- Set default checkin period to 2-min in static-config.json. The quick demo will still use 5 second - as checkin period. +- Remove the HTML version of developer guide. +- Remove source code in S3 bucket. The S3 bucket will only be used to host quick demo + CloudFormation. +- Remove `convertToPeculiarFloat` function from `DataCollectionProtoWriter`. +- Set default checkin period to 2-min in `static-config.json`. The quick demo will still use 5 + second as checkin period. - Update FleetWise CLI Model to GA release version. - Update Customer Demo to remove service-linked role creation for FleetWise Account Registration. @@ -229,7 +256,7 @@ Bugfixes/Improvements: - Unit tests added to release, including clang-format and clang-tidy tests. - Source code now available on GitHub: https://github.com/aws/aws-iot-fleetwise-edge - GitHub CI job added that runs subset of unit tests that do not require SocketCAN. -- Edge agent source code: +- FWE source code: - No changes. - Edge agent developer guide and associated scripts: - Cloud demo script `demo.sh`: @@ -249,7 +276,7 @@ Features: Bugfixes/Improvements: -- Edge agent source code: +- FWE source code: - Fixed bug in `PayloadManager.cpp` that caused corruption of the persisted data. - Improved the documentation of the Protobuf schemas. - Added retry with exponential back-off for making initial connection to AWS IoT Core. @@ -267,7 +294,7 @@ Bugfixes/Improvements: - CloudFormation templates `fwdemo.yml` and `fwdev.yml`: - Kernel updated and SocketCAN modules installed from `linux-modules-extra-aws` to avoid modules becoming unavailable after system upgrade of EC2 instance. - - Edge agent now compiled and run on the same EC2 instance, rather than using CodePipeline. + - FWE now compiled and run on the same EC2 instance, rather than using CodePipeline. ## v0.1.0 (2021-11-29) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9bd6a5fa..3d8110a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,20 +2,18 @@ cmake_minimum_required(VERSION 3.10.2) -project(iotfleetwise VERSION 1.0.6) +project(iotfleetwise VERSION 1.0.7) -# AWS IoT FleetWise Edge uses C++14 for compatibility reasons with -# Automotive middlewares ( Adaptive AUTOSAR, ROS2) +# FWE uses C++14 for compatibility reasons with Automotive middlewares (Adaptive AUTOSAR, ROS2) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED True) -message(STATUS "Hello from the AWS IoT FleetWise build system!") +message(STATUS "Hello from the FWE build system!") # Print out the compile commands which is useful for IDEs set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -# All AWS IoT FleetWise build and configure options are prefaced with an FWE_ to set -# them apart from other build options. +# All build and configuration options are prefaced with FWE_ option(FWE_STATIC_LINK "Statically link external libs" OFF) option(FWE_CODE_COVERAGE "Enables code coverage" OFF) option(FWE_VALGRIND "Enable valgrind tests" OFF) @@ -27,11 +25,12 @@ option(FWE_WERROR "Enable -Werror compiler flag" OFF) option(FWE_SECURITY_COMPILE_FLAGS "Add security related compile options" OFF) option(FWE_AWS_SDK_SHARED_LIBS "Use AWS SDK shared libs. Needs to be set to the same value of BUILD_SHARED_LIBS that the SDK was compiled with." OFF) option(FWE_AWS_SDK_EXTRA_LIBS "Extra libs required to link with the AWS SDK. When FWE_STATIC_LINK is ON, setting this to ON will automatically find the standard libs. Can be a space-separated list of libs." ON) -option(FWE_FEATURE_CAMERA "Enable Camera Data Collection feature" OFF) +option(FWE_FEATURE_GREENGRASSV2 "Enable Greengrass connection module" OFF) option(FWE_FEATURE_CUSTOM_DATA_SOURCE "Include the custom data source interface" OFF) option(FWE_FEATURE_IWAVE_GPS "Include the IWave GPS example for a custom data source" OFF) option(FWE_FEATURE_EXTERNAL_GPS "Include the external GPS example for a custom data source" OFF) -option(FWE_BUILD_EXECUTABLE "Build the FleetWise Edge executable" ON) +option(FWE_FEATURE_AAOS_VHAL "Include the Android Automotive VHAL example for a custom data source" OFF) +option(FWE_BUILD_EXECUTABLE "Build the executable" ON) option(FWE_BUILD_ANDROID_SHARED_LIBRARY "Build the android shared library" OFF) if(FWE_FEATURE_IWAVE_GPS) add_compile_options("-DFWE_FEATURE_IWAVE_GPS") @@ -39,6 +38,12 @@ endif() if(FWE_FEATURE_EXTERNAL_GPS) add_compile_options("-DFWE_FEATURE_EXTERNAL_GPS") endif() +if(FWE_FEATURE_AAOS_VHAL) + add_compile_options("-DFWE_FEATURE_AAOS_VHAL") +endif() +if(FWE_FEATURE_GREENGRASSV2) + add_compile_options("-DFWE_FEATURE_GREENGRASSV2") +endif() # Define the default build type if(NOT CMAKE_BUILD_TYPE) @@ -48,10 +53,6 @@ endif() include(cmake/graphviz.cmake) include(cmake/compiler_gcc.cmake) include(cmake/protobufs.cmake) -if(FWE_FEATURE_CAMERA) - add_compile_options("-DFWE_FEATURE_CAMERA") - include(cmake/ddsidls.cmake) -endif() include(cmake/snappy.cmake) include(CTest) include(cmake/unit_test.cmake) diff --git a/README.md b/README.md index 22648caa..44b702ad 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,32 @@ # Reference Implementation for AWS IoT FleetWise > :information_source: To quickly get started, jump to the -> [Developer Guide](./docs/dev-guide/edge-agent-dev-guide.md), the +> [Edge Agent Developer Guide](./docs/dev-guide/edge-agent-dev-guide.md), the > [Android Guide](./tools/android-app/README.md), or the > [Raspberry Pi Tutorial](./docs/rpi-tutorial/raspberry-pi-tutorial.md) AWS IoT FleetWise is a service that makes it easy for Automotive OEMs, Fleet operators, Independent -Software vendors (ISVs) to collect, store, organize, and monitor data from vehicles at scale. Edge -Agent Reference Implementation for AWS IoT FleetWise (the “Reference Implementation”) provides C++ -libraries that can be run with simulated vehicle data on certain supported vehicle hardware or that -can help you develop an Edge Agent to run an application on your vehicle that integrates with AWS -IoT FleetWise. You can then use AWS IoT FleetWise's to process the collected data, gain insights -about the vehicle's health and use the service's visual interface to help diagnose and troubleshoot -potential issues with your vehicles. Furthermore, AWS IoT FleetWise's capability to collect ECU data -and store them on cloud databases enables you to utilize different AWS services (Analytics Services, -ML, etc.) to develop novel use-cases that augment your existing vehicle functionality. In -particular, AWS IoT FleetWise can leverage fleet data (Big Data) and enable you to develop use cases -that create business value, for example: improve electric vehicle range estimation, optimized -battery life charging, optimized vehicle routing, etc. AWS IoT FleetWise can be extended to utilize -cloud computing capabilities for use-cases such as helping to improve pet/child detection, Driver -Monitoring System applications, Predictive Diagnostics, electric vehicle's battery cells outlier -detection, etc. You can use the included sample C++ application to learn more about the Reference -Implementation, develop an Edge Agent for your use case and test interactions before integration. +Software vendors (ISVs) to collect, store, organize, and monitor data from vehicles at scale. The +Reference Implementation for AWS IoT FleetWise ("FWE") provides C++ libraries that can be run with +simulated vehicle data on certain supported vehicle hardware or that can help you develop an Edge +Agent to run an application on your vehicle that integrates with AWS IoT FleetWise. You can then use +AWS IoT FleetWise's to process the collected data, gain insights about the vehicle's health and use +the service's visual interface to help diagnose and troubleshoot potential issues with your +vehicles. Furthermore, AWS IoT FleetWise's capability to collect ECU data and store them on cloud +databases enables you to utilize different AWS services (Analytics Services, ML, etc.) to develop +novel use-cases that augment your existing vehicle functionality. In particular, AWS IoT FleetWise +can leverage fleet data (Big Data) and enable you to develop use cases that create business value, +for example: improve electric vehicle range estimation, optimized battery life charging, optimized +vehicle routing, etc. AWS IoT FleetWise can be extended to utilize cloud computing capabilities for +use-cases such as helping to improve pet/child detection, Driver Monitoring System applications, +Predictive Diagnostics, electric vehicle's battery cells outlier detection, etc. You can use the +included sample C++ application to learn more about the FWE, develop an Edge Agent for your use case +and test interactions before integration. > _**Important**_ As provided in the AWS IoT FleetWise > [Service Terms](https://aws.amazon.com/service-terms/), you are solely responsible for your Edge > Agent, including ensuring that your Edge Agent and any updates and modifications to it are -> deployed and maintained safely and securely in any vehicles +> deployed and maintained safely and securely in any vehicles. ## AWS IoT FleetWise Architecture @@ -49,7 +49,7 @@ The following diagram illustrates a high-level architecture of the system. -**Edge Agent receives two documents:** +**FWE receives two documents:** 1. _Decoder Manifest_ - this document describes how signals are collected from the vehicle, and will include details such as, but not limited to: Bus ID, network name, decoding information, etc. @@ -59,11 +59,11 @@ The following diagram illustrates a high-level architecture of the system. be collected, for example, when Vehicle Speed > 100 km/Hr and Driver Seatbelt is Off and Ambient Temperature < 0 degree C. -## Edge Agent Deployment & Supported Platforms +## FWE Deployment & Supported Platforms -Edge Agent functional flexibility and its use of dynamic memory allocation means that it cannot -reside in the real-time safety vehicle ECUs. Edge Agent must also be connected to the internet and -preferably has access to a “good” portion of vehicle ECU data. OEMs have the flexibility to decide +The functional flexibility of FWE and its use of dynamic memory allocation means that it cannot +reside in the real-time safety vehicle ECUs. FWE must also be connected to the internet and +preferably has access to a "good" portion of vehicle ECU data. OEMs have the flexibility to decide where they can deploy their Edge Agent binary. Possible options include (if present): 1. Vehicle Gateway such as the @@ -74,13 +74,11 @@ where they can deploy their Edge Agent binary. Possible options include (if pres 3. Vehicle's High Performance Computer 4. Telecommunication Control Unit -Edge Agent Reference Implementation for AWS IoT FleetWise was built and tested on 64-bit -architectures. It has been tested on both ARM and X86 multicore based machines, with a Linux Kernel -version of 5.4 and above. The kernel module for ISO-TP (can-isotp ) would need to be installed in -addition for Kernels below 5.10 if you use the Reference Implementation. +FWE was built and tested on 64-bit architectures. It has been tested on both ARM and X86 multicore +based machines, with a Linux Kernel version of 5.4 and above. The kernel module for ISO-TP +(`can-isotp`) would need to be installed in addition for Kernels below 5.10. -Edge Agent Reference Implementation for AWS IoT FleetWise was also tested on an EC2 Instance with -the following details: +FWE was also tested on an EC2 Instance with the following details: - **Platform**: Ubuntu - **Platform Details**: Linux/UNIX @@ -93,13 +91,12 @@ the following details: ## AWS IoT FleetWise Client-Server Communication -Edge Agent Reference Implementation for AWS IoT FleetWise relies on -[AWS SDK for C++](https://github.com/aws/aws-sdk-cpp) to send and receive data from and to AWS IoT -FleetWise Server. All data sent to AWS IoT FleetWise Server using the Reference Implementation is -sent over an encrypted +FWE depends on the [AWS SDK for C++](https://github.com/aws/aws-sdk-cpp) to send and receive data +from and to AWS IoT FleetWise Server. All data sent to the AWS IoT FleetWise server is sent over an +encrypted [TLS connection](https://docs.aws.amazon.com/iot/latest/developerguide/data-encryption.html) using -MQTT, HTTPS, and WebSocket protocols, which is designed to make it secure by default while in -transit. AWS IoT FleetWise uses MQTT quality of service zero (QoS = 0). +MQTT, which is designed to make it secure by default while in transit. FWE uses MQTT quality of +service zero (QoS = 0). ## Security @@ -107,10 +104,10 @@ See [SECURITY](./SECURITY.md) for more information ## License Summary and Build Dependencies -Edge Agent Reference Implementation for AWS IoT FleetWise depends on the following open source -libraries. Refer to the corresponding links for more information. +FWE depends on the following open source libraries. Refer to the corresponding links for more +information. -- [AWS SDK for C++: v1.11.94](https://github.com/aws/aws-sdk-cpp) +- [AWS SDK for C++: v1.11.111](https://github.com/aws/aws-sdk-cpp) - [Curl: v7.58.0](https://github.com/curl/curl) - [OpenSSL: v1.1.1](https://github.com/openssl/openssl) - [zlib: v1.2.11](https://github.com/madler/zlib) @@ -121,21 +118,17 @@ libraries. Refer to the corresponding links for more information. - [JsonCpp: v1.9.5](https://github.com/open-source-parsers/jsoncpp) - [Snappy: v1.1.8](https://github.com/google/snappy) -Optional: The following dependencies are only required when the experimental option -`FWE_FEATURE_CAMERA` is enabled. +Optional: The following dependencies are only required when the option `FWE_FEATURE_GREENGRASSV2` is +enabled. -- [Fast-DDS: v2.3.3](https://github.com/eProsima/Fast-DDS) - - [Fast-CDR: v1.0.21](https://github.com/eProsima/Fast-CDR) - - [Foonathan Memory Vendor: v1.1.0](https://github.com/eProsima/foonathan_memory_vendor) - - [Foonathan Memory: v0.7](https://github.com/foonathan/memory) - - [TinyXML-2: v6.0.0](https://github.com/leethomason/tinyxml2) +- [AWS IoT Device SDK for C++ v2: v1.22.0](https://github.com/aws/aws-iot-device-sdk-cpp-v2) See [LICENSE](./LICENSE) for more information. ## Getting Help [Contact AWS Support](https://aws.amazon.com/contact-us/) if you have any technical questions about -Edge Agent Reference Implementation for AWS IoT FleetWise. +FWE. ## Metrics @@ -144,15 +137,14 @@ accessed. ## Resources -The following documents provide more information about AWS IoT FleetWise Edge. +The following documents provide more information about FWE. 1. [Change Log](./CHANGELOG.md) provides a summary of feature enhancements, updates, and resolved and known issues. -2. [AWS IoT FleetWise Edge Offboarding](./docs/AWS-IoTFleetWiseOffboarding.md) provides a summary of - the steps needed on the Client side to off board from the service. -3. [AWS IoT FleetWise Edge Agent Developer Guide](./docs/dev-guide/edge-agent-dev-guide.md) provides - step-by-step instructions for building and running Edge Agent Reference Implementation for AWS - IoT FleetWise. +1. [Offboarding and Data Deletion](./docs/AWS-IoTFleetWiseOffboarding.md) provides a summary of the + steps needed on the client side to offboard from the service. +1. [Edge Agent Developer Guide](./docs/dev-guide/edge-agent-dev-guide.md) provides step-by-step + instructions for building and running your Edge Agent. The following documents provide more information about the cloud component of AWS IoT FleetWise. diff --git a/THIRD-PARTY-LICENSES b/THIRD-PARTY-LICENSES index 96f0703c..1fcd86be 100644 --- a/THIRD-PARTY-LICENSES +++ b/THIRD-PARTY-LICENSES @@ -1,9 +1,8 @@ -AWS IoT FleetWise Edge includes the following third-party software/licensing: +The Reference Implementation for AWS IoT FleetWise includes the following third-party +software/licensing: -** AWS SDK for C++: v1.11.94 - https://github.com/aws/aws-sdk-cpp -** Fast-DDS: v2.3.3 - https://github.com/eProsima/Fast-DDS -** Fast-CDR: v1.0.21 - https://github.com/eProsima/Fast-CDR -** Foonathan Memory Vendor: v1.1.0 - https://github.com/eProsima/foonathan_memory_vendor +** AWS SDK for C++: v1.11.111 - https://github.com/aws/aws-sdk-cpp +** AWS IoT Device SDK for C++ v2: v1.22.0 - https://github.com/aws/aws-iot-device-sdk-cpp-v2 ** Google Benchmark: v1.6.1 - https://github.com/google/benchmark ** OpenSSL: v1.1.1 - https://github.com/openssl/openssl @@ -380,8 +379,8 @@ Some of the benchmark data in testdata/ is licensed differently: for more information. - paper-100k.pdf is an excerpt (bytes 92160 to 194560) from the paper - “Combinatorial Modeling of Chromatin Features Quantitatively Predicts DNA - Replication Timing in _Drosophila_” by Federico Comoglio and Renato Paro, + "Combinatorial Modeling of Chromatin Features Quantitatively Predicts DNA + Replication Timing in _Drosophila_" by Federico Comoglio and Renato Paro, which is licensed under the CC-BY license. See http://www.ploscompbiol.org/static/license for more ifnormation. @@ -392,55 +391,6 @@ Some of the benchmark data in testdata/ is licensed differently: (http://www.gutenberg.org/ebooks/53). ----------------- - -** Foonathan Memory: v0.7 - https://github.com/foonathan/memory - -Copyright (C) 2015-2020 Jonathan Müller - -This software is provided 'as-is', without any express or -implied warranty. In no event will the authors be held -liable for any damages arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute -it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; - you must not claim that you wrote the original software. - If you use this software in a product, an acknowledgment - in the product documentation would be appreciated but - is not required. - -2. Altered source versions must be plainly marked as such, - and must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any - source distribution. - ----------------- - -** TinyXML-2: v6.0.0 - https://github.com/leethomason/tinyxml2 - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. - ---------------- ** Curl: v7.58.0 - https://github.com/curl/curl diff --git a/cmake/ddsidls.cmake b/cmake/ddsidls.cmake deleted file mode 100644 index e7811418..00000000 --- a/cmake/ddsidls.cmake +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - -# Generate C++ code from the DDS IDLs, required to interact with -# devices connected to FWE via a DDS backbone. -# The IDLs are part of a git submodule and this cmake file -# invokes the fastDDS Gen tool to generate the sources locally. -# These are the library names and aliases - -# FastDDS dependencies -find_package(fastrtps REQUIRED) -find_package(fastcdr REQUIRED) - -set(libraryTargetName iotfleetwise.idls) -set(libraryAliasName IoTFleetWise::Idls) - -# This directory will contain idls files and autogenerated source and headers -set(idlsDir ${CMAKE_BINARY_DIR}/idls/) - -set(idlsRoot "${CMAKE_CURRENT_SOURCE_DIR}/interfaces/fastdds/idl") -message(STATUS "IDLs root is: ${idlsRoot}") - -# Make a list of all the idl files -file(GLOB_RECURSE idlFilesExternal FOLLOW_SYMLINKS ${idlsRoot}/*.idl) - -# Copy the idl files to the build tree. -file(COPY ${idlFilesExternal} DESTINATION ${idlsDir}) - -foreach(idlFileName ${idlFilesExternal}) - get_filename_component(idlFile ${idlFileName} NAME) - - message(STATUS "File name is: ${idlFile}") - - if(idlFile) - message(STATUS "IDL file found.") - else() - message(FATAL_ERROR "IDL file not found.") - endif() - # Invoke the fastddsgen command - execute_process( - COMMAND fastddsgen -d ${idlsDir} ${idlFile} - WORKING_DIRECTORY ${idlsDir}) - -endforeach() -# Find the sources and the headers -FILE(GLOB sources ${idlsDir}/*.c*) -FILE(GLOB headers ${idlsDir}/*.h) - -# Create the target with the given sources and headers -add_custom_target(idlGenTarget DEPENDS ${sources} ${headers}) - -add_library( - ${libraryTargetName} - ${sources} -) - -add_dependencies(${libraryTargetName} idlGenTarget) - -target_link_libraries( - ${libraryTargetName} - PUBLIC - fastrtps - fastcdr -) -target_include_directories( - ${libraryTargetName} - PUBLIC - ${idlsDir} -) -set_target_properties(${libraryTargetName} PROPERTIES LINKER_LANGUAGE CXX) -add_library(${libraryAliasName} ALIAS ${libraryTargetName}) diff --git a/cmake/doxygen.cmake b/cmake/doxygen.cmake index 7a73beea..552ce167 100644 --- a/cmake/doxygen.cmake +++ b/cmake/doxygen.cmake @@ -25,7 +25,7 @@ if(DOXYGEN_FOUND) set(DOXYGEN_MACRO_EXPANSION YES) set(DOXYGEN_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/docs") set(DOXYGEN_PREDEFINED IOTFLEETWISE_LINUX) - set(DOXYGEN_PROJECT_NAME "AWS IoT FleetWise Edge") + set(DOXYGEN_PROJECT_NAME "Reference Implementation for AWS IoT FleetWise") set(DOXYGEN_QUIET YES) set(DOXYGEN_SOURCE_BROWSER YES) set(DOXYGEN_TEMPLATE_RELATIONS YES) diff --git a/configuration/static-config.json b/configuration/static-config.json index 835dcc55..f161fbeb 100644 --- a/configuration/static-config.json +++ b/configuration/static-config.json @@ -41,13 +41,15 @@ "internalParameters": { "readyToPublishDataBufferSize": 10000, "systemWideLogLevel": "Trace", - "dataReductionProbabilityDisabled": false + "dataReductionProbabilityDisabled": false, + "maximumAwsSdkHeapMemoryBytes": 10000000 }, "publishToCloudParameters": { "maxPublishMessageCount": 1000, "collectionSchemeManagementCheckinIntervalMs": 120000 }, "mqttConnection": { + "connectionType": "iotCore", "endpointUrl": "my-endpoint.my-region.amazonaws.com", "clientId": "VEHICLE_ID_GOES_HERE", "collectionSchemeListTopic": "$aws/iotfleetwise/vehicles/VEHICLE_ID_GOES_HERE/collection_schemes", diff --git a/docs/AWS-IoTFleetWiseOffboarding.md b/docs/AWS-IoTFleetWiseOffboarding.md index c6eb1212..7d7b2ff0 100644 --- a/docs/AWS-IoTFleetWiseOffboarding.md +++ b/docs/AWS-IoTFleetWiseOffboarding.md @@ -1,27 +1,27 @@ -## AWS IoT FleetWise Offboarding and Data Deletion +## Offboarding and Data Deletion -To delete all of your “free text data” from the ECU running AWS IoT FleetWise, please run the -following commands: +To delete all of your "free text data" from an ECU running the Reference Implementation for AWS IoT +FleetWise ("FWE"), please run the following commands: -1. Stop AWS IoT FleetWise +1. Stop FWE ```bash sudo systemctl stop fwe@0 ``` -2. Disable AWS IoT FleetWise +2. Disable FWE ```bash sudo systemctl disable fwe@0 ``` -3. Delete all persistent data files +3. Delete the persistent data files for FWE ```bash sudo rm -f /var/aws-iot-fleetwise/* ``` -4. Delete AWS IoT FleetWise configuration files +4. Delete the configuration files for FWE ```bash sudo rm -f /etc/aws-iot-fleetwise/* diff --git a/docs/dev-guide/edge-agent-dev-guide-nxp-s32g.md b/docs/dev-guide/edge-agent-dev-guide-nxp-s32g.md index afde39f6..1384748e 100644 --- a/docs/dev-guide/edge-agent-dev-guide-nxp-s32g.md +++ b/docs/dev-guide/edge-agent-dev-guide-nxp-s32g.md @@ -1,16 +1,16 @@ -# Getting started with AWS IoT FleetWise Edge Agent on NXP S32G +# Getting started on an NXP S32G board -This section describes how to deploy AWS IoT FleetWise Edge Agent onto an NXP S32G-VNP-RDB2 board. +This section describes how to get started on an NXP S32G-VNP-RDB2 board. ## Prerequisites -- **NXP Semiconductors S32G Reference Design 2** — Part number: S32G-VNP-RDB2 -- **AWS IoT FleetWise Edge Agent Compiled for ARM64** — If you are using an EC2 Graviton instance as - your development machine, you will have completed this already in - [previous step](./edge-agent-dev-guide.md#compile-aws-iot-fleetwise-edge-agent-software). +- **NXP Semiconductors S32G Reference Design 2:** Part number S32G-VNP-RDB2 +- **FWE Compiled for ARM64:** If you are using an EC2 Graviton instance as your development machine, + you will have completed this already in + [a previous step](./edge-agent-dev-guide.md#compile-your-edge-agent). - _If you are using a local Intel x86_64 (amd64) development machine_, you will need to run the - following to cross-compile AWS IoT FleetWise Edge Agent: + following to cross-compile your Edge Agent: ```bash cd ~/aws-iot-fleetwise-edge \ @@ -19,7 +19,7 @@ This section describes how to deploy AWS IoT FleetWise Edge Agent onto an NXP S3 && ./tools/build-fwe-cross-arm64.sh ``` -- **Internet Router with Ethernet** — The S32G-VNP-RDB2 must be connected to an internet router via +- **Internet Router with Ethernet:** The S32G-VNP-RDB2 must be connected to an internet router via an Ethernet cable for internet connectivity. It is beyond the scope of this document to describe how this is achieved, but one possibility is to use a WiFi to Ethernet bridge and a smartphone acting as an internet hotspot. @@ -28,8 +28,8 @@ This section describes how to deploy AWS IoT FleetWise Edge Agent onto an NXP S3 The following instructions use the development machine to build an SD-card image based on the Ubuntu variant of the NXP Linux BSP version 35.0, with the addition of the `can-isotp` kernel module -required by AWS IoT FleetWise Edge Agent, an updated version of the `canutils` package and a -`systemd` service called `setup-socketcan` for bringing up the CAN interfaces at startup. +required by FWE, an updated version of the `canutils` package and a `systemd` service called +`setup-socketcan` for bringing up the CAN interfaces at startup. 1. Run the following _on the development machine_ to install the dependencies of the `bitbake` tool of the Yocto / OpenEmbedded project. @@ -91,10 +91,9 @@ required by AWS IoT FleetWise Edge Agent, an updated version of the `canutils` p ## Provision AWS IoT Credentials -Run the following commands _on the development machine_ (after compiling AWS IoT FleetWise Edge -Agent for ARM64 as explained above), to create an IoT Thing and provision credentials for it. The -AWS IoT FleetWise Edge Agent binary and its configuration files will be packaged into a ZIP file -ready to be deployed to the board. +Run the following commands _on the development machine_ (after compiling FWE for ARM64 as explained +above), to create an IoT Thing and provision credentials for it. Your Edge Agent binary and its +configuration files will be packaged into a ZIP file ready to be deployed to the board. ```bash mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ @@ -119,7 +118,7 @@ mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ && cd .. && zip -r aws-iot-fleetwise-deploy.zip . ``` -## Deploy AWS IoT FleetWise Edge Agent on NXP S32G +## Deploy Edge Agent on NXP S32G board 1. Run the following _on your local machine_ to copy the deployment ZIP file from the EC2 machine to your local machine: @@ -137,7 +136,7 @@ mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ ``` 1. SSH to the S32G board, as described above, then run the following **_on the S32G_** to install - AWS IoT FleetWise Edge Agent as a service: + your Edge Agent as a service: ```bash mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ @@ -147,8 +146,7 @@ mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ && sudo ./tools/install-fwe.sh ``` -1. Run the following **_on the S32G_** to view and follow the AWS IoT FleetWise Edge Agent log - (press CTRL+C to exit): +1. Run the following **_on the S32G_** to view and follow the log (press CTRL+C to exit): ```bash sudo journalctl -fu fwe@0 --output=cat diff --git a/docs/dev-guide/edge-agent-dev-guide-renesas-rcar-s4.md b/docs/dev-guide/edge-agent-dev-guide-renesas-rcar-s4.md index 7b624c54..65146cfb 100644 --- a/docs/dev-guide/edge-agent-dev-guide-renesas-rcar-s4.md +++ b/docs/dev-guide/edge-agent-dev-guide-renesas-rcar-s4.md @@ -1,11 +1,11 @@ -# Getting started with AWS IoT FleetWise Edge Agent on Renesas R-Car S4 +# Getting started on a Renesas R-Car S4 board -This section describes how to deploy AWS IoT FleetWise Edge Agent onto an Renesas +This section describes how to get started on a Renesas [R-Car S4 Reference Board/Spider](https://www.renesas.com/jp/en/products/automotive-products/automotive-system-chips-socs/rtp8a779f0askb0sp2s-r-car-s4-reference-boardspider). ## Prerequisites -- **Renesas Electronics Corporation R-Car S4 Reference Board/Spider** +- **Renesas Electronics Corporation R-Car S4 Reference Board/Spider:** - Spider board has eMMC and micro SD-card slot but the can't use simalutaneously. - For using sdcard, please execute following two steps. The detail files can find in [R-Car S4 SDK Start Up Guide PKG](https://www.renesas.com/us/en/products/automotive-products/automotive-system-chips-socs/r-car-s4-automotive-system-chip-soc-car-servercommunication-gateway#design_development). @@ -16,11 +16,12 @@ This section describes how to deploy AWS IoT FleetWise Edge Agent onto an Renesa 1. Please change SW3 and SW6 on the CPU board. - For more details, please refer to section 3.7.8.1 "Enable SD Card" in "R-Car S4_StartupGuide_x_x_x.pdf" -- **AWS IoT FleetWise Edge Agent Compiled for ARM64** — If you are using an EC2 Graviton instance as - your development machine, you will have completed this already above. +- **FWE Compiled for ARM64:** If you are using an EC2 Graviton instance as your development machine, + you will have completed this already in + [a previous step](./edge-agent-dev-guide.md#compile-your-edge-agent). - _If you are using a local Intel x86_64 development machine running ubuntu 20.04_, you will need - to run the following to cross-compile AWS IoT FleetWise Edge Agent: + to run the following to cross-compile your Edge Agent: ```bash cd ~/aws-iot-fleetwise-edge @@ -29,7 +30,7 @@ This section describes how to deploy AWS IoT FleetWise Edge Agent onto an Renesa && ./tools/build-fwe-cross-arm64.sh ``` -- **Internet Router with Ethernet** — The R-Car S4 Spider board must be connected to an internet +- **Internet Router with Ethernet:** The R-Car S4 Spider board must be connected to an internet router via an Ethernet cable for internet connectivity. It is beyond the scope of this document to describe how this is achieved, but one possibility is to use a WiFi to Ethernet bridge and a smartphone acting as an internet hotspot. @@ -100,10 +101,10 @@ on the Ubuntu variant of the Renesas Linux BSP version 5.10.41. ## Provision AWS IoT Credentials -Run the following commands _on the development machine_ (after compiling AWS IoT FleetWise Edge -Agent for ARM64 as explained [above](#prerequisites)), to create an IoT Thing and provision -credentials for it. The AWS IoT FleetWise Edge Agent binary and its configuration files will be -packaged into a ZIP file ready to be deployed to the board. +Run the following commands _on the development machine_ (after compiling the FWE for ARM64 as +explained [above](#prerequisites)), to create an IoT Thing and provision credentials for it. Your +Edge Agent binary and its configuration files will be packaged into a ZIP file ready to be deployed +to the board. ```bash mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ @@ -128,7 +129,7 @@ mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ && cd .. && zip -r aws-iot-fleetwise-deploy.zip . ``` -## Deploy AWS IoT FleetWise Edge Agent software on R-Car S4 Spider board +## Deploy Edge Agent on R-Car S4 Spider board 1. Run the following _on your local machine_ to copy the deployment ZIP file from the EC2 machine to your local machine: @@ -146,7 +147,7 @@ mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ ``` 1. SSH to the R-Car S4 Spider board, as described above, then run the following **_on the R-Car S4 - Spider board_** to install AWS IoT FleetWise Edge Agent as a service: + Spider board_** to install your Edge Agent as a service: ```bash mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ @@ -159,8 +160,8 @@ mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ sudo -H ./tools/install-cansim.sh ``` -1. Run the following **_on the R-Car S4 Spider board_** to view and follow the AWS IoT FleetWise - Edge Agent log (press CTRL+C to exit): +1. Run the following **_on the R-Car S4 Spider board_** to view and follow the log (press CTRL+C to + exit): ```bash sudo journalctl -fu fwe@0 --output=cat diff --git a/docs/dev-guide/edge-agent-dev-guide.md b/docs/dev-guide/edge-agent-dev-guide.md index e7d0b1c1..5411ce72 100644 --- a/docs/dev-guide/edge-agent-dev-guide.md +++ b/docs/dev-guide/edge-agent-dev-guide.md @@ -1,6 +1,15 @@ -# AWS IoT FleetWise Edge Agent Developer Guide +# Edge Agent Developer Guide -**Copyright © Amazon Web Services, Inc. and/or its affiliates. All rights reserved.** +**Note:** AWS IoT FleetWise is currently available in US East (N. Virginia) and Europe (Frankfurt). + +**Topics** + +- [Introduction](#introduction) +- [Quick start demo](#quick-start-demo) +- [Getting started guide](#getting-started-guide) +- [Software Architecture](#software-architecture) + +**Copyright (C) Amazon Web Services, Inc. and/or its affiliates. All rights reserved.** Amazon's trademarks and trade dress may not be used in connection with any product or service that is not Amazon's, in any manner that is likely to cause confusion among customers, or in any manner @@ -8,71 +17,90 @@ that disparages or discredits Amazon. All other trademarks not owned by Amazon a their respective owners, who may or may not be affiliated with, connected to, or sponsored by Amazon. -**Note** - -- AWS IoT FleetWise is currently available in US East (N. Virginia) and Europe (Frankfurt). +## Introduction **AWS IoT FleetWise** provides a set of tools that enable automakers to collect, transform, and transfer vehicle data to the cloud at scale. With AWS IoT FleetWise you can build virtual representations of vehicle networks and define data collection rules to transfer only high-value data from your vehicles to AWS Cloud. -**AWS IoT FleetWise Edge Agent** software provides C++ libraries that enable you to run the -application on your vehicle. You can use AWS IoT FleetWise pre-configured analytic capabilities to -process collected data, gain insights about vehicle health, and use the service's visual interface -to help diagnose and troubleshoot potential issues with the vehicle. +**The Reference Implementation for AWS IoT FleetWise ("FWE")** provides C++ libraries that can be +run with simulated vehicle data on certain supported vehicle hardware or that can help you develop +an Edge Agent to run an application on your vehicle that integrates with AWS IoT FleetWise. You can +use AWS IoT FleetWise pre-configured analytic capabilities to process collected data, gain insights +about vehicle health, and use the service's visual interface to help diagnose and troubleshoot +potential issues with the vehicle. AWS IoT FleetWise's capability to collect ECU data and store them on cloud databases enables you to utilize different AWS services, such as Analytics Services, and ML, to develop novel use-cases that augment and/or supplement your existing vehicle functionality. In particular, AWS IoT FleetWise can help utilize fleet data (Big Data) to create value. For example, you can develop use cases that optimize vehicle routing, improve electric vehicle range estimation, and optimize battery life -charging. You can extend AWS IoT FleetWise other use cases such as for pet or child detection, -Driver Monitoring System applications, predictive diagnostics, and for outlier detection with an -electric vehicle's battery cells. - -You can use the included sample C++ application to learn more about AWS IoT FleetWise Edge Agent -interfaces and to test interactions before integration. - -**_The AWS IoT FleetWise in-vehicle software component is licensed to you under the -[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). You are solely -responsible for ensuring such software and any updates and modifications thereto are deployed and -maintained safely and securely in any vehicles and do not otherwise impact vehicle safety._** - -**Topics** - -- [AWS IoT FleetWise quick start demo](#aws-iot-fleetwise-quick-start-demo) -- [Getting started with AWS IoT FleetWise Edge Agent](#getting-started-with-aws-iot-fleetwise-edge-agent) -- [AWS IoT FleetWise Edge Agent Architecture](#aws-iot-fleetwise-edge-agent-architecture) - -# AWS IoT FleetWise quick start demo +charging. You can use the data ingested through AWS IoT FleetWise to develop applications for +predictive diagnostics, and for outlier detection with an electric vehicle's battery cells. + +You can use the included sample C++ application to learn more about the Reference Implementation, +develop an Edge Agent for your use case and test interactions before integration. + +This software is licensed under the +[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +### Disclaimer + +**_The Reference Implementation for AWS IoT FleetWise ("FWE") is intended to help you develop your +Edge Agent for AWS IoT FleetWise and includes sample code that you may reference or modify so your +Edge Agent meets your requirements. As provided in the AWS IoT FleetWise Service Terms, you are +solely responsible for your Edge Agent, including ensuring that your Edge Agent and any updates and +modifications thereto are deployed and maintained safely and securely in any vehicles._** + +**_This software code base includes modules that are still in development and are disabled by +default. These modules are not intended for use in a production environment. This includes a Remote +Profiler module that helps sending traces from the device to AWS Cloud Watch. FWE has been checked +for any memory leaks and runtime errors such as type overflows using Valgrind. No issues have been +detected during the load tests._** + +**_Note that vehicle data collected through your use of AWS IoT FleetWise is intended for +informational purposes only (including to help you train cloud-based artificial intelligence and +machine learning models), and you may not use AWS IoT FleetWise to control or operate vehicle +functions. You are solely responsible for all liability that may arise in connection with any use +outside of AWS IoT FleetWise's intended purpose and in any manner contrary to applicable vehicle +regulations. Vehicle data collected through your use of AWS IoT FleetWise should be evaluated for +accuracy as appropriate for your use case, including for purposes of meeting any compliance +obligations you may have under applicable vehicle safety regulations (such as safety monitoring and +reporting obligations). Such evaluation should include collecting and reviewing information through +other industry standard means and sources (such as reports from drivers of vehicles). You and your +End Users are solely responsible for all decisions made, advice given, actions taken, and failures +to take action based on your use of AWS IoT FleetWise._** + +# Quick start demo This guide is intended to quickly demonstrate the basic features of AWS IoT FleetWise by firstly -deploying the AWS IoT FleetWise Edge Agent to an AWS EC2 instance representing one or more simulated -vehicles. A script is then run using the AWS CLI to control AWS IoT FleetWise Cloud in order collect -data from the vehicle. +deploying FWE to an AWS EC2 instance representing one or more simulated vehicles. A script is then +run using the AWS CLI to control AWS IoT FleetWise in order collect data from the simulated vehicle. This guide showcases AWS IoT FleetWise at a high level. If you are interested in exploring AWS IoT -FleetWise at a more detailed technical level, see -[Getting started with AWS IoT FleetWise Edge Agent](#getting-started-with-aws-iot-fleetwise-edge-agent). +FleetWise at a more detailed technical level, see the +[Getting started guide](#getting-started-guide). **Topics:** - [Prerequisites for quick start demo](#prerequisites-for-quick-start-demo) -- [Deploy the AWS IoT FleetWise Edge Agent software](#deploy-the-aws-iot-fleetwise-edge-agent-software) -- [Use the AWS IoT FleetWise cloud demo](#use-the-aws-iot-fleetwise-cloud-demo) +- [Deploy Edge Agent](#deploy-edge-agent) +- [Use the AWS IoT FleetWise demo](#use-the-aws-iot-fleetwise-demo) - [Explore collected data](#explore-collected-data) ## Prerequisites for quick start demo -This guide assumes you have already logged in to the AWS console in your desired region using an -account with administrator rights. - -- Note: AWS IoT FleetWise is currently available in US East (N. Virginia) and Europe (Frankfurt). +- Access to an AWS Account with administrator privileges. +- Logged in to the AWS Console in your desired region using the account with administrator + privileges. + - **Note:** AWS IoT FleetWise is currently available in US East (N. Virginia) and Europe + (Frankfurt). -## Deploy the AWS IoT FleetWise Edge Agent software +## Deploy Edge Agent -An AWS CloudFormation template is used to deploy the AWS IoT FleetWise Edge Agent to a new AWS EC2 +After reviewing [the FWE source code](../../src/), to ensure that it meets your use case and +requirements, you can use the following CloudFormation template to deploy FWE to a new AWS EC2 instance. 1. Click here to @@ -85,22 +113,21 @@ instance. 1. Choose **Create stack**. 1. Wait until the status of the Stack is 'CREATE_COMPLETE', this will take approximately 10 minutes. -AWS IoT FleetWise Edge Agent software has been deployed to an AWS EC2 Graviton (ARM64) Instance -along with credentials that allow it to connect to AWS IoT Core. CAN data is also being generated on -the EC2 instance to simulate periodic hard-braking events. The AWS IoT FleetWise Cloud demo script -in the following section will deploy a campaign to the simulated fleet of vehicles to capture the -engine torque when a hard braking-event occurs. +FWE has been deployed to an AWS EC2 Graviton (ARM64) Instance along with credentials that allow it +to connect to AWS IoT Core. CAN data is also being generated on the EC2 instance to simulate +periodic hard-braking events. The AWS IoT FleetWise demo script in the following section will deploy +a campaign to the simulated fleet of vehicles to capture the engine torque when a hard braking-event +occurs. -## Use the AWS IoT FleetWise cloud demo +## Use the AWS IoT FleetWise demo The instructions below will register your AWS account for AWS IoT FleetWise, create a demonstration vehicle model, register the virtual vehicle created in the previous section and run a campaign to collect data from it. 1. Open the AWS CloudShell: [Launch CloudShell](https://console.aws.amazon.com/cloudshell/home) -1. Copy and paste the following commands to clone the latest AWS IoT FleetWise Edge Agent software - from GitHub, install the dependencies of the cloud demo script and enable latest IoT FleetWise - commands in the AWS CLI. +1. Copy and paste the following commands to clone the latest FWE software from GitHub, install the + dependencies of the demo script and enable latest IoT FleetWise commands in the AWS CLI. ```bash git clone https://github.com/aws/aws-iot-fleetwise-edge.git ~/aws-iot-fleetwise-edge \ @@ -117,7 +144,7 @@ collect data from it. rm -rf ./aws* ``` - The AWS IoT FleetWise Cloud demo script performs the following: + The AWS IoT FleetWise demo script performs the following: - Registers your AWS account with AWS IoT FleetWise, if not already registered - Creates Timestream database and table for collected data for Timestream campaigns, if not @@ -184,14 +211,14 @@ collect data from it. ``` 1. When the script completes, a path to an HTML file is given in the format - `/home/cloudshell-user/aws-iot-fleetwise-cloud/fwdemo-.html`. Copy the path, then - click on the Actions drop down menu in the top-right corner of the CloudShell window and choose - **Download file**. Paste the path to the file, choose **Download**, and open the downloaded file - in your browser. + `/home/cloudshell-user/aws-iot-fleetwise-edge/fwdemo-*.html`. Copy the path, then click on the + Actions drop down menu in the top-right corner of the CloudShell window and choose **Download + file**. Paste the path to the file, choose **Download**, and open the downloaded file in your + browser. If you enabled S3 upload, results are stored in - `/home/cloudshell-user/aws-iot-fleetwise-cloud/fwdemo--s3-json-result.html` and - `/home/cloudshell-user/aws-iot-fleetwise-cloud/fwdemo--s3-parquet-result.html` + `/home/cloudshell-user/aws-iot-fleetwise-edge/fwdemo-*-s3-json-result.html` and + `/home/cloudshell-user/aws-iot-fleetwise-edge/fwdemo-*-s3-parquet-result.html` ## Explore collected data @@ -203,56 +230,56 @@ collect data from it. ![](./images/collected_data_plot.png) -# Getting started with AWS IoT FleetWise Edge Agent +# Getting started guide -This guide is intended to demonstrate the basic features of AWS IoT FleetWise by firstly building -AWS IoT FleetWise Edge and running it on a development machine (locally or on AWS EC2) in order to -represent a simulated vehicle. A script is then run to interact with AWS IoT FleetWise Cloud in -order collect data from the simulated vehicle. Instructions are also provided for running AWS IoT -FleetWise Edge Agent on an NXP S32G-VNP-RDB2 development board or Renesas R-Car S4 Spider board and +This guide is intended to demonstrate the basic features of AWS IoT FleetWise by firstly allowing +you to build your own Edge Agent and running it on a development machine (locally or on AWS EC2) in +order to represent a simulated vehicle. A script is then run to interact with AWS IoT FleetWise in +order to collect data from the simulated vehicle. Instructions are also provided for running your +own Edge Agent on an NXP S32G-VNP-RDB2 development board or Renesas R-Car S4 Spider board and deploying a campaign to collect OBD data. -This guide covers building AWS IoT FleetWise at a detailed technical level, using a development -machine to build and run the Edge Agent executable. If you would prefer to learn about AWS IoT -FleetWise at a higher level that does not require use of a development machine, see -[AWS IoT FleetWise quick start demo](#aws-iot-fleetwise-quick-start-demo). +This guide covers building your Edge Agent at a detailed technical level, using a development +machine to build and run the executable. If you would prefer to learn about AWS IoT FleetWise at a +higher level that does not require use of a development machine, see the +[Quick start demo](#quick-start-demo). #### Topics: -- [Getting started with AWS IoT FleetWise on a development machine](#getting-started-with-aws-iot-fleetwise-on-a-development-machine) +- [Getting started on a development machine](#getting-started-on-a-development-machine) - [Prerequisites for development machine](#prerequisites-for-development-machine) - [Launch your development machine](#launch-your-development-machine) - - [Compile AWS IoT FleetWise Edge Agent software](#compile-aws-iot-fleetwise-edge-agent-software) - - [Deploy AWS IoT FleetWise Edge Agent software](#deploy-aws-iot-fleetwise-edge-agent-software) + - [Compile your Edge Agent](#compile-your-edge-agent) + - [Deploy your Edge Agent](#deploy-your-edge-agent) - [Run the AWS IoT FleetWise demo script](#run-the-aws-iot-fleetwise-demo-script) -- [Getting started with AWS IoT FleetWise Edge Agent on a NXP S32G board](./edge-agent-dev-guide-nxp-s32g.md) +- [Getting started on an NXP S32G board](./edge-agent-dev-guide-nxp-s32g.md) - [Prerequisites for NXP S32G](./edge-agent-dev-guide-nxp-s32g.md#prerequisites) - [Build an SD-Card Image](./edge-agent-dev-guide-nxp-s32g.md#build-an-sd-card-image) - [Flash the SD-Card Image](./edge-agent-dev-guide-nxp-s32g.md#flash-the-sd-card-image) - [Specify initial board configuration](./edge-agent-dev-guide-nxp-s32g.md#specify-initial-board-configuration) - [Provision AWS IoT Credentials](./edge-agent-dev-guide-nxp-s32g.md#provision-aws-iot-credentials) - - [Deploy AWS IoT FleetWise Edge Agent on NXP S32G](./edge-agent-dev-guide-nxp-s32g.md#deploy-aws-iot-fleetwise-edge-agent-on-nxp-s32g) + - [Deploy Edge Agent on NXP S32G board](./edge-agent-dev-guide-nxp-s32g.md#deploy-edge-agent-on-nxp-s32g-board) - [Collect OBD Data](./edge-agent-dev-guide-nxp-s32g.md#collect-obd-data) -- [Getting started with AWS IoT FleetWise Edge Agent on a Renesas R-Car S4 board](./edge-agent-dev-guide-renesas-rcar-s4.md) +- [Getting started on a Renesas R-Car S4 board](./edge-agent-dev-guide-renesas-rcar-s4.md) - [Prerequisites](./edge-agent-dev-guide-renesas-rcar-s4.md#prerequisites) - [Build an SD-Card Image](./edge-agent-dev-guide-renesas-rcar-s4.md#build-an-sd-card-image) - [Flash the SD-Card Image](./edge-agent-dev-guide-renesas-rcar-s4.md#flash-the-sd-card-image) - [Specify initial board configuration](./edge-agent-dev-guide-renesas-rcar-s4.md#specify-initial-board-configuration) - [Provision AWS IoT Credentials](./edge-agent-dev-guide-renesas-rcar-s4.md#provision-aws-iot-credentials) - - [Deploy and run AWS IoT FleetWise Edge Agent software on R-Car S4 Spider board](./edge-agent-dev-guide-renesas-rcar-s4.md#deploy-aws-iot-fleetwise-edge-agent-software-on-r-car-s4-spider-board) + - [Deploy Edge Agent on R-Car S4 Spider board](./edge-agent-dev-guide-renesas-rcar-s4.md#deploy-edge-agent-on-r-car-s4-spider-board) - [Collect OBD Data](./edge-agent-dev-guide-renesas-rcar-s4.md#collect-obd-data) -## Getting started with AWS IoT FleetWise on a development machine +## Getting started on a development machine -This section describes how to get started with AWS IoT FleetWise Edge Agent on a development -machine. +This section describes how to get started on a development machine. ### Prerequisites for development machine - Access to an AWS Account with administrator privileges. - Logged in to the AWS Console in your desired region using the account with administrator privileges. - - Note: AWS IoT FleetWise is currently available in US East (N. Virginia) and Europe (Frankfurt). + - **Note:** AWS IoT FleetWise is currently available in US East (N. Virginia) and Europe + (Frankfurt). - A local Linux or MacOS machine. ### Launch your development machine @@ -280,21 +307,21 @@ launch an AWS EC2 Graviton (arm64) instance. Pricing for EC2 can be found, ssh -i ubuntu@ ``` -### Compile AWS IoT FleetWise Edge Agent software +### Compile your Edge Agent -1. Run the following _on the development machine_ to clone the latest AWS IoT FleetWise Edge Agent - software from GitHub. +1. Run the following _on the development machine_ to clone the latest FWE source code from GitHub. ```bash git clone https://github.com/aws/aws-iot-fleetwise-edge.git ~/aws-iot-fleetwise-edge \ && cd ~/aws-iot-fleetwise-edge ``` - You can browse the [README.md](../../README.md) and source code while referring to this guide. +1. Review, modify and supplement [the FWE source code](../../src/) to ensure it meets your use case + and requirements. -1. Install the AWS IoT FleetWise Edge Agent dependencies +1. Install the dependencies for FWE by running the commands below. - Commands below will: + The commands below will: 1. Install the following Ubuntu packages: `libssl-dev libboost-system-dev libboost-log-dev libboost-thread-dev build-essential cmake unzip git wget curl zlib1g-dev libcurl4-openssl-dev libsnappy-dev default-jre libasio-dev`. @@ -315,16 +342,16 @@ launch an AWS EC2 Graviton (arm64) instance. Pricing for EC2 can be found, && sudo -H ./tools/install-cansim.sh ``` -1. Run the following to compile AWS IoT FleetWise Edge Agent software: +1. Run the following to compile your own Edge Agent: ```bash ./tools/build-fwe-native.sh ``` -### Deploy AWS IoT FleetWise Edge Agent software +### Deploy your Edge Agent 1. Run the following _on the development machine_ to provision an AWS IoT Thing with credentials and - install AWS IoT FleetWise Edge Agent as a service. + install your Edge Agent as a service. **Note** To create AWS IoT things in Europe (Frankfurt), configure `--region` to `eu-central-1` in the call to `provision.sh` @@ -347,16 +374,16 @@ launch an AWS EC2 Graviton (arm64) instance. Pricing for EC2 can be found, && sudo ./tools/install-fwe.sh ``` - 1. At this point AWS IoT FleetWise Edge Agent is running and periodically sending 'checkins' to - AWS IoT FleetWise, in order to announce its current list of campaigns (which at this stage - will be an empty list). CAN data is also being generated on the development machine to - simulate periodic hard-braking events. The AWS IoT FleetWise Cloud demo script in the - following section will deploy a campaign to the simulated vehicle to capture the engine torque - when a hard braking-event occurs. + 1. At this point your Edge Agent is running and periodically sending 'checkins' to AWS IoT + FleetWise, in order to announce its current list of campaigns (which at this stage will be an + empty list). CAN data is also being generated on the development machine to simulate periodic + hard-braking events. The AWS IoT FleetWise demo script in the following section will deploy a + campaign to the simulated vehicle to capture the engine torque when a hard braking-event + occurs. -1. Run the following to view and follow the AWS IoT FleetWise Edge Agent log. You can open a new SSH - session with the development machine and run this command to follow the log in real time as the - campaign is deployed in the next section. To exit the logs, use CTRL + C. +1. Run the following to view and follow the log for your Edge Agent. You can open a new SSH session + with the development machine and run this command to follow the log in real time as the campaign + is deployed in the next section. To exit the logs, use CTRL + C. ```bash sudo journalctl -fu fwe@0 --output=cat @@ -369,7 +396,7 @@ vehicle model, register the virtual vehicle created in the previous section, and collect data from it. 1. Run the following _on the development machine_ to install the dependencies of the AWS IoT - FleetWise Cloud demo script: + FleetWise demo script: 1. Following command installs the following Ubuntu packages: `python3 python3-pip`. It then installs the following PIP packages: `wrapt plotly pandas cantools boto3 fastparquet` @@ -499,15 +526,15 @@ collect data from it. ./demo.sh --vehicle-name fwdemo-ec2 --dbc-file --campaign-file --region eu-central-1 ``` -## Getting started with AWS IoT FleetWise Edge Agent on NXP S32G +## Getting started on a NXP S32G board -[Getting started with AWS IoT FleetWise Edge Agent on NXP S32G](./edge-agent-dev-guide-nxp-s32g.md) +[Getting started on an NXP S32G board](./edge-agent-dev-guide-nxp-s32g.md) -## Getting started with AWS IoT FleetWise Edge Agent on Renesas S4 +## Getting started on an Renesas S4 board -[Getting started with AWS IoT FleetWise Edge Agent on Renesas R-Car S4](./edge-agent-dev-guide-renesas-rcar-s4.md) +[Getting started on a Renesas R-Car S4 board](./edge-agent-dev-guide-renesas-rcar-s4.md) -# AWS IoT FleetWise Edge Agent Architecture +# Software Architecture AWS IoT FleetWise is an AWS service that enables automakers to collect, store, organize, and monitor data from vehicles. Automakers need the ability to connect remotely to their fleet of vehicles and @@ -518,8 +545,8 @@ enable OEMs to optimize the data collection process by defining what signals to to collect them, and most importantly the trigger conditions, or events, that enable the collection process. -This document reviews the architecture, operation, and key features of the AWS IoT FleetWise Edge -Agent software. +This document reviews the architecture, operation, and key features of the Reference Implementation +for AWS IoT FleetWise ("FWE"). **Topics** @@ -539,7 +566,8 @@ Agent software. - [Security](#security) - [Best practices and recommendation](#best-practices-and-recommendation) - [Supported platforms](#supported-platforms) -- [Disclaimer](#disclaimer) +- [Getting help](#getting-help) +- [Resources](#resources) ## Terminology @@ -549,12 +577,11 @@ EngineSpeed, and RadarAmplitude. **Data Collection Scheme:** A document that defines the rules used to collect data from a vehicle. It defines an event trigger, filters, duration and frequency of data collection. This document is -used by AWS IoT FleetWise Edge Agent for collecting and filtering only relevant data. A data -collection scheme can use multiple event triggers. It can be attached to a single vehicle or a fleet -of vehicles. +used by FWE for collecting and filtering only relevant data. A data collection scheme can use +multiple event triggers. It can be attached to a single vehicle or a fleet of vehicles. -**Board Support Package (BSP):** A set of libraries that support running AWS IoT FleetWise Edge -Agent on a POSIX Operating System. +**Board Support Package (BSP):** A set of libraries that support running FWE on a POSIX Operating +System. **Controller Area Network (CAN):** A serial networking technology that enables vehicle electronic devices to interconnect. @@ -588,31 +615,29 @@ foundation to define data collection schemes and specify what data to collect an triggers to inspect data. AWS IoT FleetWise enables you to create campaigns that can be deployed to a fleet of vehicles. Once -a campaign is active, it is deployed from the cloud to the target vehicles via a push mechanism. AWS -IoT FleetWise Edge Agent software uses the underlying collection schemes to acquire sensor data from -the vehicle network. It applies the inspection rules and uploads the data back to AWS IoT FleetWise -data plane. The data plane persists collected data in the OEM's AWS Account; the account can then be -used to analyse the data. +a campaign is active, it is deployed from the cloud to the target vehicles via a push mechanism. FWE +uses the underlying collection schemes to acquire sensor data from the vehicle network. It applies +the inspection rules and uploads the data back to AWS IoT FleetWise data plane. The data plane +persists collected data in the OEM's AWS Account; the account can then be used to analyse the data. ### Software Layers -AWS IoT FleetWise architecture allocates its functionalities into a set of software layers that are -interdependent. The software layers exchange over abstract APIs that implement a dependency -inversion design pattern. The below section describes each of the layers bottom up as outline in the -figure above. +The functionality of FWE is dependent on a set of software layers that are interdependent. The +software layers exchange over abstract APIs that implement a dependency inversion design pattern. +The below section describes each of the layers bottom up as outline in the figure above. **Vehicle Data Acquisition** -This layer binds the AWS IoT FleetWise Edge Agent software with the vehicle network, leveraging the -onboard device drivers and middle-wares the OEM uses in the target hardware. It has a dependency on -the host environment including the operating system, the peripheral drivers and the vehicle -architecture overall. This layer listens and/or requests data from the vehicle network and -normalizes it, before applying various signal decoding rules that are vehicle protocol specific e.g. -CAN Signal database files. The output of this layer is a set of transformed key/value pairs of -signals and their corresponding values. This layer has a dependency on the decoding rules the OEM -has defined for the signals they want to inspect, which are specified in the Cloud Control plane. -This layer stores the decoded values into a signal history buffer. This signal history buffer has a -maximum fixed size to not exhaust the system resources. +This layer binds FWE with the vehicle network, leveraging the onboard device drivers and +middle-wares the OEM uses in the target hardware. It has a dependency on the host environment +including the operating system, the peripheral drivers and the vehicle architecture overall. This +layer listens and/or requests data from the vehicle network and normalizes it, before applying +various signal decoding rules that are vehicle protocol specific e.g. CAN Signal database files. The +output of this layer is a set of transformed key/value pairs of signals and their corresponding +values. This layer has a dependency on the decoding rules the OEM has defined for the signals they +want to inspect, which are specified in the Cloud Control plane. This layer stores the decoded +values into a signal history buffer. This signal history buffer has a maximum fixed size to not +exhaust the system resources. **Vehicle Data Inspection** @@ -622,43 +647,41 @@ snapshot of the data at hand is shared with the Cloud data plane. More than one be defined and applied to the signal data. This layer holds a set of data structures that allow fast indexing of the data by identifier and time, so that the data collection can be achieved as a quick as the conditions are met. Once a snapshot of the signal data is available, this layer passes over -the data to the Off-board connectivity layer for further processing. The mechanism of data +the data to the offboard connectivity layer for further processing. The mechanism of data transmission between these two layers uses also a message queue with a predefined maximum size. **Scheme Management** -The Cloud control plane serves the device software with data collection scheme and decoder -manifests. A decoder manifest is an artifact that defines the vehicle signal catalog and the way -each signal can get decoded from its raw format. A collection scheme describes the inspection rules -to be applied to the signal values. The Scheme Management module has the responsibility of managing -the life cycle e.g. activation/deactivation based on time and version of the collection schemes and -the decoder manifest delivered to the device software. The outcome of the scheme management are at -any given point in time, the inspection matrix needed by the Data Inspection Layer and the decoding -dictionary needed by by the Data Normalization layer. +The Cloud control plane serves FWE with data collection scheme and decoder manifests. A decoder +manifest is an artifact that defines the vehicle signal catalog and the way each signal can get +decoded from its raw format. A collection scheme describes the inspection rules to be applied to the +signal values. The Scheme Management module has the responsibility of managing the life cycle e.g. +activation/deactivation based on time and version of the collection schemes and the decoder manifest +delivered to FWE. The outcome of the scheme management are at any given point in time, the +inspection matrix needed by the Data Inspection Layer and the decoding dictionary needed by by the +Data Normalization layer. -**Off-board Connectivity** +**Offboard Connectivity** -This layer of the software owns the communication of the AWS IoT FleetWise vehicle software with the -outside world i.e. AWS Cloud. It implements a publish/subscribe pattern, with two communication -channels, one used for receiving collection schemes and decoder manifest from the Control Plane, and -one for publishing the collected data back to the Data Plane. +This layer of the software owns the communication of FWE with the outside world i.e. AWS Cloud. It +implements a publish/subscribe pattern, with two communication channels, one used for receiving +collection schemes and decoder manifest from the Control Plane, and one for publishing the collected +data back to the Data Plane. -This layer ensures that the Device Software has valid credentials to communicate securely with the -Cloud APIs. +This layer ensures that FWE has valid credentials to communicate securely with the Cloud APIs. **Service Control** -This layer owns the execution context of the AWS IoT FleetWise Edge Agent software within the target -hardware in the vehicle. It manages the lifecycle of the vehicle software, including the -startup/shutdown sequences, along with acting as a local monitoring module to ensure smooth -execution of the service. The configuration of the service is validated and loaded into the system -in this layer of the software. +This layer owns the execution context of FWE within the target hardware in the vehicle. It manages +the lifecycle of FWE, including the startup/shutdown sequences, along with acting as a local +monitoring module to ensure smooth execution of the service. The configuration of the service is +validated and loaded into the system in this layer of the software. ### Overview of the software libraries -The code base of the device software consists of 6 C++ libraries that implement the functionalities -of the layers described above. All these libraries are loaded in a POSIX user space application -running a single process. +The code base of the Reference Implementation consists of 6 C++ libraries that implement the +functionalities of the layers described above. All these libraries are loaded in a POSIX user space +application running a single process. ![software libraries](./images/software_libraries.png) @@ -703,8 +726,8 @@ Collection Schemes and Decoder manifests. This library is used by the Data Inspection Library to normalize and decode the raw CAN Frames, and by the Execution Management library to initiate the Collection Scheme and decoder Manifest decoding. -The library also implements a serialization module to serialize the data the device software wants -to send to the data plane. The serialization schema is described below in the data model. +The library also implements a serialization module to serialize the data FWE wants to send to the +data plane. The serialization schema is described below in the data model. **Data Inspection Library** @@ -719,13 +742,12 @@ This library implements a software module for each of the following: - Cache of the needed signals in a signal history buffer. Upon fulfillment of one or more trigger conditions, this library extracts from the signal history -buffer a data snapshot that's shared with the Off-board connectivity library for further processing. +buffer a data snapshot that's shared with the offboard connectivity library for further processing. Again here a circular buffer is used as a transport mechanism of the data between the two libraries. **Connectivity Library** -This library implements the communication routines between the device software and the Cloud Control -and Data Plane. +This library implements the communication routines between FWE and the Cloud Control and Data Plane. Since all the communication between the device and the cloud occurs over a secure MQTT connection, this library uses the AWS IoT Device SDK for C++ v2 as an MQTT client. It creates exactly one @@ -738,10 +760,10 @@ update of either the Scheme or the decoder manifests, which are enacted accordan **Execution Management Library** -This library implements the bootstrap sequence of the device software. It parses the provided -configuration and ensures that are the above libraries are provided with their corresponding -settings. During shutdown, it ensures that all the modules and corresponding system resources -(threads, loggers, and sockets) are stopped/closed properly. +This library implements the bootstrap sequence of FWE. It parses the provided configuration and +ensures that are the above libraries are provided with their corresponding settings. During +shutdown, it ensures that all the modules and corresponding system resources (threads, loggers, and +sockets) are stopped/closed properly. If there is no connectivity, or during shutdown of the service , this library persists the data snapshots that are queued for sending to the cloud. Upon re-connection, this library will attempt to @@ -754,7 +776,7 @@ application. ## Programming and Execution model -The device software implements a concurrent and event-based multithreading system. +FWE implements a concurrent and event-based multithreading system. - **In the Vehicle Network Management library,** each CAN Network Interface spawns one thread to take care of opening a Socket to the network device. E.g. if the device has 4 CAN Networks @@ -779,8 +801,7 @@ The device software implements a concurrent and event-based multithreading syste - **In the Connectivity Library**, most of the execution runs in the context of the main application thread. Callbacks and notifications from the MQTT stack happen in the context of the AWS IoT Device SDK thread. This module intercepts the notifications and switches the thread context to one - of the device software threads so that no real data processing happens in the MQTT connection - thread. + of FWE threads so that no real data processing happens in the MQTT connection thread. - **In the Execution Management Library**, there are two threads - One thread which is managing all the bootstrap sequence and intercepting SystemD signals i.e. application main thread @@ -790,68 +811,66 @@ The device software implements a concurrent and event-based multithreading syste ## Data Models -The device software defines four schemas that describe the communication with the Cloud Control and -Data Plane services. +FWE defines four schemas that describe the communication with the Cloud Control and Data Plane +services. -All the payloads exchanged between the device software and the cloud services are serialized in a -Protobuff format. +All the payloads exchanged between FWE and the cloud services are serialized in a Protobuff format. ### Device to Cloud communication -The device software sends two artifacts with the Cloud services: +FWE sends two artifacts with the Cloud services: **Check-in Information** This check-in information consists of data collection schemes and decoder manifest Amazon Resource -Name (ARN) that are active in the device software at a given time point. This check-in message is -send regularly at a configurable frequency to the cloud services. Refer to +Name (ARN) that are active in FWE at a given time point. This check-in message is send regularly at +a configurable frequency to the cloud services. Refer to [checkin.proto](../../interfaces/protobuf/schemas/edgeToCloud/checkin.proto). **Data Snapshot Information** This message is send conditionally to the cloud data plane services once one or more inspection rule -is met. Depending on the configuration of the software, (e.g. send decoded and raw data), the device -software sends one or more instance of this message in an MQTT packet to the cloud. Refer to +is met. Depending on the configuration of FWE, (e.g. send decoded and raw data), FWE sends one or +more instance of this message in an MQTT packet to the cloud. Refer to [vehicle_data.proto](../../interfaces/protobuf/schemas/edgeToCloud/vehicle_data.proto). ### Cloud to Device communication -The Cloud Control plane services publish to the Device Software dedicated MQTT Topic the following -two artifacts: +The Cloud Control plane services publish to FWE dedicated MQTT Topic the following two artifacts: -**Decoder Manifest** — This artifact describes the Vehicle Network Interfaces that the user defined. +**Decoder Manifest:** This artifact describes the Vehicle Network Interfaces that the user defined. The description includes the semantics of each of the Network interface traffic to be inspected including the signal decoding rules. Refer to [decoder_manifest.proto](../../interfaces/protobuf/schemas/cloudToEdge/decoder_manifest.proto). -**Collection Scheme** - -This artifact describes effectively the inspection rules, that the device software will apply on the -network traffic it receives. Using the decoder manifest, the Inspection module will apply the rules -defined in the collection schemes to generate data snapshots. Refer to +**Collection Scheme:** This artifact describes effectively the inspection rules, that FWE will apply +on the network traffic it receives. Using the decoder manifest, the Inspection module will apply the +rules defined in the collection schemes to generate data snapshots. Refer to [collection_schemes.proto](../../interfaces/protobuf/schemas/cloudToEdge/collection_schemes.proto). ## Data Persistency -The device software requires a temporary disk location in order to persist and reload the documents -it exchanges with the cloud services. The persistency operates on three types of documents: +FWE requires a temporary disk location in order to persist and reload the documents it exchanges +with the cloud services. The persistency operates on three types of documents: -- Collection Schemes data: This data set is persisted during shutdown of the device software, and - re-loaded upon startup. -- Decoder Manifest Data: This data set is persisted during shutdown of the device software, and - re-loaded upon startup. +- Collection Schemes data: This data set is persisted during shutdown of FWE, and re-loaded upon + startup. +- Decoder Manifest Data: This data set is persisted during shutdown of FWE, and re-loaded upon + startup. - Data Snapshots: This data set is persisted when there is no connectivity in the system. Upon the next startup of the service, the data is reloaded and send if there is connectivity. The persistency module operates on a fixed/configurable maximum partition size. If there is no space left, the module does not persist the data. -All data exchanged between the device software and the cloud or persisted on the disk is compressed. +Persisted data is uploaded once on the bootup. Upload will be repeated after interval that is set in +the static configuration under ["staticConfig"]["persistency"]["persistencyUploadRetryIntervalMs"]. +If this value is not set, upload will be retried only on the next bootup. ## Logging -The device software defines a Logger interface and implements a standard output logging backend. The -interface can be extended to support other logging backends if needed. +FWE defines a Logger interface and implements a standard output logging backend. The interface can +be extended to support other logging backends if needed. The logger interface defines the following severity levels : @@ -876,13 +895,13 @@ described below in the configuration section. Each log entry includes the follow [Thread: ID] [Time] [Level] [Filename:LineNumber] [Function()]: [Message] ``` -- Thread — the thread ID triggering the log entry. -- Time — the timestamp in milliseconds since Epoch. -- Level — the severity of the log entry. -- Filename - the file name that invoked the log entry. -- LineNumber - the line in the file that invoked the log entry. -- Function — the function name that invoked the log entry. -- Message — the actual log message. +- **Thread:** the thread ID triggering the log entry. +- **Time:** the timestamp in milliseconds since Epoch. +- **Level:** the severity of the log entry. +- **Filename:** the file name that invoked the log entry. +- **LineNumber:** the line in the file that invoked the log entry. +- **Function:** the function name that invoked the log entry. +- **Message:** the actual log message. ## Configuration @@ -911,12 +930,14 @@ described below in the configuration section. Each log entry includes the follow | | persistencyUploadRetryIntervalMs | Interval to wait before retrying to upload persisted signal data (in milliseconds). After successfully uploading, the persisted signal data will be cleared. Only signal data that could not be uploaded will be persisted. (in milliseconds) | integer | | internalParameters | readyToPublishDataBufferSize | Size of the buffer used for storing ready to publish, filtered data | integer | | | systemWideLogLevel | Sets logging level severity: `Trace`, `Info`, `Warning`, `Error` | string | -| | logColor | Whether logs should be colored: `Auto`, `Yes`, `No`. Default to `Auto`, meaning the agent will try to detect whether colored output is supported (for example when connected to a tty) | string | +| | logColor | Whether logs should be colored: `Auto`, `Yes`, `No`. Default to `Auto`, meaning FWE will try to detect whether colored output is supported (for example when connected to a tty) | string | +| | maximumAwsSdkHeapMemoryBytes | The maximum size of AWS SDK heap memory | integer | | | dataReductionProbabilityDisabled | Disables probability-based DDC (only for debug purpose) | boolean | | | metricsCyclicPrintIntervalMs | Sets the interval in milliseconds how often the application metrics should be printed to stdout. Default 0 means never | string | | publishToCloudParameters | maxPublishMessageCount | Maximum messages that can be published to the cloud in one payload | integer | | | collectionSchemeManagementCheckinIntervalMs | Time interval between collection schemes checkins(in milliseconds) | integer | | mqttConnection | endpointUrl | AWS account's IoT device endpoint | string | +| | connectionType | The connection module type, it can be iotCore or iotGreengrassV2 | string | | | clientId | The ID that uniquely identifies this device in the AWS Region | string | | | collectionSchemeListTopic | Topic for subscribing to Collection Scheme | string | | | decoderManifestTopic | Topic for subscribing to Decoder Manifest | string | @@ -937,12 +958,11 @@ described below in the configuration section. Each log entry includes the follow ## Security -The device software has been designed with security principles in mind. Security has been -incorporated into four main domains: +FWE has been designed with security principles in mind. Security has been incorporated into four +main domains: -- Device Authentication: the device software uses Client Certificates (x.509) to communicate with - AWS IoT services. All the communications from and to the device software are over a secure TLS - Connection. Refer to the +- Device Authentication: FWE uses Client Certificates (x.509) to communicate with AWS IoT services. + All the communications from and to FWE are over a secure TLS Connection. Refer to the [AWS IoT Security documentation](https://docs.aws.amazon.com/iot/latest/developerguide/x509-client-certs.html) for further details. - Data in transit: All the data exchanged with the AWS IoT services is encrypted in transit. @@ -950,16 +970,15 @@ incorporated into four main domains: persistency. It's assumed that the software operates in a secure partition that the OEM puts in place and rely on the OEM secure storage infrastructure that is applied for all IO operations happening in the gateway e.g. via HSM, OEM crypto stack. -- Access to vehicle CAN data: the device software assumes that the software operates in a secure - execution partition, that guarantees that if needed, the CAN traffic is encrypted/decrypted by the - OEM Crypto stack (either on chip/HSM or via separate core running the crypto stack). +- Access to vehicle CAN data: FWE assumes that the software operates in a secure execution + partition, that guarantees that if needed, the CAN traffic is encrypted/decrypted by the OEM + Crypto stack (either on chip/HSM or via separate core running the crypto stack). -The device software can be extended to invoke cryptography APIs to encrypt and decrypt the data as -per the need. +FWE can be extended to invoke cryptography APIs to encrypt and decrypt the data as per the need. -The device software has been designed to be deployed in a non safety relevant in-vehicle -domain/partition. Due to its use of dynamic memory allocation, this software is not suited for -deployment on real time/lock step/safety cores. +FWE has been designed to be deployed in a non safety relevant in-vehicle domain/partition. Due to +its use of dynamic memory allocation, this software is not suited for deployment on real time/lock +step/safety cores. ### Best Practices and recommendation @@ -982,35 +1001,25 @@ Please refer to [Device Manufacturing and Provisioning with X.509 Certificates in AWS IoT Core](https://d1.awsstatic.com/whitepapers/device-manufacturing-provisioning.pdf) for security recommendations on device manufacturing and provisioning. -Note: This is **only** a recommendation. Your system must be protected with proper security -measures. +Note: This is **only** a recommendation. You are responsible for protecting your system with proper +security measures. ## Supported Platforms -The AWS IoT FleetWise Edge Agent software has been developed for 64 bit architecture. It has been -tested on both ARM and X86 multicore based machines, with a Linux Kernel version of 5.4 and above. -The kernel module for ISO-TP (can-isotp) would need to be installed in addition for Kernels below -5.10. - -## Disclaimer - -The device software code base includes modules that are still in development an are disabled by -default. These modules are not intended for use in a production environment. This includes the Data -Distribution Service Support (DDS) that is used to communicate with any sensor node connected over a -DDS bus and a Remote Profiler module that helps sending traces from the device to AWS Cloud Watch. -The device software has been checked for any memory leaks and runtime errors such as type overflows -using Valgrind. No issues have been detected during the load tests. +FWE has been developed for 64 bit architecture. It has been tested on both ARM and x86 multicore +based machines, with a Linux Kernel version of 5.4 and above. The kernel module for ISO-TP +(`can-isotp`) would need to be installed in addition for kernels below 5.10. ## Getting Help Contact [AWS Support](https://aws.amazon.com/contact-us/) if you have any technical questions about -AWS IoT FleetWise Edge Agent. +FWE. ## Resources -The following documents or websites provide more information about AWS IoT FleetWise Edge Agent. +The following documents or websites provide more information about FWE. 1. [Change Log](../../CHANGELOG.md) provides a summary of feature enhancements, updates, and resolved and known issues. -2. [AWS IoT FleetWise Edge Agent Offboarding](../AWS-IoTFleetWiseOffboarding.md) provides a summary - of the steps needed on the Client side to off board from the service. +1. [Offboarding and Data Deletion](../AWS-IoTFleetWiseOffboarding.md) provides a summary of the + steps needed on the client side to offboard from the service. diff --git a/docs/iwave-g26-tutorial/iwave-g26-tutorial.md b/docs/iwave-g26-tutorial/iwave-g26-tutorial.md index a744411b..d74b967a 100644 --- a/docs/iwave-g26-tutorial/iwave-g26-tutorial.md +++ b/docs/iwave-g26-tutorial/iwave-g26-tutorial.md @@ -1,6 +1,19 @@ -# Tutorial: Run AWS IoT FleetWise Edge Agent on iWave G26 TCU +# iWave G26 TCU Tutorial -**Copyright © Amazon Web Services, Inc. and/or its affiliates. All rights reserved.** +**Topics** + +- [Introduction](#introduction) +- [About this tutorial](#about-this-tutorial) +- [Prerequisites](#prerequisites) +- [Step 1: Set up the iWave Systems G26 TCU](#step-1-set-up-iwave-systems-g26-tcu) +- [Step 2: Launch your development machine](#step-2-launch-your-development-machine) +- [Step 3: Compile Edge Agent](#step-3-compile-edge-agent) +- [Step 4: Provision AWS IoT credentials](#step-4-provision-aws-iot-credentials) +- [Step 5: Deploy Edge Agent](#step-5-deploy-edge-agent) +- [Step 6: Connect the iWave G26 TCU to the vehicle](#step-6-connect-the-tcu-to-the-vehicle) +- [Step 7: Collect OBD Data](#step-7-collect-obd-data) + +**Copyright (C) Amazon Web Services, Inc. and/or its affiliates. All rights reserved.** Amazon's trademarks and trade dress may not be used in connection with any product or service that is not Amazon's, in any manner that is likely to cause confusion among customers, or in any manner @@ -8,47 +21,80 @@ that disparages or discredits Amazon. All other trademarks not owned by Amazon a their respective owners, who may or may not be affiliated with, connected to, or sponsored by Amazon. -**Note** - -- AWS IoT FleetWise is currently available in US East (N. Virginia) and Europe (Frankfurt). -- The AWS IoT FleetWise in-vehicle software component is licensed to you under the Apache License, - Version 2.0. -- You are solely responsible for ensuring such software and any updates and modifications thereto - are deployed and maintained safely and securely in any vehicles and do not otherwise impact - vehicle safety. +## Introduction + +**AWS IoT FleetWise** provides a set of tools that enable automakers to collect, transform, and +transfer vehicle data to the cloud at scale. With AWS IoT FleetWise you can build virtual +representations of vehicle networks and define data collection rules to transfer only high-value +data from your vehicles to AWS Cloud. + +**The Reference Implementation for AWS IoT FleetWise ("FWE")** provides C++ libraries that can be +run with simulated vehicle data on certain supported vehicle hardware or that can help you develop +an Edge Agent to run an application on your vehicle that integrates with AWS IoT FleetWise. You can +use AWS IoT FleetWise pre-configured analytic capabilities to process collected data, gain insights +about vehicle health, and use the service's visual interface to help diagnose and troubleshoot +potential issues with the vehicle. + +AWS IoT FleetWise's capability to collect ECU data and store them on cloud databases enables you to +utilize different AWS services, such as Analytics Services, and ML, to develop novel use-cases that +augment and/or supplement your existing vehicle functionality. In particular, AWS IoT FleetWise can +help utilize fleet data (Big Data) to create value. For example, you can develop use cases that +optimize vehicle routing, improve electric vehicle range estimation, and optimize battery life +charging. You can use the data ingested through AWS IoT FleetWise to develop applications for +predictive diagnostics, and for outlier detection with an electric vehicle's battery cells. + +You can use the included sample C++ application to learn more about the Reference Implementation, +develop an Edge Agent for your use case and test interactions before integration. + +This software is licensed under the +[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +### Disclaimer + +**_The Reference Implementation for AWS IoT FleetWise ("FWE") is intended to help you develop your +Edge Agent for AWS IoT FleetWise and includes sample code that you may reference or modify so your +Edge Agent meets your requirements. As provided in the AWS IoT FleetWise Service Terms, you are +solely responsible for your Edge Agent, including ensuring that your Edge Agent and any updates and +modifications thereto are deployed and maintained safely and securely in any vehicles._** + +**_This software code base includes modules that are still in development and are disabled by +default. These modules are not intended for use in a production environment. This includes a Remote +Profiler module that helps sending traces from the device to AWS Cloud Watch. FWE has been checked +for any memory leaks and runtime errors such as type overflows using Valgrind. No issues have been +detected during the load tests._** + +**_Note that vehicle data collected through your use of AWS IoT FleetWise is intended for +informational purposes only (including to help you train cloud-based artificial intelligence and +machine learning models), and you may not use AWS IoT FleetWise to control or operate vehicle +functions. You are solely responsible for all liability that may arise in connection with any use +outside of AWS IoT FleetWise's intended purpose and in any manner contrary to applicable vehicle +regulations. Vehicle data collected through your use of AWS IoT FleetWise should be evaluated for +accuracy as appropriate for your use case, including for purposes of meeting any compliance +obligations you may have under applicable vehicle safety regulations (such as safety monitoring and +reporting obligations). Such evaluation should include collecting and reviewing information through +other industry standard means and sources (such as reports from drivers of vehicles). You and your +End Users are solely responsible for all decisions made, advice given, actions taken, and failures +to take action based on your use of AWS IoT FleetWise._** ## About this tutorial -AWS IoT FleetWise provides a set of tools for automakers to collect, transform, and transfer vehicle -data to the cloud at scale. With AWS IoT FleetWise, you can build virtual representations of vehicle -networks and define data collection rules to transfer only high-value data from your vehicles to the -AWS cloud. Follow the steps in this tutorial to set up and configure the iWave G26 TCU device -hardware to work with the AWS IoT FleetWise vehicle agent software. You can then connect the device -to a vehicle so that it collects vehicle J1979 OBD-II data and transfers it to the AWS IoT FleetWise -service. To additionally also collected GPS data more steps described here +Follow the steps in this tutorial to set up and configure the iWave G26 TCU device hardware to work +with your Edge Agent compiled from the FWE source code. You can then connect the device to a vehicle +so that it collects vehicle J1979 OBD-II data and transfers it to the AWS IoT FleetWise service. To +additionally also collected GPS data more steps described here [iwavegps](../../src/datamanagement/custom/example/iwavegps/README.md) are required. They require the steps below to be executed first. **Estimated Time**: 60 minutes -## Topics - -- [Prerequisites](#prerequisites) -- [Step 1: Set up the iWave Systems G26 TCU](#step-1-set-up-iwave-systems-g26-tcu) -- [Step 2: Launch your development machine](#step-2-launch-your-development-machine) -- [Step 3: Compile AWS IoT FleetWise Edge Agent software](#step-3-compile-aws-iot-fleetwise-edge-agent-software) -- [Step 4: Provision AWS IoT credentials](#step-4-provision-aws-iot-credentials) -- [Step 5: Deploy Edge Agent](#step-5-deploy-edge-agent) -- [Step 6: Connect the iWave G26 TCU to the vehicle](#step-6-connect-the-tcu-to-the-vehicle) -- [Step 7: Collect OBD Data](#step-7-collect-obd-data) - ## Prerequisites - A [G26 TCU from iWave Systems](https://www.iwavesystems.com/product/telematics-control-unit/) device - Access to an AWS account with administrator permissions - To be signed in to the AWS Management Console with an account in your chosen Region - - Note: AWS IoT FleetWise is currently available in US East (N. Virginia) and Europe (Frankfurt). + - **Note:** AWS IoT FleetWise is currently available in US East (N. Virginia) and Europe + (Frankfurt). - A SIM card - A local Linux or MacOS machine @@ -288,8 +334,6 @@ you can use a local Intel x86_64 (amd64) machine. We recommended using the follo launch an AWS EC2 Graviton (arm64) instance. For more information about Amazon EC2 pricing, see [Amazon EC2 On-Demand Pricing](https://aws.amazon.com/ec2/pricing/on-demand/). -### To launch an Amazon EC2 instance with administrator permissions - 1. Sign in to your [AWS account](https://aws.amazon.com/console/). 1. Open the [**Launch CloudFormation Template**](https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate?templateUrl=https%3A%2F%2Faws-iot-fleetwise.s3.us-west-2.amazonaws.com%2Flatest%2Fcfn-templates%2Ffwdev.yml&stackName=fwdev¶m_Ec2VolumeSize=20). @@ -310,33 +354,34 @@ launch an AWS EC2 Graviton (arm64) instance. For more information about Amazon E ssh -i ubuntu@ ``` -## Step 3: Compile AWS IoT FleetWise Edge Agent software - -Next, compile the AWS IoT FleetWise Edge Agent software for the ARM 32-bit architecture of the i.MX6 -processor present in the G26 TCU device. +## Step 3: Compile Edge Agent -### To compile the AWS IoT FleetWise Edge Agent software +Next, compile FWE for the ARM 32-bit architecture of the i.MX6 processor present in the G26 TCU +device. -1. On your development machine, clone the latest AWS IoT FleetWise Edge Agent software from GitHub - by running the following: +1. On your development machine, clone the latest FWE source code from GitHub by running the + following: ```bash git clone https://github.com/aws/aws-iot-fleetwise-edge.git ~/aws-iot-fleetwise-edge \ - && cd ~/aws-iot-fleetwise-edge + && cd ~/aws-iot-fleetwise-edge ``` -2. Install the AWS IoT FleetWise Edge Agent dependencies. The command below installs the following - Ubuntu packages for cross-compiling the Edge Agent for ARM 32-bit: +1. Review, modify and supplement [the FWE source code](../../src/) to ensure it meets your use case + and requirements. -`libssl-dev libboost-system-dev libboost-log-dev libboost-thread-dev build-essential cmake unzip git wget curl zlib1g-dev libcurl4-openssl-dev libsnappy-dev default-jre libasio-dev`. +1. Install the dependencies for FWE. The command below installs the following Ubuntu packages for + cross-compiling FWE for ARM 32-bit: -Additionally, it installs the following: `jsoncpp protobuf aws-sdk-cpp`. + `libssl-dev libboost-system-dev libboost-log-dev libboost-thread-dev build-essential cmake unzip git wget curl zlib1g-dev libcurl4-openssl-dev libsnappy-dev default-jre libasio-dev`. -```bash -sudo -H ./tools/install-deps-cross-armhf.sh -``` + Additionally, it installs the following: `jsoncpp protobuf aws-sdk-cpp`. -3. To compile AWS IoT FleetWise Edge Agent software, run the following command: + ```bash + sudo -H ./tools/install-deps-cross-armhf.sh + ``` + +1. To compile your Edge Agent, run the following command: ```bash ./tools/build-fwe-cross-armhf.sh @@ -345,8 +390,8 @@ sudo -H ./tools/install-deps-cross-armhf.sh ## Step 4: Provision AWS IoT credentials On the development machine, create an IoT thing and provision its credentials by running the -following command. The AWS IoT FleetWise Edge Agent binary and its configuration files are packaged -into a ZIP file that is ready for deployment to the TCU. +following command. Your Edge Agent binary and its configuration files are packaged into a ZIP file +that is ready for deployment to the TCU. ```bash mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ @@ -373,8 +418,6 @@ mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ ## Step 5: Deploy Edge Agent -### To deploy AWS IoT FleetWise Edge Agent - 1. On your local machine, copy the deployment ZIP file from the machine with Amazon EC2 to your local machine by running the following command: @@ -390,8 +433,8 @@ mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ ``` 1. As described in step 5 of [setting up the TCU](#step-1-set-up-iwave-systems-g26-tcu), connect - through SSH to the TCU. On the TCU, install AWS IoT FleetWise Edge Agent as a service by running - the following command: + through SSH to the TCU. On the TCU, install your Edge Agent as a service by running the following + command: ```bash mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ @@ -401,15 +444,14 @@ mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ && ./tools/install-fwe.sh ``` -1. On the TCU, view and follow the AWS IoT FleetWise Edge Agent log (press CTRL+C to exit) by - running the following command: +1. On the TCU, view and follow your Edge Agent log (press CTRL+C to exit) by running the following + command: ```bash journalctl -fu fwe@0 --output=cat ``` - The following line appears, confirming the AWS IoT FleetWise Edge Agent successfully connected to - AWS IoT Core: + The following line appears, confirming your Edge Agent successfully connected to AWS IoT Core: ``` [INFO ] [AwsIotConnectivityModule.cpp:161] [connect()] [Connection completed successfully] @@ -433,10 +475,9 @@ mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ ## Step 7: Collect OBD data -1. On the development machine, install the AWS IoT FleetWise cloud demo script dependencies by - running the following commands. The script installs the following Ubuntu packages: - `python3 python3-pip`, and then installs the following PIP packages: - `wrapt plotly pandas cantools`. +1. On the development machine, install the AWS IoT FleetWise demo script dependencies by running the + following commands. The script installs the following Ubuntu packages: `python3 python3-pip`, and + then installs the following PIP packages: `wrapt plotly pandas cantools`. ```bash cd ~/aws-iot-fleetwise-edge/tools/cloud \ diff --git a/docs/metrics.md b/docs/metrics.md index 49f403e7..2cb90162 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -1,9 +1,9 @@ # Application level metrics -The AWS IoT Fleetwise Edge agent includes a +The Reference Implementation for AWS IoT Fleetwise ("FWE") includes a [TraceModule](../src/platform/linux/logmanagement/src/TraceModule.cpp). The TraceModule provides a set of metrics that are used as an entry point to efficiently diagnose issues, saving you time since -you no longer need to review the entire log of all edge agent instances running. +you no longer need to review the entire log of all FWE instances running. - **`RFrames0` - `RFrames19`** are monotonic counters of the number of raw can frames read on each bus. If these counters remain null or remain fixed for a longer runtime, the system might either @@ -12,17 +12,17 @@ you no longer need to review the entire log of all edge agent instances running. - **`ConInt`** and **`ConRes`** enable you to monitor the the number of MQTT connection interruptions and connection resumptions. If and how long it takes to detect a connection loss depends on the kernel configuration parameters `/proc/sys/net/ipv4/tcp/keepalive*` and the compile - time constants of AWS IoT Fleetwise Edge: `MQTT_CONNECT_KEEP_ALIVE_SECONDS` and - `MQTT_PING_TIMOUT_MS`. If the values of the metric `ConInt` are not null, the internet coverage in - the tested environment might be unreliable, or `MQTT_PING_TIMOUT_MS`, which defaults to 3 seconds, - needs to be increased because there's high latency to the IoT Core endpoint. Changing the AWS - Region can help to decrease latency. + time constants of FWE: `MQTT_CONNECT_KEEP_ALIVE_SECONDS` and `MQTT_PING_TIMOUT_MS`. If the values + of the metric `ConInt` are not null, the internet coverage in the tested environment might be + unreliable, or `MQTT_PING_TIMOUT_MS`, which defaults to 3 seconds, needs to be increased because + there's high latency to the IoT Core endpoint. Changing the AWS Region can help to decrease + latency. - **`CeTrgCnt`** is a monotonic counter that monitors the number of triggers (inspection rules) - detected since the AWS IoT Fleetwise Edge process started. Triggers are detected if one or more - data collection campaign conditions are true. If this counter is larger than zero, but no data - appears in the cloud, either no actual data was collected ( such as a time-based data collection - campaign with no bus activity), or the data has been ingested to the cloud but there was an error - processing it. To debug this, + detected since the FWE process started. Triggers are detected if one or more data collection + campaign conditions are true. If this counter is larger than zero, but no data appears in the + cloud, either no actual data was collected ( such as a time-based data collection campaign with no + bus activity), or the data has been ingested to the cloud but there was an error processing it. To + debug this, [enable cloud logs in AWS IoT Fleetwise settings](https://docs.aws.amazon.com/iot-fleetwise/latest/developerguide/logging-cw.html). - **`QUEUE_CONSUMER_TO_INSPECTION_SIGNALS`** monitors the current count of signals in queue to the signal history buffer. If this value is close to the value defined in the static config @@ -51,8 +51,8 @@ you no longer need to review the entire log of all edge agent instances running. - `CeSCnt` is a monotonic counter that counts the signals decoded and processed since startup. This can be used for performance evaluations. - `CpuPercentageSum` and `CpuThread_*` tracks the CPU usage for the complete process and per thread. - In multi-core systems this can be above 100%. AWS IoT Fleetwise Edge uses the linux `/proc/` - directory to calculate this information. + In multi-core systems this can be above 100%. FWE uses the linux `/proc/` directory to calculate + this information. - `MemoryMaxResidentRam` gives the maximum bytes of resident RAM used by the process. If this is above 50 MB high consider switching from cmake Debug to Release build. Also the queue sizes in the static config can be reduced. @@ -65,29 +65,29 @@ you no longer need to review the entire log of all edge agent instances running. campaigns to the first signal data being published. If at least one time based collection scheme is active this should be at most the time period of that collection scheme. -# How to collect metrics from a FWE +# How to collect metrics from FWE -There are multiple ways to collect metrics depending on how AWS IoT Fleetwise Edge (FWE) is -integrated. We describe two methods: using the RemoteProfiler and collecting processed logs and -extract metrics (like through the AWS Systems Manager). +There are multiple ways to collect metrics depending on how FWE is integrated. We describe two +methods: using the RemoteProfiler and collecting processed logs and extract metrics (like through +the AWS Systems Manager). Each method incurs charges for different AWS services like [AWS IoT Core](https://aws.amazon.com/iot-core/pricing/), [Amazon CloudWatch](https://aws.amazon.com/cloudwatch/pricing/), [AWS System Manager](https://aws.amazon.com/systems-manager/pricing/) and more. For example, using -the [RemoteProfiler](#method-1-use-the-remoteprofiler-module) method, AWS Iot Fleetwise Edge uploads -at your configured interval, which is currently ~300 metrics. Per 10 metrics data points uploaded, -at least one message will be published to AWS IoT Core and one AWS IoT Rules Engine Action will be -executed. If `profilerPrefix` is different for every vehicle, ~300 new Amazon CloudWatch metrics -will be used per vehicle. +the [RemoteProfiler](#method-1-use-the-remoteprofiler-module) method, FWE uploads at your configured +interval, which is currently ~300 metrics. Per 10 metrics data points uploaded, at least one message +will be published to AWS IoT Core and one AWS IoT Rules Engine Action will be executed. If +`profilerPrefix` is different for every vehicle, ~300 new Amazon CloudWatch metrics will be used per +vehicle. ## Method 1: Use the RemoteProfiler module -The RemoteProfiler module is provided as part of the AWS IoT FleetWise Edge C++ code base. If -activated, it will regularly ingest the metrics and logs to AWS IoT Core topics, which have -underlying AWS IoT Core Rules and actions to route the data to Amazon CloudWatch. The same existing -MQTT connection used to ingest the data collection campaign is reused for this purpose. In order to -activate the RemoteProfile, add the following parameters to your config file: +The RemoteProfiler module is provided as part of the FWE C++ code base. If activated, it will +regularly ingest the metrics and logs to AWS IoT Core topics, which have underlying AWS IoT Core +Rules and actions to route the data to Amazon CloudWatch. The same existing MQTT connection used to +ingest the data collection campaign is reused for this purpose. In order to activate the +RemoteProfile, add the following parameters to your config file: ```json { @@ -147,10 +147,10 @@ can produce multiple gigabytes of logs per day. These logs can be collected full over ssh from single vehicles in case of need for debugging or cyclically from the whole fleet. To manage easy remote connections to multiple vehicles AWS Systems Manager or AWS IoT secure tunneling could be used. For aggregation, custom scripts can be used to filter certain log levels. The log -levels in AWS IoT Fleetwise Edge logs go from `[ERROR]` to `[TRACE]`. To make the metrics easier to -parse, you can set the parameter `.staticConfig.internalParameters.metricsCyclicPrintIntervalMs` in -the static config an interval like 60000. This will cause the metrics to print in an easy parsable -format to the log every minute. The following regex expression can be used by any log/metrics +levels in the FWE logs go from `[ERROR]` to `[TRACE]`. To make the metrics easier to parse, you can +set the parameter `.staticConfig.internalParameters.metricsCyclicPrintIntervalMs` in the static +config an interval like 60000. This will cause the metrics to print in an easy parsable format to +the log every minute. The following regex expression can be used by any log/metrics aggregator/uploader that supports Python. For lines that start with `TraceModule-ConsoleLogging-TraceAtomicVariable` or `TraceModule-ConsoleLogging-Variable`: @@ -185,10 +185,9 @@ if and how to upload them to the cloud. # Adding new metrics -Adding new metrics requires changing the C++ code and recompiling AWS IoT Fleetwise Edge. Add the -Metrics to the `TraceVariable` enum in -[TraceModule.h](../src/platform/linux/logmanagement/include/TraceModule.h) and assign a short name -in the function `getVariableName` of +Adding new metrics requires changing the C++ code and recompiling FWE. Add the metrics to the +`TraceVariable` enum in [TraceModule.h](../src/platform/linux/logmanagement/include/TraceModule.h) +and assign a short name in the function `getVariableName` of [TraceModule.cpp](../src/platform/linux/logmanagement/src/TraceModule.cpp). Then you can set the metrics anywhere by using: diff --git a/docs/rpi-tutorial/raspberry-pi-tutorial.md b/docs/rpi-tutorial/raspberry-pi-tutorial.md index 4be03352..bed1005e 100644 --- a/docs/rpi-tutorial/raspberry-pi-tutorial.md +++ b/docs/rpi-tutorial/raspberry-pi-tutorial.md @@ -1,32 +1,79 @@ -# Tutorial: Run AWS IoT FleetWise Edge Agent on a Raspberry Pi - -**Copyright © Amazon Web Services, Inc. and/or its affiliates. All rights reserved.** - -Amazon's trademarks and trade dress may not be used in connection with any product or service that -is not Amazon's, in any manner that is likely to cause confusion among customers, or in any manner -that disparages or discredits Amazon. All other trademarks not owned by Amazon are the property of -their respective owners, who may or may not be affiliated with, connected to, or sponsored by -Amazon. - -**Note** - -- AWS IoT FleetWise is currently available in US East (N. Virginia) and Europe (Frankfurt). -- The AWS IoT FleetWise in-vehicle software component is licensed to you under the Apache License, - Version 2.0. -- You are solely responsible for ensuring such software and any updates and modifications thereto - are deployed and maintained safely and securely in any vehicles and do not otherwise impact - vehicle safety. +# Raspberry Pi Tutorial ## Topics +- [Introduction](#introduction) - [Prerequisites](#prerequisites) - [Step 1: Set up the Raspberry Pi](#step-1-setup-the-raspberry-pi) - [Step 2: Launch your development machine](#step-2-launch-your-development-machine) -- [Step 3: Compile AWS IoT FleetWise Edge Agent software](#step-3-compile-aws-iot-fleetwise-edge-agent-software) +- [Step 3: Compile Edge Agent](#step-3-compile-egde-agent) - [Step 4: Provision AWS IoT credentials](#step-4-provision-aws-iot-credentials) - [Step 5: Deploy Edge Agent](#step-5-deploy-edge-agent) - [Step 6: Deploy a campaign to the Raspberry Pi](#step-6-deploy-a-campaign-to-the-raspberry-pi) +**Copyright (C) Amazon Web Services, Inc. and/or its affiliates. All rights reserved.** + +Amazon's trademarks and trade dress may not be used in connection with any product or service that +is not Amazon's, in any manner that is likely to cause confusion among customers, or in any manner +that disparages or discredits Amazon. All other trademarks not owned by Amazon are the property of +their respective owners, who may or may not be affiliated with, connected to, or sponsored by +Amazon. + +## Introduction + +**AWS IoT FleetWise** provides a set of tools that enable automakers to collect, transform, and +transfer vehicle data to the cloud at scale. With AWS IoT FleetWise you can build virtual +representations of vehicle networks and define data collection rules to transfer only high-value +data from your vehicles to AWS Cloud. + +**The Reference Implementation for AWS IoT FleetWise ("FWE")** provides C++ libraries that can be +run with simulated vehicle data on certain supported vehicle hardware or that can help you develop +an Edge Agent to run an application on your vehicle that integrates with AWS IoT FleetWise. You can +use AWS IoT FleetWise pre-configured analytic capabilities to process collected data, gain insights +about vehicle health, and use the service's visual interface to help diagnose and troubleshoot +potential issues with the vehicle. + +AWS IoT FleetWise's capability to collect ECU data and store them on cloud databases enables you to +utilize different AWS services, such as Analytics Services, and ML, to develop novel use-cases that +augment and/or supplement your existing vehicle functionality. In particular, AWS IoT FleetWise can +help utilize fleet data (Big Data) to create value. For example, you can develop use cases that +optimize vehicle routing, improve electric vehicle range estimation, and optimize battery life +charging. You can use the data ingested through AWS IoT FleetWise to develop applications for +predictive diagnostics, and for outlier detection with an electric vehicle's battery cells. + +You can use the included sample C++ application to learn more about the Reference Implementation, +develop an Edge Agent for your use case and test interactions before integration. + +This software is licensed under the +[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +### Disclaimer + +**_The Reference Implementation for AWS IoT FleetWise ("FWE") is intended to help you develop your +Edge Agent for AWS IoT FleetWise and includes sample code that you may reference or modify so your +Edge Agent meets your requirements. As provided in the AWS IoT FleetWise Service Terms, you are +solely responsible for your Edge Agent, including ensuring that your Edge Agent and any updates and +modifications thereto are deployed and maintained safely and securely in any vehicles._** + +**_This software code base includes modules that are still in development and are disabled by +default. These modules are not intended for use in a production environment. This includes a Remote +Profiler module that helps sending traces from the device to AWS Cloud Watch. FWE has been checked +for any memory leaks and runtime errors such as type overflows using Valgrind. No issues have been +detected during the load tests._** + +**_Note that vehicle data collected through your use of AWS IoT FleetWise is intended for +informational purposes only (including to help you train cloud-based artificial intelligence and +machine learning models), and you may not use AWS IoT FleetWise to control or operate vehicle +functions. You are solely responsible for all liability that may arise in connection with any use +outside of AWS IoT FleetWise's intended purpose and in any manner contrary to applicable vehicle +regulations. Vehicle data collected through your use of AWS IoT FleetWise should be evaluated for +accuracy as appropriate for your use case, including for purposes of meeting any compliance +obligations you may have under applicable vehicle safety regulations (such as safety monitoring and +reporting obligations). Such evaluation should include collecting and reviewing information through +other industry standard means and sources (such as reports from drivers of vehicles). You and your +End Users are solely responsible for all decisions made, advice given, actions taken, and failures +to take action based on your use of AWS IoT FleetWise._** + ## Prerequisites - A Raspberry Pi, version 3 or later, (64-bit) @@ -38,7 +85,8 @@ Amazon. [2-Channel Isolated CAN Bus Expansion HAT](https://rarecomponents.com/store/2-ch-can-hat-waveshare). - Access to an AWS account with administrator permissions - To be signed in to the AWS Management Console with an account in your chosen Region - - Note: AWS IoT FleetWise is currently available in US East (N. Virginia) and Europe (Frankfurt). + - **Note:** AWS IoT FleetWise is currently available in US East (N. Virginia) and Europe + (Frankfurt). - A local Windows, Mac, or Linux machine ## Step 1: Setup the Raspberry Pi @@ -81,8 +129,6 @@ you can use a local Intel x86_64 (amd64) machine. We recommended using the follo launch an AWS EC2 Graviton (arm64) instance. For more information about Amazon EC2 pricing, see [Amazon EC2 On-Demand Pricing](https://aws.amazon.com/ec2/pricing/on-demand/). -### To launch an Amazon EC2 instance with administrator permissions - 1. Sign in to your [AWS account](https://aws.amazon.com/console/). 1. Open the [**Launch CloudFormation Template**](https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate?templateUrl=https%3A%2F%2Faws-iot-fleetwise.s3.us-west-2.amazonaws.com%2Flatest%2Fcfn-templates%2Ffwdev.yml&stackName=fwdev¶m_Ec2VolumeSize=20). @@ -103,23 +149,23 @@ launch an AWS EC2 Graviton (arm64) instance. For more information about Amazon E ssh -i ubuntu@ ``` -## Step 3: Compile AWS IoT FleetWise Edge Agent software - -Next, compile the AWS IoT FleetWise Edge Agent software for the ARM 64-bit architecture of the -processor present in the Raspberry Pi. +## Step 3: Compile Edge Agent -### To compile the AWS IoT FleetWise Edge Agent software +Next, compile FWE for the ARM 64-bit architecture of the processor present in the Raspberry Pi. -1. On your development machine, clone the latest AWS IoT FleetWise Edge Agent software from GitHub - by running the following: +1. On your development machine, clone the latest FWE source code from GitHub by running the + following: ```bash git clone https://github.com/aws/aws-iot-fleetwise-edge.git ~/aws-iot-fleetwise-edge \ && cd ~/aws-iot-fleetwise-edge ``` -1. Install the AWS IoT FleetWise Edge Agent dependencies. The command below installs the following - Ubuntu packages for compiling the Edge Agent for ARM 64-bit: +1. Review, modify and supplement [the FWE source code](../../src/) to ensure it meets your use case + and requirements. + +1. Install the FWE dependencies. The command below installs the following Ubuntu packages for + compiling FWEfor ARM 64-bit: `libssl-dev libboost-system-dev libboost-log-dev libboost-thread-dev build-essential cmake unzip git wget curl zlib1g-dev libcurl4-openssl-dev libsnappy-dev default-jre libasio-dev`. @@ -130,8 +176,8 @@ processor present in the Raspberry Pi. sudo -H ./tools/install-deps-native.sh ``` -1. To compile AWS IoT FleetWise Edge Agent software, run the following command. (If you are using a - local x86_64 development machine, use the `build-fwe-cross-arm64.sh` script instead.) +1. To compile your Edge Agent, run the following command. (If you are using a local x86_64 + development machine, use the `build-fwe-cross-arm64.sh` script instead.) ```bash ./tools/build-fwe-native.sh @@ -140,8 +186,8 @@ processor present in the Raspberry Pi. ## Step 4: Provision AWS IoT credentials On the development machine, create an IoT Thing with the name `fwdemo-rpi` and provision its -credentials by running the following command. The AWS IoT FleetWise Edge Agent binary and its -configuration files are packaged into a ZIP file that is ready for deployment to the Raspberry Pi. +credentials by running the following command. Your Edge Agent binary and its configuration files are +packaged into a ZIP file that is ready for deployment to the Raspberry Pi. ```bash mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ @@ -168,8 +214,6 @@ mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ ## Step 5: Deploy Edge Agent -### To deploy AWS IoT FleetWise Edge Agent - 1. On your local machine, copy the deployment ZIP file from the machine with Amazon EC2 to your local machine by running the following command: @@ -185,8 +229,8 @@ mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ ``` 1. As described in step 4 of [setting up the Raspberry Pi](#step-1-setup-the-raspberry-pi), connect - through SSH to the Raspberry Pi. On the Raspberry Pi, install AWS IoT FleetWise Edge Agent as a - service by running the following command: + through SSH to the Raspberry Pi. On the Raspberry Pi, install your Edge Agent as a service by + running the following command: ```bash mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ @@ -209,13 +253,12 @@ mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ ip link set up can0 txqueuelen 1000 type can bitrate 500000 restart-ms 100 ip link set up can1 txqueuelen 1000 type can bitrate 500000 restart-ms 100 ``` -1. Restart the setup-socketcan service and the IoT FleetWise Edge Agent service: +1. Restart the setup-socketcan service and your Edge Agent service: ``` sudo systemctl restart setup-socketcan sudo systemctl restart fwe@0 ``` -1. To verify the IoT FleetWise Edge Agent is running and is connected to the cloud, check the Edge - Agent log files: +1. To verify your Edge Agent is running and is connected to the cloud, check the log file: ``` sudo journalctl -fu fwe@0 --output=cat ``` @@ -225,15 +268,13 @@ mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ ``` - Use the [troubleshooting information and solutions](https://docs.aws.amazon.com/iot-fleetwise/latest/developerguide/troubleshooting.html) - in the AWS IoT FleetWise Developer Guide to help resolve issues with AWS IoT FleetWise Edge - Agent. + in the AWS IoT FleetWise Developer Guide to help resolve issues with FWE. ## Step 6: Deploy a campaign to the Raspberry Pi -1. On the development machine, install the AWS IoT FleetWise cloud demo script dependencies by - running the following commands. The script installs the following Ubuntu packages: - `python3 python3-pip`, and then installs the following PIP packages: - `wrapt plotly pandas cantools`. +1. On the development machine, install the AWS IoT FleetWise demo script dependencies by running the + following commands. The script installs the following Ubuntu packages: `python3 python3-pip`, and + then installs the following PIP packages: `wrapt plotly pandas cantools`. ```bash cd ~/aws-iot-fleetwise-edge/tools/cloud \ diff --git a/interfaces/fastdds/idl/Camera.idl b/interfaces/fastdds/idl/Camera.idl deleted file mode 100644 index e83cfbb2..00000000 --- a/interfaces/fastdds/idl/Camera.idl +++ /dev/null @@ -1,44 +0,0 @@ -// Maximum size of a frame e.g. 1024 * 1024 -const long MAX_FRAME_SIZE = 1048576; -const long MAX_FRAME_COUNT = 50; - -// A struct representing a single Frame. -// Consists of the timestamp when the Frame was captured -// and the actual frame data as a vector of bytes of MAX_FRAME_SIZE -struct CameraFrame { - - unsigned long long timestamp; - sequence frameData; -}; - -// A struct representing a Frame snapshot. -// Consist of a unique identifier of the snapshot ( Event that triggered it) -// and a vector of Frames of max size of MAX_IMAGES -struct CameraDataItem { - - // This allows FWE to identify the data item i.e. if it was requested - // or simply was put on the topic based on the ADAS system decision e.g. unknown object - // set to zero if this was triggered by the ADAS system. - string dataItemId; - // Metadata if any - unsigned long metadata; - // Frame Buffer which consists of a sequence of individual frames. - sequence frameBuffer; -}; - - -struct CameraDataRequest { - - // Unique Identifier of the request. Should be kept during response. - unsigned long dataItemId; - // Options: Should include settings around compression or cropping if needed. Else default is raw. - map options; - - // Set of offsets building up a time interval. - // The frame service should return all frames that were captured by the camera sensor - // between sample info source timestamp - negativeOffsetMs and + positiveOffsetMs - // Negative offset representing the number of milliseconds before the Sample Info source timestamp - unsigned long negativeOffsetMs; - // Positive offset representing the number of milliseconds after the Sample Info source timestamp - unsigned long positiveOffsetMs; -}; diff --git a/interfaces/persistency/examples/persistencyMetadataFormat.json b/interfaces/persistency/examples/persistencyMetadataFormat.json new file mode 100644 index 00000000..635f96a6 --- /dev/null +++ b/interfaces/persistency/examples/persistencyMetadataFormat.json @@ -0,0 +1,15 @@ +{ + "version": "1.0", + "files": [ + { + "filename": "exampleFile1.bin", + "payloadSize": 10, + "compressionRequired": false + }, + { + "filename": "exampleFile2.bin", + "payloadSize": 247, + "compressionRequired": true + } + ] +} diff --git a/interfaces/persistency/schemas/persistencyMetadataFormat.json b/interfaces/persistency/schemas/persistencyMetadataFormat.json new file mode 100644 index 00000000..a12311d5 --- /dev/null +++ b/interfaces/persistency/schemas/persistencyMetadataFormat.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "http://aws-iot-automotive.com/edgeConfiguration", + "type": "object", + "additionalProperties": false, + "title": "IoTFleetWise Persistency Metadata", + "description": "The persistency metadata schema. Payloads are stored in separate files", + "properties": { + "version": { + "type": "string", + "description": "Schema Version" + }, + "files": { + "type": "array", + "additionalProperties": false, + "description": "Persisted payload filenames with associated metadata", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "filename": { + "type": "string", + "description": "Payload filename" + }, + "payloadSize": { + "type": "number", + "description": "Payload size is used to validate that payload was persisted completely" + }, + "compressionRequired": { + "type": "boolean", + "description": "Specifies if payload compression was required by campaign" + } + }, + "required": ["filename", "payloadSize", "compressionRequired"] + } + } + }, + "required": ["version", "files"] +} diff --git a/interfaces/protobuf/schemas/cloudToEdge/collection_schemes.proto b/interfaces/protobuf/schemas/cloudToEdge/collection_schemes.proto index 87ebe358..0bdd62fb 100644 --- a/interfaces/protobuf/schemas/cloudToEdge/collection_schemes.proto +++ b/interfaces/protobuf/schemas/cloudToEdge/collection_schemes.proto @@ -7,7 +7,7 @@ option java_package = "com.amazonaws.iot.autobahn.schemas"; package Aws.IoTFleetWise.Schemas.CollectionSchemesMsg; /* - * List of collectionSchemes to be enacted by AWS IoT FleetWise Edge + * List of collectionSchemes */ message CollectionSchemes { @@ -103,9 +103,10 @@ message CollectionScheme { Probabilities probabilities = 14; /* - * Image Data to collect as part of this collectionScheme. + * This field was never supported, so it should not be used any more. */ - repeated ImageData image_data = 15; + reserved 15; + reserved "image_data"; } message Probabilities{ @@ -430,62 +431,3 @@ message RawCanFrame { */ uint32 minimum_sample_period_ms = 4; } - -message ImageData{ - - /* - * Image Source Node ID which contain network interface information needed to access the image source. - * - */ - uint32 image_source_node_id = 1; - - /* - * Each image source may have a list of supported image types. - */ - ImageType image_type = 2; - - /* - * Images may be collected in different ways, such as time based windows and frame based requests - */ - oneof image_collection_method { - - /* - * A time based window of before and after time relative to an event trigger. - */ - TimeBasedImageData time_based_image_data = 3; - } - - /* - * Image data to be collected within a specified time range centered at the event trigger time of the collectionScheme. Note - * that the number of frames collected in this time range will be dependant on the Frames Per Second rate support by - * the Camera ECU. - */ - message TimeBasedImageData { - - /* - * The duration in milliseconds of image data to collect before the event is triggered. For after duration, - * after_duration_ms of the collectionScheme is used. - */ - uint32 before_duration_ms = 1; - } - - /* - * Image sources may have one or more supported image types - this field lets the customer specify which image type - * they would like to collect. - */ - enum ImageType { - COMPRESSED_JPG = 0; - COMPRESSED_PNG = 1; - // Reserve 2-99 for compressed formats - RAW_RGB8 = 100; - RAW_RGBA8 = 101; - RAW_RGB16 = 102; - RAW_RGBA16 = 103; - RAW_BGR8 = 104; - RAW_BGRA8 = 105; - RAW_BGR16 = 106; - RAW_BGRA16 = 107; - RAW_MONO8= 108; - RAW_MONO16= 109; - } -} diff --git a/interfaces/protobuf/schemas/edgeConfiguration/staticConfiguration.json b/interfaces/protobuf/schemas/edgeConfiguration/staticConfiguration.json index c623d752..92a4ddeb 100644 --- a/interfaces/protobuf/schemas/edgeConfiguration/staticConfiguration.json +++ b/interfaces/protobuf/schemas/edgeConfiguration/staticConfiguration.json @@ -196,6 +196,10 @@ "metricsCyclicPrintIntervalMs": { "type": "string", "description": "Sets the interval in milliseconds how often the application metrics should be printed to stdout. Default 0 means never" + }, + "maximumAwsSdkHeapMemoryBytes": { + "type": "integer", + "description": "Set the maximum AWS SDK heap memory bytes. Default to 10000000" } }, "required": [ @@ -270,22 +274,49 @@ "rootCA": { "type": "string", "description": "The root CA certificate (optional)" + }, + "connectionType": { + "type": "string", + "enum": ["iotCore", "iotGreengrassV2"], + "description": "Choose the connection module. Default to iotCore" } }, "required": [ - "endpointUrl", - "clientId", "collectionSchemeListTopic", "decoderManifestTopic", "canDataTopic", "checkinTopic" ], - "oneOf": [ + "anyOf": [ + { + "properties": { + "connectionType": { "const": "iotCore" } + }, + "required": ["endpointUrl", "clientId"], + "oneOf": [ + { + "required": ["certificateFilename", "privateKeyFilename"] + }, + { + "required": ["certificate", "privateKey"] + } + ] + }, { - "required": ["certificateFilename", "privateKeyFilename"] + "properties": { + "connectionType": { "const": "iotGreengrassV2" } + } }, { - "required": ["certificate", "privateKey"] + "required": ["endpointUrl", "clientId"], + "oneOf": [ + { + "required": ["certificateFilename", "privateKeyFilename"] + }, + { + "required": ["certificate", "privateKey"] + } + ] } ] } diff --git a/interfaces/protobuf/schemas/edgeToCloud/vehicle_data.proto b/interfaces/protobuf/schemas/edgeToCloud/vehicle_data.proto index 81df35d4..3e707a4a 100644 --- a/interfaces/protobuf/schemas/edgeToCloud/vehicle_data.proto +++ b/interfaces/protobuf/schemas/edgeToCloud/vehicle_data.proto @@ -7,7 +7,7 @@ option java_package = "com.amazonaws.iot.autobahn.schemas"; package Aws.IoTFleetWise.Schemas.VehicleDataMsg; /* - * VehicleData payload from IoTFleetWise Edge for all events triggered by CollectionSchemes including condition based + * VehicleData payload for all events triggered by CollectionSchemes including condition based * events and periodic time based events. */ message VehicleData { diff --git a/src/datamanagement/CMakeLists.txt b/src/datamanagement/CMakeLists.txt index c5313667..ba225326 100644 --- a/src/datamanagement/CMakeLists.txt +++ b/src/datamanagement/CMakeLists.txt @@ -3,6 +3,7 @@ add_subdirectory(datacollection) add_subdirectory(datadecoding) add_subdirectory(datainspection) add_subdirectory(datamanager) +add_subdirectory(datasender) if(FWE_FEATURE_CUSTOM_DATA_SOURCE) add_subdirectory(custom) endif() diff --git a/src/datamanagement/custom/CMakeLists.txt b/src/datamanagement/custom/CMakeLists.txt index 9b234f10..6c198d6c 100644 --- a/src/datamanagement/custom/CMakeLists.txt +++ b/src/datamanagement/custom/CMakeLists.txt @@ -10,6 +10,9 @@ endif() if(FWE_FEATURE_EXTERNAL_GPS) set(EXTRA_SOURCE_FILES ${EXTRA_SOURCE_FILES} example/externalgps/src/ExternalGpsSource.cpp) endif() +if(FWE_FEATURE_AAOS_VHAL) + set(EXTRA_SOURCE_FILES ${EXTRA_SOURCE_FILES} example/aaosvhal/src/AaosVhalSource.cpp) +endif() set(SRCS generic/src/CustomDataSource.cpp ${EXTRA_SOURCE_FILES} @@ -27,6 +30,9 @@ endif() if(FWE_FEATURE_EXTERNAL_GPS) set(EXTRA_INCLUDE_DIRS ${EXTRA_INCLUDE_DIRS} example/externalgps/include) endif() +if(FWE_FEATURE_AAOS_VHAL) + set(EXTRA_INCLUDE_DIRS ${EXTRA_INCLUDE_DIRS} example/aaosvhal/include) +endif() target_include_directories(${libraryTargetName} PUBLIC generic/include ${EXTRA_INCLUDE_DIRS} @@ -55,6 +61,9 @@ endif() if(FWE_FEATURE_EXTERNAL_GPS) set(EXTRA_INSTALL_FILES ${EXTRA_INSTALL_FILES} example/externalgps/include/ExternalGpsSource.h) endif() +if(FWE_FEATURE_AAOS_VHAL) + set(EXTRA_INSTALL_FILES ${EXTRA_INSTALL_FILES} example/aaosvhal/include/AaosVhalSource.h) +endif() install( FILES generic/include/CustomDataSource.h @@ -84,6 +93,9 @@ if(${BUILD_TESTING}) if(FWE_FEATURE_EXTERNAL_GPS) set(EXTRA_TEST_FILES ${EXTRA_TEST_FILES} example/externalgps/test/ExternalGpsSourceTest.cpp) endif() + if(FWE_FEATURE_AAOS_VHAL) + set(EXTRA_TEST_FILES ${EXTRA_TEST_FILES} example/aaosvhal/test/AaosVhalSourceTest.cpp) + endif() set( testSources generic/test/CustomDataSourceTest.cpp diff --git a/src/datamanagement/custom/README.md b/src/datamanagement/custom/README.md index 7cfc8f93..972a60cd 100644 --- a/src/datamanagement/custom/README.md +++ b/src/datamanagement/custom/README.md @@ -1,21 +1,20 @@ # Custom data source (treating data not on CAN as CAN signals) -> :warning: **Internal code** structures of AWS IoT FleetWise Edge **might be fundamentally -> restructured**, so extending the code might involve significant work when upgrading to newer -> versions. To avoid this problems the external interfaces can be used to hand over data to AWS IoT -> FleetWise Edge over supported communication channels. For example use a small custom separate -> process to read data and put it to a real or a virtual CAN that is then read by AWS IoT FleetWise -> Edge. +> :warning: **Internal code** structures of the Reference Implementation for AWS IoT FleetWise +> ("FWE") **might be fundamentally restructured**, so extending the code might involve significant +> work when upgrading to newer versions. To avoid this problems the external interfaces can be used +> to hand over data to FWE over supported communication channels. For example use a small custom +> separate process to read data and put it to a real or a virtual CAN that is then read by FWE. Considering the warning this gives a temporary workaround to get the data directly into AWS IoT FleetWise _without_ ever putting it on a real or virtual SocketCAN interface. In the following we will read data from a custom data source (like a tty or a file etc.) convert it to a double and pass it on to be treated like a Signal read on CAN. All this will happen in a separate thread but inside -the AWS IoT FleetWise Edge agent. We will use an example where we read NMEA GPS data from a file and -handle them as if the were received on a CAN. We assume we have a the two signals -`Vehicle.CurrentLocation.Longitude` and `Vehicle.CurrentLocation.Latitude` in our signal catalog and -in a model manifest. For this the API calls UpdateSignalCatalog and CreateModelManifest can be used. -Now we create a special decoder manifest +FWE. We will use an example where we read NMEA GPS data from a file and handle them as if the were +received on a CAN. We assume we have a the two signals `Vehicle.CurrentLocation.Longitude` and +`Vehicle.CurrentLocation.Latitude` in our signal catalog and in a model manifest. For this the API +calls UpdateSignalCatalog and CreateModelManifest can be used. Now we create a special decoder +manifest ```json { diff --git a/src/datamanagement/custom/example/aaosvhal/include/AaosVhalSource.h b/src/datamanagement/custom/example/aaosvhal/include/AaosVhalSource.h new file mode 100644 index 00000000..aca01ce7 --- /dev/null +++ b/src/datamanagement/custom/example/aaosvhal/include/AaosVhalSource.h @@ -0,0 +1,78 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include "ClockHandler.h" +#include "CollectionInspectionAPITypes.h" +#include "CustomDataSource.h" +#include "Timer.h" +#include +#include +#include + +namespace Aws +{ +namespace IoTFleetWise +{ +namespace DataManagement +{ +using namespace Aws::IoTFleetWise::Platform::Linux; +using namespace Aws::IoTFleetWise::VehicleNetwork; +using namespace Aws::IoTFleetWise::DataInspection; + +/** + * To implement a custom data source create a new class and inherit from CustomDataSource + * then call setFilter() then start() and provide an implementation for pollData + */ +class AaosVhalSource : public CustomDataSource +{ +public: + /** + * @param signalBufferPtr the signal buffer is used pass extracted data + */ + AaosVhalSource( SignalBufferPtr signalBufferPtr ); + /** + * Initialize AaosVhalSource and set filter for CustomDataSource + * + * @param canChannel the CAN channel used in the decoder manifest + * @param canRawFrameId the CAN message Id used in the decoder manifest + * + * @return on success true otherwise false + */ + bool init( CANChannelNumericID canChannel, CANRawFrameID canRawFrameId ); + + /** + * Returns a vector of vehicle property info + * + * @return Vehicle property info, with each member containing an array with 4 values: + * - Vehicle property ID + * - Area index + * - Result index + * - Signal ID + */ + std::vector> getVehiclePropertyInfo(); + + /** + * Set Vehicle property value + * + * @param signalId Signal ID + * @param value Property value + */ + void setVehicleProperty( SignalID signalId, double value ); + + static constexpr const char *CAN_CHANNEL_NUMBER = "canChannel"; + static constexpr const char *CAN_RAW_FRAME_ID = "canFrameId"; + +protected: + void pollData() override; + const char *getThreadName() override; + +private: + SignalBufferPtr mSignalBufferPtr; + std::shared_ptr mClock = ClockHandler::getClock(); + CANChannelNumericID mCanChannel{ INVALID_CAN_SOURCE_NUMERIC_ID }; + CANRawFrameID mCanRawFrameId{ 0 }; +}; +} // namespace DataManagement +} // namespace IoTFleetWise +} // namespace Aws diff --git a/src/datamanagement/custom/example/aaosvhal/src/AaosVhalSource.cpp b/src/datamanagement/custom/example/aaosvhal/src/AaosVhalSource.cpp new file mode 100644 index 00000000..d7793b73 --- /dev/null +++ b/src/datamanagement/custom/example/aaosvhal/src/AaosVhalSource.cpp @@ -0,0 +1,76 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Includes +#include "AaosVhalSource.h" +#include "LoggingModule.h" +#include "TraceModule.h" + +namespace Aws +{ +namespace IoTFleetWise +{ +namespace DataManagement +{ +constexpr const char *AaosVhalSource::CAN_CHANNEL_NUMBER; +constexpr const char *AaosVhalSource::CAN_RAW_FRAME_ID; +AaosVhalSource::AaosVhalSource( SignalBufferPtr signalBufferPtr ) + : mSignalBufferPtr{ std::move( signalBufferPtr ) } +{ +} + +bool +AaosVhalSource::init( CANChannelNumericID canChannel, CANRawFrameID canRawFrameId ) +{ + if ( canChannel == INVALID_CAN_SOURCE_NUMERIC_ID ) + { + return false; + } + mCanChannel = canChannel; + mCanRawFrameId = canRawFrameId; + setFilter( mCanChannel, mCanRawFrameId ); + return true; +} +const char * +AaosVhalSource::getThreadName() +{ + return "AaosVhalSource"; +} + +std::vector> +AaosVhalSource::getVehiclePropertyInfo() +{ + std::vector> propertyInfo; + auto signalInfo = getSignalInfo(); + for ( const auto &signal : signalInfo ) + { + propertyInfo.push_back( std::array{ + static_cast( signal.mOffset ), // Vehicle property ID + signal.mFirstBitPosition, // Area index + signal.mSizeInBits, // Result index + signal.mSignalID // Signal ID + } ); + } + return propertyInfo; +} + +void +AaosVhalSource::setVehicleProperty( SignalID signalId, double value ) +{ + auto timestamp = mClock->systemTimeSinceEpochMs(); + TraceModule::get().incrementAtomicVariable( TraceAtomicVariable::QUEUE_CONSUMER_TO_INSPECTION_SIGNALS ); + if ( !mSignalBufferPtr->push( CollectedSignal( signalId, timestamp, value ) ) ) + { + TraceModule::get().decrementAtomicVariable( TraceAtomicVariable::QUEUE_CONSUMER_TO_INSPECTION_SIGNALS ); + FWE_LOG_WARN( "Signal buffer full" ); + } +} + +void +AaosVhalSource::pollData() +{ +} + +} // namespace DataManagement +} // namespace IoTFleetWise +} // namespace Aws diff --git a/src/datamanagement/custom/example/aaosvhal/test/AaosVhalSourceTest.cpp b/src/datamanagement/custom/example/aaosvhal/test/AaosVhalSourceTest.cpp new file mode 100644 index 00000000..62e9c0d4 --- /dev/null +++ b/src/datamanagement/custom/example/aaosvhal/test/AaosVhalSourceTest.cpp @@ -0,0 +1,75 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +#include "AaosVhalSource.h" +#include "WaitUntil.h" +#include +#include + +using namespace Aws::IoTFleetWise::DataManagement; +using namespace Aws::IoTFleetWise::TestingSupport; + +class AaosVhalSourceTest : public ::testing::Test +{ +protected: + void + SetUp() override + { + std::unordered_map frameMap; + CANMessageDecoderMethod decoderMethod; + decoderMethod.collectType = CANMessageCollectType::DECODE; + decoderMethod.format.mMessageID = 12345; + CANSignalFormat sig1; + CANSignalFormat sig2; + sig1.mOffset = 0x0207; + sig1.mFirstBitPosition = 0xAA; + sig1.mSizeInBits = 0x55; + sig1.mSignalID = 0x1234; + sig2.mOffset = 0x0209; + sig2.mFirstBitPosition = 0x55; + sig2.mSizeInBits = 0xAA; + sig2.mSignalID = 0x5678; + decoderMethod.format.mSignals.push_back( sig1 ); + decoderMethod.format.mSignals.push_back( sig2 ); + frameMap[1] = decoderMethod; + mDictionary = std::make_shared(); + mDictionary->canMessageDecoderMethod[1] = frameMap; + } + + void + TearDown() override + { + } + + std::shared_ptr mDictionary; +}; + +TEST_F( AaosVhalSourceTest, testDecoding ) // NOLINT +{ + SignalBufferPtr signalBufferPtr = std::make_shared( 100 ); + AaosVhalSource vhalSource( signalBufferPtr ); + ASSERT_FALSE( vhalSource.init( INVALID_CAN_SOURCE_NUMERIC_ID, 1 ) ); + ASSERT_TRUE( vhalSource.init( 1, 1 ) ); + vhalSource.start(); + CollectedSignal firstSignal; + CollectedSignal secondSignal; + DELAY_ASSERT_FALSE( signalBufferPtr->pop( firstSignal ) ); + vhalSource.onChangeOfActiveDictionary( mDictionary, VehicleDataSourceProtocol::RAW_SOCKET ); + DELAY_ASSERT_FALSE( signalBufferPtr->pop( firstSignal ) ); + auto propInfo = vhalSource.getVehiclePropertyInfo(); + ASSERT_EQ( 2, propInfo.size() ); + auto sig1Info = std::array{ 0x0207, 0xAA, 0x55, 0x1234 }; + ASSERT_EQ( sig1Info, propInfo[0] ); + auto sig2Info = std::array{ 0x0209, 0x55, 0xAA, 0x5678 }; + ASSERT_EQ( sig2Info, propInfo[1] ); + vhalSource.setVehicleProperty( 0x1234, 52.5761 ); + vhalSource.setVehicleProperty( 0x5678, 12.5761 ); + + WAIT_ASSERT_TRUE( signalBufferPtr->pop( firstSignal ) ); + ASSERT_TRUE( signalBufferPtr->pop( secondSignal ) ); + ASSERT_EQ( firstSignal.signalID, 0x1234 ); + ASSERT_EQ( secondSignal.signalID, 0x5678 ); + ASSERT_NEAR( firstSignal.value.value.doubleVal, 52.5761, 0.0001 ); + ASSERT_NEAR( secondSignal.value.value.doubleVal, 12.5761, 0.0001 ); + + ASSERT_TRUE( vhalSource.stop() ); +} diff --git a/src/datamanagement/custom/example/externalgps/include/ExternalGpsSource.h b/src/datamanagement/custom/example/externalgps/include/ExternalGpsSource.h index cda4a923..19a6f73d 100644 --- a/src/datamanagement/custom/example/externalgps/include/ExternalGpsSource.h +++ b/src/datamanagement/custom/example/externalgps/include/ExternalGpsSource.h @@ -59,8 +59,6 @@ class ExternalGpsSource : public CustomDataSource static bool validLatitude( double latitude ); static bool validLongitude( double longitude ); - static const uint32_t CYCLIC_LOG_PERIOD_MS = 1000; - uint16_t mLatitudeStartBit = 0; uint16_t mLongitudeStartBit = 0; diff --git a/src/datamanagement/custom/example/externalgps/src/ExternalGpsSource.cpp b/src/datamanagement/custom/example/externalgps/src/ExternalGpsSource.cpp index f4fd6458..a80beb28 100644 --- a/src/datamanagement/custom/example/externalgps/src/ExternalGpsSource.cpp +++ b/src/datamanagement/custom/example/externalgps/src/ExternalGpsSource.cpp @@ -1,9 +1,10 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -#if defined( IOTFLEETWISE_LINUX ) + // Includes #include "ExternalGpsSource.h" #include "LoggingModule.h" +#include "TraceModule.h" namespace Aws { @@ -61,8 +62,18 @@ ExternalGpsSource::setLocation( double latitude, double longitude ) return; } auto timestamp = mClock->systemTimeSinceEpochMs(); - mSignalBufferPtr->push( CollectedSignal( latitudeSignalId, timestamp, latitude ) ); - mSignalBufferPtr->push( CollectedSignal( longitudeSignalId, timestamp, longitude ) ); + TraceModule::get().incrementAtomicVariable( TraceAtomicVariable::QUEUE_CONSUMER_TO_INSPECTION_SIGNALS ); + if ( !mSignalBufferPtr->push( CollectedSignal( latitudeSignalId, timestamp, latitude ) ) ) + { + TraceModule::get().decrementAtomicVariable( TraceAtomicVariable::QUEUE_CONSUMER_TO_INSPECTION_SIGNALS ); + FWE_LOG_WARN( "Signal buffer full" ); + } + TraceModule::get().incrementAtomicVariable( TraceAtomicVariable::QUEUE_CONSUMER_TO_INSPECTION_SIGNALS ); + if ( !mSignalBufferPtr->push( CollectedSignal( longitudeSignalId, timestamp, longitude ) ) ) + { + TraceModule::get().decrementAtomicVariable( TraceAtomicVariable::QUEUE_CONSUMER_TO_INSPECTION_SIGNALS ); + FWE_LOG_WARN( "Signal buffer full" ); + } } void @@ -84,4 +95,3 @@ ExternalGpsSource::validLongitude( double longitude ) } // namespace DataManagement } // namespace IoTFleetWise } // namespace Aws -#endif diff --git a/src/datamanagement/custom/example/externalgps/test/ExternalGpsSourceTest.cpp b/src/datamanagement/custom/example/externalgps/test/ExternalGpsSourceTest.cpp index a502f470..83562b35 100644 --- a/src/datamanagement/custom/example/externalgps/test/ExternalGpsSourceTest.cpp +++ b/src/datamanagement/custom/example/externalgps/test/ExternalGpsSourceTest.cpp @@ -6,6 +6,7 @@ #include using namespace Aws::IoTFleetWise::DataManagement; +using namespace Aws::IoTFleetWise::TestingSupport; class ExternalGpsSourceTest : public ::testing::Test { @@ -43,19 +44,29 @@ TEST_F( ExternalGpsSourceTest, testDecoding ) // NOLINT { SignalBufferPtr signalBufferPtr = std::make_shared( 100 ); ExternalGpsSource gpsSource( signalBufferPtr ); - gpsSource.init( 1, 1, 0, 32 ); + ASSERT_FALSE( gpsSource.init( INVALID_CAN_SOURCE_NUMERIC_ID, 1, 0, 32 ) ); + ASSERT_TRUE( gpsSource.init( 1, 1, 0, 32 ) ); gpsSource.start(); - gpsSource.setLocation( 52.5761, 12.5761 ); - gpsSource.onChangeOfActiveDictionary( mDictionary, VehicleDataSourceProtocol::RAW_SOCKET ); - CollectedSignal firstSignal; CollectedSignal secondSignal; + DELAY_ASSERT_FALSE( signalBufferPtr->pop( firstSignal ) ); + gpsSource.onChangeOfActiveDictionary( mDictionary, VehicleDataSourceProtocol::RAW_SOCKET ); + DELAY_ASSERT_FALSE( signalBufferPtr->pop( firstSignal ) ); + gpsSource.setLocation( 360, 360 ); // Invalid + gpsSource.setLocation( 52.5761, 12.5761 ); + WAIT_ASSERT_TRUE( signalBufferPtr->pop( firstSignal ) ); ASSERT_TRUE( signalBufferPtr->pop( secondSignal ) ); ASSERT_EQ( firstSignal.signalID, 0x1234 ); ASSERT_EQ( secondSignal.signalID, 0x5678 ); ASSERT_NEAR( firstSignal.value.value.doubleVal, 52.5761, 0.0001 ); ASSERT_NEAR( secondSignal.value.value.doubleVal, 12.5761, 0.0001 ); + + ASSERT_TRUE( gpsSource.init( 1, 1, 123, 456 ) ); // Invalid start bits + gpsSource.setLocation( 52.5761, 12.5761 ); + DELAY_ASSERT_FALSE( signalBufferPtr->pop( firstSignal ) ); + + ASSERT_TRUE( gpsSource.stop() ); } // Test longitude west @@ -65,15 +76,19 @@ TEST_F( ExternalGpsSourceTest, testWestNegativeLongitude ) // NOLINT ExternalGpsSource gpsSource( signalBufferPtr ); gpsSource.init( 1, 1, 0, 32 ); gpsSource.start(); - gpsSource.setLocation( 52.5761, -12.5761 ); - gpsSource.onChangeOfActiveDictionary( mDictionary, VehicleDataSourceProtocol::RAW_SOCKET ); - CollectedSignal firstSignal; CollectedSignal secondSignal; + DELAY_ASSERT_FALSE( signalBufferPtr->pop( firstSignal ) ); + gpsSource.onChangeOfActiveDictionary( mDictionary, VehicleDataSourceProtocol::RAW_SOCKET ); + DELAY_ASSERT_FALSE( signalBufferPtr->pop( firstSignal ) ); + gpsSource.setLocation( 52.5761, -12.5761 ); + WAIT_ASSERT_TRUE( signalBufferPtr->pop( firstSignal ) ); ASSERT_TRUE( signalBufferPtr->pop( secondSignal ) ); ASSERT_EQ( firstSignal.signalID, 0x1234 ); ASSERT_EQ( secondSignal.signalID, 0x5678 ); ASSERT_NEAR( firstSignal.value.value.doubleVal, 52.5761, 0.0001 ); ASSERT_NEAR( secondSignal.value.value.doubleVal, -12.5761, 0.0001 ); // negative number + + ASSERT_TRUE( gpsSource.stop() ); } diff --git a/src/datamanagement/custom/example/iwavegps/README.md b/src/datamanagement/custom/example/iwavegps/README.md index 59f86888..9291ba22 100644 --- a/src/datamanagement/custom/example/iwavegps/README.md +++ b/src/datamanagement/custom/example/iwavegps/README.md @@ -17,7 +17,7 @@ The decoder manifest changes are explained in the README in the parent folder # Configuration If you provide in the `staticConfig` section of the config.json file the following section you can -change the parameters without recompiling AWS IoT FleetWise Edge: +change the parameters without recompiling FWE: ``` "iWaveGpsExample": { diff --git a/src/datamanagement/custom/example/iwavegps/src/IWaveGpsSource.cpp b/src/datamanagement/custom/example/iwavegps/src/IWaveGpsSource.cpp index 88986ef4..aaf113e9 100644 --- a/src/datamanagement/custom/example/iwavegps/src/IWaveGpsSource.cpp +++ b/src/datamanagement/custom/example/iwavegps/src/IWaveGpsSource.cpp @@ -4,6 +4,7 @@ // Includes #include "IWaveGpsSource.h" #include "LoggingModule.h" +#include "TraceModule.h" #include #include #include @@ -97,10 +98,22 @@ IWaveGpsSource::pollData() { mValidCoordinateCounter++; auto timestamp = mClock->systemTimeSinceEpochMs(); - mSignalBufferPtr->push( - CollectedSignal( getSignalIdFromStartBit( mLatitudeStartBit ), timestamp, lastValidLatitude ) ); - mSignalBufferPtr->push( - CollectedSignal( getSignalIdFromStartBit( mLongitudeStartBit ), timestamp, lastValidLongitude ) ); + + TraceModule::get().incrementAtomicVariable( TraceAtomicVariable::QUEUE_CONSUMER_TO_INSPECTION_SIGNALS ); + if ( !mSignalBufferPtr->push( + CollectedSignal( getSignalIdFromStartBit( mLatitudeStartBit ), timestamp, lastValidLatitude ) ) ) + { + TraceModule::get().decrementAtomicVariable( TraceAtomicVariable::QUEUE_CONSUMER_TO_INSPECTION_SIGNALS ); + FWE_LOG_WARN( "Signal buffer full" ); + } + + TraceModule::get().incrementAtomicVariable( TraceAtomicVariable::QUEUE_CONSUMER_TO_INSPECTION_SIGNALS ); + if ( !mSignalBufferPtr->push( + CollectedSignal( getSignalIdFromStartBit( mLongitudeStartBit ), timestamp, lastValidLongitude ) ) ) + { + TraceModule::get().decrementAtomicVariable( TraceAtomicVariable::QUEUE_CONSUMER_TO_INSPECTION_SIGNALS ); + FWE_LOG_WARN( "Signal buffer full" ); + } } if ( mCyclicLoggingTimer.getElapsedMs().count() > CYCLIC_LOG_PERIOD_MS ) { diff --git a/src/datamanagement/custom/example/iwavegps/test/IWaveGpsSourceTest.cpp b/src/datamanagement/custom/example/iwavegps/test/IWaveGpsSourceTest.cpp index a7322233..094667c9 100644 --- a/src/datamanagement/custom/example/iwavegps/test/IWaveGpsSourceTest.cpp +++ b/src/datamanagement/custom/example/iwavegps/test/IWaveGpsSourceTest.cpp @@ -69,13 +69,15 @@ TEST_F( IWaveGpsSourceTest, testDecoding ) "$GPVTG,29.30,T,31.32,M,33.34,N,35.36,K,A*37C\n\n\n"; nmeaFile->close(); IWaveGpsSource gpsSource( signalBufferPtr ); - gpsSource.init( filePath, 1, 1, 0, 32 ); + ASSERT_FALSE( gpsSource.init( filePath, INVALID_CAN_SOURCE_NUMERIC_ID, 1, 0, 32 ) ); + ASSERT_TRUE( gpsSource.init( filePath, 1, 1, 0, 32 ) ); gpsSource.connect(); gpsSource.start(); - gpsSource.onChangeOfActiveDictionary( mDictionary, VehicleDataSourceProtocol::RAW_SOCKET ); - CollectedSignal firstSignal; CollectedSignal secondSignal; + DELAY_ASSERT_FALSE( signalBufferPtr->pop( firstSignal ) ); + gpsSource.onChangeOfActiveDictionary( mDictionary, VehicleDataSourceProtocol::RAW_SOCKET ); + WAIT_ASSERT_TRUE( signalBufferPtr->pop( firstSignal ) ); ASSERT_TRUE( signalBufferPtr->pop( secondSignal ) ); ASSERT_EQ( firstSignal.signalID, 0x1234 ); @@ -83,6 +85,9 @@ TEST_F( IWaveGpsSourceTest, testDecoding ) // raw value from NMEA 5234.56789 01234.56789 converted to DD ASSERT_NEAR( firstSignal.value.value.doubleVal, 52.5761, 0.0001 ); ASSERT_NEAR( secondSignal.value.value.doubleVal, 12.5761, 0.0001 ); + + ASSERT_TRUE( gpsSource.stop() ); + ASSERT_TRUE( gpsSource.disconnect() ); } // Test longitude west @@ -107,4 +112,7 @@ TEST_F( IWaveGpsSourceTest, testWestNegativeLongitude ) // raw value from NMEA 5234.56789 01234.56789 converted to DD ASSERT_NEAR( firstSignal.value.value.doubleVal, 52.5761, 0.0001 ); ASSERT_NEAR( secondSignal.value.value.doubleVal, -12.5761, 0.0001 ); // negative number + + ASSERT_TRUE( gpsSource.stop() ); + ASSERT_TRUE( gpsSource.disconnect() ); } diff --git a/src/datamanagement/custom/generic/include/CustomDataSource.h b/src/datamanagement/custom/generic/include/CustomDataSource.h index bb3191b0..cd4b7b66 100644 --- a/src/datamanagement/custom/generic/include/CustomDataSource.h +++ b/src/datamanagement/custom/generic/include/CustomDataSource.h @@ -3,7 +3,9 @@ #pragma once #include "IActiveDecoderDictionaryListener.h" +#include "SignalTypes.h" #include "Thread.h" +#include namespace Aws { @@ -41,7 +43,6 @@ class CustomDataSource : public IActiveDecoderDictionaryListener /** * Get the unique SignalID, which is used to assign values to signals - * Only call from inside doWork/pollData function * * @param startBit defined in the DecoderManifest in the message given to setFilter * @@ -50,6 +51,13 @@ class CustomDataSource : public IActiveDecoderDictionaryListener */ SignalID getSignalIdFromStartBit( uint16_t startBit ); + /** + * Returns the signal information from the decoder manifest + * + * @return Signal info + */ + std::vector getSignalInfo(); + ~CustomDataSource() override; CustomDataSource( const CustomDataSource & ) = delete; CustomDataSource &operator=( const CustomDataSource & ) = delete; diff --git a/src/datamanagement/custom/generic/src/CustomDataSource.cpp b/src/datamanagement/custom/generic/src/CustomDataSource.cpp index 29fb9030..974b669b 100644 --- a/src/datamanagement/custom/generic/src/CustomDataSource.cpp +++ b/src/datamanagement/custom/generic/src/CustomDataSource.cpp @@ -52,6 +52,7 @@ CustomDataSource::start() SignalID CustomDataSource::getSignalIdFromStartBit( uint16_t startBit ) { + std::lock_guard lock( mExtractionOngoing ); for ( auto &signal : mUsedMessageFormat.mSignals ) { if ( signal.mFirstBitPosition == startBit ) @@ -62,6 +63,13 @@ CustomDataSource::getSignalIdFromStartBit( uint16_t startBit ) return INVALID_SIGNAL_ID; } +std::vector +CustomDataSource::getSignalInfo() +{ + std::lock_guard lock( mExtractionOngoing ); + return mUsedMessageFormat.mSignals; +} + bool CustomDataSource::stop() { diff --git a/src/datamanagement/datacollection/CMakeLists.txt b/src/datamanagement/datacollection/CMakeLists.txt index ecb15d02..3ac3f49b 100644 --- a/src/datamanagement/datacollection/CMakeLists.txt +++ b/src/datamanagement/datacollection/CMakeLists.txt @@ -7,8 +7,6 @@ set(libraryAliasName IoTFleetWise::DataCollection) set(SRCS src/CollectionSchemeIngestion.cpp src/CollectionSchemeIngestionList.cpp - src/DataCollectionProtoWriter.cpp - src/DataCollectionSender.cpp ) add_library( @@ -45,8 +43,6 @@ install( include/CANInterfaceIDTranslator.h include/CollectionSchemeIngestion.h include/CollectionSchemeIngestionList.h - include/DataCollectionProtoWriter.h - include/DataCollectionSender.h include/ICollectionScheme.h include/ICollectionSchemeList.h DESTINATION include @@ -55,23 +51,10 @@ install( if(${BUILD_TESTING}) message(STATUS "Building tests for ${libraryTargetName}") - find_library(GMOCK_LIB - NAMES - gmock) - - find_library(GMOCK_MAIN_LIBRARY - NAMES - gmock_main) - - file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/test/dm-collection-scheme-example.json - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) - - set( testSources - test/DataCollectionProtoWriterTest.cpp - test/DataCollectionSenderTest.cpp ) + # Add the executable targets foreach(testSource ${testSources}) # Need a name for each exec so use filename w/o extension @@ -84,9 +67,7 @@ if(${BUILD_TESTING}) ${testName} PRIVATE ${libraryTargetName} - Boost::filesystem IoTFleetWise::TestingSupport - ${GMOCK_LIB} ) target_include_directories( diff --git a/src/datamanagement/datacollection/include/CollectionSchemeIngestion.h b/src/datamanagement/datacollection/include/CollectionSchemeIngestion.h index 9d6528cd..374f21c1 100644 --- a/src/datamanagement/datacollection/include/CollectionSchemeIngestion.h +++ b/src/datamanagement/datacollection/include/CollectionSchemeIngestion.h @@ -58,8 +58,6 @@ class CollectionSchemeIngestion : public ICollectionScheme const RawCanFrames_t &getCollectRawCanFrames() const override; - const ImagesDataType &getImageCaptureData() const override; - bool isPersistNeeded() const override; bool isCompressionNeeded() const override; @@ -93,11 +91,6 @@ class CollectionSchemeIngestion : public ICollectionScheme */ RawCanFrames_t mCollectedRawCAN; - /** - * @brief Vector of Image Capture metadata. - */ - ImagesDataType mImagesCaptureData; - /** * @brief Expression Node Pointer to the Tree Root */ diff --git a/src/datamanagement/datacollection/include/DataCollectionSender.h b/src/datamanagement/datacollection/include/DataCollectionSender.h deleted file mode 100644 index a6bd8913..00000000 --- a/src/datamanagement/datacollection/include/DataCollectionSender.h +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -// Includes -#include "CollectionInspectionAPITypes.h" -#include "DataCollectionProtoWriter.h" -#include "ISender.h" -#include -#include -#include - -namespace Aws -{ -namespace IoTFleetWise -{ -namespace DataManagement -{ -using namespace Aws::IoTFleetWise::Platform::Linux; -using namespace Aws::IoTFleetWise::DataInspection; -using namespace Aws::IoTFleetWise::OffboardConnectivity; -using Aws::IoTFleetWise::OffboardConnectivity::CollectionSchemeParams; - -/** - * @brief Destination type of the data. - * - */ -enum class SendDestination -{ - MQTT -}; - -/** - * @brief Serializes collected data and sends it to the cloud. - * The maxMessageCount option limits the number of messages - * (or signals) appended to the protobuf message before the - * protobuf is serialized and sent to the cloud. - */ -class DataCollectionSender -{ -public: - /** - * @brief Constructor. Setup the DataCollectionSender. - * - * @param sender ISender interface to the cloud - * @param maxMessageCount Maximum number of messages before the data is serialized and sent to the cloud - * @param canIDTranslator Needed to translate the internal used can channel id to the can interface id used by - */ - DataCollectionSender( std::shared_ptr sender, - unsigned maxMessageCount, - CANInterfaceIDTranslator &canIDTranslator ); - - /** - * @brief Serializes the collected data and transmits it to the cloud - * - * @param triggeredCollectionSchemeDataPtr pointer to the collected data and metadata - * to be sent to cloud - */ - void send( const TriggeredCollectionSchemeDataPtr triggeredCollectionSchemeDataPtr ); - - /** - * @brief Send the serialized data to the cloud - * - * @return SUCCESS if transmit was successful, else return an errorcode - */ - ConnectivityError transmit(); - - /** - * @brief Send the serialized data to the cloud - * - * @param payload payload proto to be transmitted - * @return SUCCESS if transmit was successful, else return an errorcode - */ - ConnectivityError transmit( const std::string &payload ); - -private: - // A unique ID that FWE generates each time a collectionScheme condition is triggered - uint32_t mCollectionEventID{}; - std::shared_ptr mSender; - SendDestination mSendDestination{ SendDestination::MQTT }; - unsigned mTransmitThreshold; // max number of messages that can be sent to cloud at one time - std::string mPersistencyPath; - DataCollectionProtoWriter mProtoWriter; - std::string mProtoOutput; - CollectionSchemeParams mCollectionSchemeParams; - - /** - * @brief Set up collectionSchemeParams struct - */ - void setCollectionSchemeParameters( const TriggeredCollectionSchemeData &triggeredCollectionSchemeDataPtr ); - - /** - * @brief Serialize and send the protobuf data to the cloud - */ - void serializeAndTransmit(); -}; - -} // namespace DataManagement -} // namespace IoTFleetWise -} // namespace Aws diff --git a/src/datamanagement/datacollection/include/ICollectionScheme.h b/src/datamanagement/datacollection/include/ICollectionScheme.h index 639f0942..d115b5c6 100644 --- a/src/datamanagement/datacollection/include/ICollectionScheme.h +++ b/src/datamanagement/datacollection/include/ICollectionScheme.h @@ -19,37 +19,6 @@ namespace IoTFleetWise namespace DataManagement { -// Camera Data collection related types -enum class ImageCollectionType -{ - TIME_BASED, - FRAME_BASED, - NONE -}; - -struct ImageCollectionInfo -{ - ImageCollectionInfo() = default; - ImageCollectionInfo( ImageDeviceID id, uint32_t format, ImageCollectionType cType, uint32_t beforeDurationMsIn ) - : deviceID( id ) - , imageFormat( format ) - , collectionType( cType ) - , beforeDurationMs( beforeDurationMsIn ) - { - } - ImageDeviceID deviceID{ 0 }; // Unique Identifier of the image sensor in the system - uint32_t imageFormat{ 0 }; // Image format expected from the System e.g. PNG. - // Exact ids of the type will end up in an enum in the - // CollectionScheme decoder. - ImageCollectionType collectionType{ ImageCollectionType::NONE }; // Whether Images are collected from the device - // based on a timewindow or based on frame number. - uint32_t beforeDurationMs{ 0 }; // Amount of time in ms to be collected from the - // image sensor buffer. This time is counted before a - // condition is met and thus can be used to create - // a time interval before and after a certain condition is - // met in the system. -}; - struct SignalCollectionInfo { /** @@ -234,12 +203,6 @@ class ICollectionScheme using RawCanFrames_t = std::vector; const RawCanFrames_t INVALID_RAW_CAN_COLLECTED_SIGNALS = std::vector(); - /** - * @brief ImagesDataType is vector representing metadata for Image Capture. - */ - using ImagesDataType = std::vector; - const ImagesDataType INVALID_IMAGE_DATA = std::vector(); - /** * @brief Signals_t is a vector that represents the AST Expression Tree per collectionScheme provided. */ @@ -376,13 +339,6 @@ class ICollectionScheme */ virtual const RawCanFrames_t &getCollectRawCanFrames() const = 0; - /** - * @brief Returns all Image Capture settings for this collectionScheme - * - * @return if not ready an empty vector - */ - virtual const ImagesDataType &getImageCaptureData() const = 0; - /** * @brief Returns all of the Expression Nodes * diff --git a/src/datamanagement/datacollection/src/CollectionSchemeIngestion.cpp b/src/datamanagement/datacollection/src/CollectionSchemeIngestion.cpp index 84c17692..c5b3f2f6 100644 --- a/src/datamanagement/datacollection/src/CollectionSchemeIngestion.cpp +++ b/src/datamanagement/datacollection/src/CollectionSchemeIngestion.cpp @@ -137,37 +137,6 @@ CollectionSchemeIngestion::build() FWE_LOG_ERROR( "COLLECTION_SCHEME_TYPE_NOT_SET" ); } - // Build Image capture collection info - for ( int imageDataIndex = 0; imageDataIndex < mProtoCollectionSchemeMessagePtr->image_data_size(); - ++imageDataIndex ) - { - // Get a reference to the RAW CAN Frame in the protobuf - const CollectionSchemesMsg::ImageData &imageData = - mProtoCollectionSchemeMessagePtr->image_data( imageDataIndex ); - - ImageCollectionInfo imageCaptureData; - // Read the Image capture settings - imageCaptureData.deviceID = imageData.image_source_node_id(); - // We only support Time based image capture. We skip any other type. - if ( imageData.image_collection_method_case() == - CollectionSchemesMsg::ImageData::ImageCollectionMethodCase::kTimeBasedImageData ) - { - imageCaptureData.collectionType = ImageCollectionType::TIME_BASED; - // Timing constraints - imageCaptureData.beforeDurationMs = imageData.time_based_image_data().before_duration_ms(); - // Image format - imageCaptureData.imageFormat = static_cast( imageData.image_type() ); - FWE_LOG_INFO( "Adding Image capture settings for DeviceID: " + - std::to_string( imageCaptureData.deviceID ) ); - - mImagesCaptureData.emplace_back( imageCaptureData ); - } - else - { - FWE_LOG_WARN( "Unsupported Image capture settings provided, skipping" ); - } - } - FWE_LOG_INFO( "Successfully built CollectionScheme ID: " + mProtoCollectionSchemeMessagePtr->campaign_sync_id() ); // Set ready flag to true @@ -384,8 +353,8 @@ CollectionSchemeIngestion::serializeNode( const CollectionSchemesMsg::ConditionN // If no left node this node_operator is invalid if ( node.node_operator().has_left_child() ) { - FWE_LOG_TRACE( "Processing left child" ); currentNode->nodeType = convertOperatorType( node.node_operator().operator_() ); + FWE_LOG_TRACE( "Processing left child" ); // If no valid function found return always false if ( currentNode->nodeType == ExpressionNodeType::BOOLEAN ) { @@ -566,17 +535,6 @@ CollectionSchemeIngestion::getCollectRawCanFrames() const return mCollectedRawCAN; } -const ICollectionScheme::ImagesDataType & -CollectionSchemeIngestion::getImageCaptureData() const -{ - if ( !mReady ) - { - return INVALID_IMAGE_DATA; - } - - return mImagesCaptureData; -} - const struct ExpressionNode * CollectionSchemeIngestion::getCondition() const { diff --git a/src/datamanagement/datacollection/src/DataCollectionSender.cpp b/src/datamanagement/datacollection/src/DataCollectionSender.cpp deleted file mode 100644 index 4347ab7d..00000000 --- a/src/datamanagement/datacollection/src/DataCollectionSender.cpp +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Includes -#include "DataCollectionSender.h" -#include "LoggingModule.h" -#include "TraceModule.h" -#include -#include -#include - -namespace Aws -{ -namespace IoTFleetWise -{ -namespace DataManagement -{ - -DataCollectionSender::DataCollectionSender( std::shared_ptr sender, - unsigned maxMessageCount, - CANInterfaceIDTranslator &canIDTranslator ) - : mSender( std::move( sender ) ) - , mTransmitThreshold{ ( maxMessageCount > 0U ) ? maxMessageCount : UINT_MAX } - , mProtoWriter( canIDTranslator ) -{ -} - -void -DataCollectionSender::send( const TriggeredCollectionSchemeDataPtr triggeredCollectionSchemeDataPtr ) -{ - if ( triggeredCollectionSchemeDataPtr == nullptr ) - { - FWE_LOG_WARN( "Nothing to send as the input is empty" ); - return; - } - - // Assign a unique event id to the edge to cloud payload - mCollectionEventID = triggeredCollectionSchemeDataPtr->eventID; - - setCollectionSchemeParameters( *triggeredCollectionSchemeDataPtr ); - - if ( mSendDestination == SendDestination::MQTT ) - { - mProtoWriter.setupVehicleData( triggeredCollectionSchemeDataPtr, mCollectionEventID ); - } - - // Iterate through all the signals and add to the protobuf - for ( const auto &signal : triggeredCollectionSchemeDataPtr->signals ) - { - if ( mSendDestination == SendDestination::MQTT ) - { - mProtoWriter.append( signal ); - if ( mProtoWriter.getVehicleDataMsgCount() >= mTransmitThreshold ) - { - serializeAndTransmit(); - // Setup the next payload chunk - mProtoWriter.setupVehicleData( triggeredCollectionSchemeDataPtr, mCollectionEventID ); - } - } - } - - // Iterate through all the raw CAN frames and add to the protobuf - for ( const auto &canFrame : triggeredCollectionSchemeDataPtr->canFrames ) - { - if ( mSendDestination == SendDestination::MQTT ) - { - mProtoWriter.append( canFrame ); - if ( mProtoWriter.getVehicleDataMsgCount() >= mTransmitThreshold ) - { - serializeAndTransmit(); - // Setup the next payload chunk - mProtoWriter.setupVehicleData( triggeredCollectionSchemeDataPtr, mCollectionEventID ); - } - } - } - - if ( mSendDestination == SendDestination::MQTT ) - { - // Add DTC info to the payload - if ( triggeredCollectionSchemeDataPtr->mDTCInfo.hasItems() ) - { - mProtoWriter.setupDTCInfo( triggeredCollectionSchemeDataPtr->mDTCInfo ); - const auto &dtcCodes = triggeredCollectionSchemeDataPtr->mDTCInfo.mDTCCodes; - - // Iterate through all the DTC codes and add to the protobuf - for ( const auto &dtc : dtcCodes ) - { - mProtoWriter.append( dtc ); - - if ( mProtoWriter.getVehicleDataMsgCount() >= mTransmitThreshold ) - { - serializeAndTransmit(); - // Setup the next payload chunk - mProtoWriter.setupVehicleData( triggeredCollectionSchemeDataPtr, mCollectionEventID ); - // Setup DTC metadata for the next payload - mProtoWriter.setupDTCInfo( triggeredCollectionSchemeDataPtr->mDTCInfo ); - } - } - } - } - - // Add Geohash to the payload - if ( triggeredCollectionSchemeDataPtr->mGeohashInfo.hasItems() ) - { - if ( mSendDestination == SendDestination::MQTT ) - { - mProtoWriter.append( triggeredCollectionSchemeDataPtr->mGeohashInfo ); - if ( mProtoWriter.getVehicleDataMsgCount() >= mTransmitThreshold ) - { - serializeAndTransmit(); - // Setup the next payload chunk - mProtoWriter.setupVehicleData( triggeredCollectionSchemeDataPtr, mCollectionEventID ); - } - } - } - - if ( mSendDestination == SendDestination::MQTT ) - { - // Serialize and transmit any remaining messages - if ( mProtoWriter.getVehicleDataMsgCount() >= 1U ) - { - FWE_LOG_TRACE( "The data collection snapshot has been written on disk and is now scheduled for upload to " - "AWS IoT Core" ); - serializeAndTransmit(); - } - } -} - -ConnectivityError -DataCollectionSender::transmit() -{ - if ( mSendDestination != SendDestination::MQTT ) - { - FWE_LOG_TRACE( "Upload destination is not set to AWS IoT Core. Skipping this request" ); - return ConnectivityError::Success; - } - - std::string payloadData; - // compress the data before transmitting if specified in the collectionScheme - if ( mCollectionSchemeParams.compression ) - { - FWE_LOG_TRACE( "Compress the payload before transmitting since compression flag is true" ); - if ( snappy::Compress( mProtoOutput.data(), mProtoOutput.size(), &payloadData ) == 0U ) - { - FWE_LOG_TRACE( "Error in compressing the payload" ); - return ConnectivityError::WrongInputData; - } - } - else - { - // Using payloadData as a container to send(), hence assign it to original uncompressed payload - payloadData = mProtoOutput; - } - - ConnectivityError ret = mSender->sendBuffer( - reinterpret_cast( payloadData.data() ), payloadData.size(), mCollectionSchemeParams ); - if ( ret != ConnectivityError::Success ) - { - FWE_LOG_ERROR( "Failed to send vehicle data proto with error: " + std::to_string( static_cast( ret ) ) ); - } - else - { - TraceModule::get().sectionEnd( TraceSection::COLLECTION_SCHEME_CHANGE_TO_FIRST_DATA ); - TraceModule::get().incrementVariable( TraceVariable::MQTT_SIGNAL_MESSAGES_SENT_OUT ); - FWE_LOG_INFO( "A Payload of size: " + std::to_string( payloadData.length() ) + - " bytes has been uploaded to AWS IoT Core" ); - } - return ret; -} - -ConnectivityError -DataCollectionSender::transmit( const std::string &payload ) -{ - if ( mSendDestination != SendDestination::MQTT ) - { - FWE_LOG_TRACE( "Upload destination is not set to AWS IoT Core. Skipping this request" ); - return ConnectivityError::WrongInputData; - } - - auto res = mSender->sendBuffer( reinterpret_cast( payload.data() ), payload.size() ); - if ( res != ConnectivityError::Success ) - { - FWE_LOG_ERROR( "offboardconnectivity error " + std::to_string( static_cast( res ) ) ); - } - return res; -} - -void -DataCollectionSender::serializeAndTransmit() -{ - if ( mSendDestination != SendDestination::MQTT ) - { - FWE_LOG_TRACE( "Upload destination is not set to AWS IoT Core. Skipping this request" ); - return; - } - - // Note: a class member is used to store the serialized proto output to avoid heap fragmentation - if ( !mProtoWriter.serializeVehicleData( &mProtoOutput ) ) - { - FWE_LOG_ERROR( "serialization failed" ); - } - else - { - // transmit the data to the cloud - auto res = transmit(); - if ( res != ConnectivityError::Success ) - { - FWE_LOG_ERROR( "offboardconnectivity error while transmitting data" + - std::to_string( static_cast( res ) ) ); - } - } -} - -void -DataCollectionSender::setCollectionSchemeParameters( - const TriggeredCollectionSchemeData &triggeredCollectionSchemeDataPtr ) -{ - mCollectionSchemeParams.persist = triggeredCollectionSchemeDataPtr.metaData.persist; - mCollectionSchemeParams.compression = triggeredCollectionSchemeDataPtr.metaData.compress; - mCollectionSchemeParams.priority = triggeredCollectionSchemeDataPtr.metaData.priority; -} - -} // namespace DataManagement -} // namespace IoTFleetWise -} // namespace Aws diff --git a/src/datamanagement/datacollection/test/DataCollectionSenderTest.cpp b/src/datamanagement/datacollection/test/DataCollectionSenderTest.cpp deleted file mode 100644 index 9e8a685b..00000000 --- a/src/datamanagement/datacollection/test/DataCollectionSenderTest.cpp +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -#include "DataCollectionSender.h" -#include -#include -#include -#include - -using namespace Aws::IoTFleetWise::DataManagement; - -class MockSender : public ISender -{ -public: - using Callback = std::function; - Callback mCallback; - - bool - isAlive() - { - return true; - } - - size_t - getMaxSendSize() const - { - return 128U * 1024U; - } - - ConnectivityError - sendBuffer( const std::uint8_t *buf, - size_t size, - struct Aws::IoTFleetWise::OffboardConnectivity::CollectionSchemeParams collectionSchemeParams = - CollectionSchemeParams() ) override - { - static_cast( collectionSchemeParams ); // Currently not implemented, hence unused - - if ( !mCallback ) - { - return ConnectivityError::NoConnection; - } - return mCallback( buf, size ); - } -}; - -class DataCollectionSenderTest : public ::testing::Test -{ -public: - void - checkProto( const std::uint8_t *buf, size_t size ) - { - std::string expectedProto; - for ( size_t i = 0U; i < size; i++ ) - { - expectedProto += static_cast( buf[i] ); - } - VehicleDataMsg::VehicleData vehicleDataTest{}; - - ASSERT_TRUE( vehicleDataTest.ParseFromString( expectedProto ) ); - - /* Read and compare to written fields */ - ASSERT_EQ( "123", vehicleDataTest.campaign_sync_id() ); - ASSERT_EQ( "456", vehicleDataTest.decoder_sync_id() ); - ASSERT_EQ( 800, vehicleDataTest.collection_event_time_ms_epoch() ); - ASSERT_EQ( vehicleDataTest.captured_signals_size(), 3 ); - ASSERT_EQ( vehicleDataTest.can_frames_size(), 3 ); - auto dtcData = vehicleDataTest.mutable_dtc_data(); - ASSERT_EQ( dtcData->relative_time_ms(), 1200 ); - ASSERT_EQ( dtcData->active_dtc_codes_size(), 2 ); - auto geohashProto = vehicleDataTest.mutable_geohash(); - ASSERT_EQ( geohashProto->geohash_string(), "9q9hwg28j" ); - ASSERT_EQ( geohashProto->prev_reported_geohash_string(), "9q9hwg281" ); - } - - void - checkProtoForMaxMessages( const std::uint8_t *buf, size_t size, uint32_t maxMessageCount ) - { - std::string expectedProto; - for ( size_t i = 0U; i < size; i++ ) - { - expectedProto += static_cast( buf[i] ); - } - VehicleDataMsg::VehicleData vehicleDataTest{}; - - ASSERT_TRUE( vehicleDataTest.ParseFromString( expectedProto ) ); - - /* Read and compare to written fields */ - ASSERT_EQ( "123", vehicleDataTest.campaign_sync_id() ); - ASSERT_EQ( "456", vehicleDataTest.decoder_sync_id() ); - ASSERT_EQ( 800, vehicleDataTest.collection_event_time_ms_epoch() ); - // Number of messages should always be less than or equal to the transmit threshold specified in config - auto dtcData = vehicleDataTest.mutable_dtc_data(); - ASSERT_LE( vehicleDataTest.can_frames_size() + vehicleDataTest.captured_signals_size() + - dtcData->active_dtc_codes_size(), - maxMessageCount ); - } - - std::shared_ptr collectedDataPtr; - DataCollectionSenderTest() - { - collectedDataPtr = std::make_shared(); - collectedDataPtr->metaData.collectionSchemeID = "123"; - collectedDataPtr->metaData.decoderID = "456"; - collectedDataPtr->triggerTime = 800; - { - CollectedSignal collectedSignalMsg1( 120 /*signalId*/, 800 /*receiveTime*/, 77.88 /*value*/ ); - collectedDataPtr->signals.push_back( collectedSignalMsg1 ); - CollectedSignal collectedSignalMsg2( 10 /*signalId*/, 1000 /*receiveTime*/, 46.5 /*value*/ ); - collectedDataPtr->signals.push_back( collectedSignalMsg2 ); - CollectedSignal collectedSignalMsg3( 12 /*signalId*/, 1200 /*receiveTime*/, 98.9 /*value*/ ); - collectedDataPtr->signals.push_back( collectedSignalMsg3 ); - } - { - std::array data = { 1, 2, 3, 4, 5, 6, 7, 8 }; - CollectedCanRawFrame canFrames1( 12 /*frameId*/, 1 /*nodeId*/, 815 /*receiveTime*/, data, sizeof data ); - collectedDataPtr->canFrames.push_back( canFrames1 ); - CollectedCanRawFrame canFrames2( 4 /*frameId*/, 2 /*nodeId*/, 1100 /*receiveTime*/, data, sizeof data ); - collectedDataPtr->canFrames.push_back( canFrames2 ); - CollectedCanRawFrame canFrames3( 6 /*frameId*/, 3 /*nodeId*/, 1300 /*receiveTime*/, data, sizeof data ); - collectedDataPtr->canFrames.push_back( canFrames3 ); - } - { - collectedDataPtr->mDTCInfo.mSID = SID::STORED_DTC; - collectedDataPtr->mDTCInfo.receiveTime = 2000; - collectedDataPtr->mDTCInfo.mDTCCodes = { "U0123", "P0456" }; - } - { - collectedDataPtr->mGeohashInfo.mGeohashString = "9q9hwg28j"; - collectedDataPtr->mGeohashInfo.mPrevReportedGeohashString = "9q9hwg281"; - } - } - -protected: - boost::filesystem::path mTmpDir = boost::filesystem::temp_directory_path(); -}; - -TEST_F( DataCollectionSenderTest, TestMaxMessageCountNotHit ) -{ - auto mockSender = std::make_shared(); - CANInterfaceIDTranslator canIDTranslator; - DataCollectionSender dataCollectionSender( mockSender, 10, canIDTranslator ); - - mockSender->mCallback = [&]( const std::uint8_t *buf, size_t size ) -> ConnectivityError { - // deserialize the proto - checkProto( buf, size ); - return ConnectivityError::Success; - }; - - dataCollectionSender.send( collectedDataPtr ); -} - -TEST_F( DataCollectionSenderTest, TestMaxMessageCountHit ) -{ - auto mockSender = std::make_shared(); - CANInterfaceIDTranslator canIDTranslator; - DataCollectionSender dataCollectionSender( mockSender, 2, canIDTranslator ); - - uint32_t maxMessageCount = 2; - mockSender->mCallback = [&]( const std::uint8_t *buf, size_t size ) -> ConnectivityError { - checkProtoForMaxMessages( buf, size, maxMessageCount ); - return ConnectivityError::Success; - }; - - dataCollectionSender.send( collectedDataPtr ); -} - -TEST_F( DataCollectionSenderTest, TestTransmitPayload ) -{ - auto mockSender = std::make_shared(); - CANInterfaceIDTranslator canIDTranslator; - DataCollectionSender dataCollectionSender( mockSender, 10, canIDTranslator ); - - std::string testProto = "abcdefjh!24$iklmnop!24$3@qrstuvwxyz"; - - mockSender->mCallback = [&]( const std::uint8_t *buf, size_t size ) -> ConnectivityError { - static_cast( buf ); - static_cast( size ); - return ConnectivityError::Success; - }; - ASSERT_EQ( dataCollectionSender.transmit( testProto ), ConnectivityError::Success ); -} diff --git a/src/datamanagement/datadecoding/CMakeLists.txt b/src/datamanagement/datadecoding/CMakeLists.txt index c58e8fcc..dfc3dd21 100644 --- a/src/datamanagement/datadecoding/CMakeLists.txt +++ b/src/datamanagement/datadecoding/CMakeLists.txt @@ -22,7 +22,8 @@ find_package(Boost 1.71.0 REQUIRED COMPONENTS filesystem) target_link_libraries( ${libraryTargetName} Boost::filesystem - IoTFleetWise::DataCollection + IoTFleetWise::DataManagementTypes + IoTFleetWise::Proto IoTFleetWise::Platform::Linux IoTFleetWise::Vehiclenetwork ) diff --git a/src/datamanagement/datadecoding/include/IDecoderDictionary.h b/src/datamanagement/datadecoding/include/IDecoderDictionary.h index d5e5c647..83891dfe 100644 --- a/src/datamanagement/datadecoding/include/IDecoderDictionary.h +++ b/src/datamanagement/datadecoding/include/IDecoderDictionary.h @@ -83,7 +83,7 @@ struct CANDecoderDictionary : DecoderDictionary }; /** - * @brief define shared pointer type for CAN Frame decoder dictionary + * @brief define shared pointer type for decoder dictionary */ using ConstDecoderDictionaryConstPtr = const std::shared_ptr; diff --git a/src/datamanagement/datadecoding/include/IDecoderManifest.h b/src/datamanagement/datadecoding/include/IDecoderManifest.h index 0ef50117..358aef9b 100644 --- a/src/datamanagement/datadecoding/include/IDecoderManifest.h +++ b/src/datamanagement/datadecoding/include/IDecoderManifest.h @@ -6,7 +6,6 @@ #include "EventTypes.h" #include "MessageTypes.h" #include "OBDDataTypes.h" -#include "SensorTypes.h" #include "SignalTypes.h" #include #include diff --git a/src/datamanagement/datadecoding/include/OBDDataDecoder.h b/src/datamanagement/datadecoding/include/OBDDataDecoder.h index 4c677536..8d2ad43c 100644 --- a/src/datamanagement/datadecoding/include/OBDDataDecoder.h +++ b/src/datamanagement/datadecoding/include/OBDDataDecoder.h @@ -58,7 +58,7 @@ class OBDDataDecoder * @brief Decodes an ECU response to a list of Emission related PIDs. * Validates first from the first byte whether it's a positive response. * @param sid SID for which the PIDs where requested. - * @param pids List of PIDs that edge agent requested from ECU + * @param pids List of PIDs that FWE requested from ECU * @param inputData raw response from the ECU * @param info Output vector of PID physical values * @return True if we received a positive response and decoded at least one supported PID. @@ -123,7 +123,7 @@ class OBDDataDecoder * @brief Check if PIDs response length is valid. When the response consists of multiple PIDs, * this function will check whether each PID exists in response and whether each PID's response * matches with decoder dictionary - * @param pids List of PIDs that edge agent requested from ECU + * @param pids List of PIDs that FWE requested from ECU * @param ecuResponse The PID response from ECU * @return true if response length is valid * @return false if response length is invalid diff --git a/src/datamanagement/datainspection/CMakeLists.txt b/src/datamanagement/datainspection/CMakeLists.txt index b8dec7e7..d93aba55 100644 --- a/src/datamanagement/datainspection/CMakeLists.txt +++ b/src/datamanagement/datainspection/CMakeLists.txt @@ -7,7 +7,6 @@ set(libraryAliasName IoTFleetWise::DataInspection) set(SRCS src/CollectionInspectionEngine.cpp src/CollectionInspectionWorkerThread.cpp - $<$:src/dds/DataOverDDSModule.cpp> src/diag/OBDOverCANModule.cpp src/diag/OBDOverCANECU.cpp src/location/GeohashFunctionNode.cpp @@ -35,6 +34,7 @@ target_link_libraries( IoTFleetWise::Platform::Linux IoTFleetWise::Vehiclenetwork IoTFleetWise::DataDecoding + IoTFleetWise::DataCollection IoTFleetWise::Platform::Utility ${JSONCPP_LIBRARY} ) @@ -49,7 +49,6 @@ install( FILES include/CollectionInspectionEngine.h include/CollectionInspectionWorkerThread.h - $<$:${CMAKE_CURRENT_LIST_DIR}/include/DataOverDDSModule.h> include/DataReduction.h include/GeohashFunctionNode.h include/IActiveConditionProcessor.h @@ -75,14 +74,6 @@ set( test/ExternalCANDataSourceTest.cpp ) -if(FWE_FEATURE_CAMERA) - set( - testSources - ${testSources} - test/DataOverDDSModuleTest.cpp - ) -endif() - if(${BUILD_TESTING}) message(STATUS "Building tests for ${libraryTargetName}") diff --git a/src/datamanagement/datainspection/include/CollectionInspectionEngine.h b/src/datamanagement/datainspection/include/CollectionInspectionEngine.h index 00940c81..583bf214 100644 --- a/src/datamanagement/datainspection/include/CollectionInspectionEngine.h +++ b/src/datamanagement/datainspection/include/CollectionInspectionEngine.h @@ -514,14 +514,6 @@ class CollectionInspectionEngine : public IActiveConditionProcessor, public Thre */ static EventID generateEventID( InspectionTimestamp timestamp ); - /** - * @brief This function looks the the active condition and - * checks whether other sensors such as Camera needs to be requested for - * extra metadata. It does then notify if that's the case. - * @param condition current condition being inspected. - */ - void evaluateAndTriggerRichSensorCapture( const ActiveCondition &condition ); - void clear(); /** diff --git a/src/datamanagement/datainspection/include/DataOverDDSModule.h b/src/datamanagement/datainspection/include/DataOverDDSModule.h deleted file mode 100644 index 565fa06e..00000000 --- a/src/datamanagement/datainspection/include/DataOverDDSModule.h +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -// Includes -#include "ClockHandler.h" -#include "InspectionEventListener.h" -#include "Signal.h" -#include "Thread.h" -#include "Timer.h" -#include "dds/DDSDataTypes.h" -#include "dds/IDDSPublisher.h" -#include "dds/IDDSSubscriber.h" -#include "dds/SensorDataListener.h" - -namespace Aws -{ -namespace IoTFleetWise -{ -namespace DataInspection -{ -using namespace Aws::IoTFleetWise::Platform::Linux; -using namespace Aws::IoTFleetWise::VehicleNetwork; - -/** - * @brief This module handles the collection of sensor data that is - * distributed over the Data Distribution Service. - * It manages the life cycle of the data readers and data writers in - * the configured DDS domains. - * This module owns a thread that intercepts event triggers from the - * inspection engine and forwards them into the underlying protocol data readers. - * It also intercepts notifications from the protocol data writer when data - * has been received from the network and triggers the cloud offboardconnectivity - * in order to upload it to IoTFleetWise's Data Plane. - */ -class DataOverDDSModule : public InspectionEventListener, public SensorDataListener -{ -public: - DataOverDDSModule() = default; - ~DataOverDDSModule() override; - - DataOverDDSModule( const DataOverDDSModule & ) = delete; - DataOverDDSModule &operator=( const DataOverDDSModule & ) = delete; - DataOverDDSModule( DataOverDDSModule && ) = delete; - DataOverDDSModule &operator=( DataOverDDSModule && ) = delete; - - /** - * @brief Initialize the module by creating the DDS readers and writers - * accroding to the data source configuration. - * @param ddsDataSourcesConfig Set of DDS Data sources configuration. Each source will have - * one reader and one writer created. - * @return True if successful. False if no source is provided or the sources - * are wrongly configured. - */ - bool init( const DDSDataSourcesConfig &ddsDataSourcesConfig ); - - /** - * @brief Connects the DDS readers/writers to their corresponding DDS Domains. - * then start monitoring the DDS Traffic. - * @return True if successful. False otherwise. - */ - bool connect(); - - /** - * @brief disconnects the DDS readers/writers from their corresponding DDS Domains. - * @return True if successful. False otherwise. - */ - bool disconnect(); - - /** - * @brief Returns the health state of the DDS Readers/Writers. - * @return True if all readers/writers are healthy. False otherwise. - */ - bool isAlive(); - - /** - * @brief Overwrite of InspectionEventListener notification. Upon a reception of this event, - * the corresponding data writer of the sourceId is invoked to request the data. - * @param eventMetadata The event metadata. - */ - void onEventOfInterestDetected( const std::vector &eventMetadata ) override; - - /** - * @brief Overwrite of SensorDataListener notification. Upon a reception of this event, - * the corresponding metadata is processed. - * @param artifactMetadata The artifact metadata. - */ - void onSensorArtifactAvailable( const SensorArtifactMetadata &artifactMetadata ) override; - -private: - // Start the worker thread - bool start(); - // Stop the worker thread - bool stop(); - // Intercepts stop signals - bool shouldStop() const; - - /** - * @brief Work function of the module. - * The thread is typically waiting on a conditional variable and wakes up upon : - * 1- The inspection Engine raises an event of interest, and so rich sensor data is requested - * from the DDS network. - * 2- A DDS node fetches an artifact from the network ( e.g. Camera snapshot ) and shares the - * artifact metadata with the module. In this case, the artifact is processed e.g. shared with - * the cloud. - */ - static void doWork( void *data ); - - Thread mThread; - std::atomic mShouldStop{ false }; - std::atomic mNewEventReceived{ false }; - mutable std::mutex mThreadMutex; - std::shared_ptr mClock = ClockHandler::getClock(); - Platform::Linux::Signal mWait; - Timer mTimer; - // For Subscriber, we don't need to track the sourceID. - std::vector mSubscribers; - // For Publisher, we need to track the sourceID as we need to know which Publisher we - // need to invoke upon a new event. - std::map mPublishers; - mutable std::mutex mEventMetaMutex; - // To protect against race condition during find and emplace ops on the - // Pub/Sub containers. - mutable std::mutex mPubSubMutex; - // One item for each source we want to request e.g. multiple cameras. - std::vector mEventMetatdata; -}; -} // namespace DataInspection -} // namespace IoTFleetWise -} // namespace Aws diff --git a/src/datamanagement/datainspection/src/CollectionInspectionEngine.cpp b/src/datamanagement/datainspection/src/CollectionInspectionEngine.cpp index f675e5af..c7209f20 100644 --- a/src/datamanagement/datainspection/src/CollectionInspectionEngine.cpp +++ b/src/datamanagement/datainspection/src/CollectionInspectionEngine.cpp @@ -670,7 +670,7 @@ CollectionInspectionEngine::collectData( ActiveCondition &condition, InspectionTimestamp &newestSignalTimestamp ) { std::shared_ptr collectedData = std::make_shared(); - collectedData->metaData = condition.mCondition.metaData; + collectedData->metadata = condition.mCondition.metadata; collectedData->triggerTime = condition.mLastTrigger.systemTimeMs; // Pack signals for ( auto &s : condition.mCondition.signals ) @@ -842,8 +842,6 @@ CollectionInspectionEngine::collectNextDataToSend( const TimePoint ¤tTime, { // Generate the Event ID and pack it into the active Condition condition.mEventID = generateEventID( currentTime.systemTimeMs ); - // Check if we need more data from other sensors - evaluateAndTriggerRichSensorCapture( condition ); // Return the collected data InspectionTimestamp newestSignalTimeStamp = 0; auto cd = collectData( condition, mNextConditionToCollectedIndex, newestSignalTimeStamp ); @@ -870,35 +868,6 @@ CollectionInspectionEngine::collectNextDataToSend( const TimePoint ¤tTime, return std::shared_ptr( nullptr ); } -void -CollectionInspectionEngine::evaluateAndTriggerRichSensorCapture( const ActiveCondition &condition ) -{ - // Find out whether Image Data is needed. - if ( condition.mCondition.includeImageCapture ) - { - std::vector eventMetadata; - // Create an Event Item for each device we want to get image from - for ( const auto &imageCollectionInfo : condition.mCondition.imageCollectionInfos ) - { - // Make sure the same Event ID is passed to the Image capture module - // The event happened afterDuration before now, and thus, the camera - // data should have been requested already during the condition eval, - // however, because the Camera request is completely not in scope of the - // InspectionCycle, we want to make sure that no data leaves the system - // if it does not fit into the probability. We also want to make sure - // we don 't trigger camera collection if the probably meanwhile changed. - // That's why the PositiveOffset is set to zero. - eventMetadata.emplace_back( condition.mEventID, - imageCollectionInfo.deviceID, - imageCollectionInfo.beforeDurationMs + condition.mCondition.afterDuration, - 0 ); - } - // This is non blocking. Listeners simply copy the metadata. - notifyListeners &>( &InspectionEventListener::onEventOfInterestDetected, - eventMetadata ); - } -} - void CollectionInspectionEngine::addNewRawCanFrame( CANRawFrameID canID, CANChannelNumericID channelID, diff --git a/src/datamanagement/datainspection/src/dds/DataOverDDSModule.cpp b/src/datamanagement/datainspection/src/dds/DataOverDDSModule.cpp deleted file mode 100644 index 4cd131c7..00000000 --- a/src/datamanagement/datainspection/src/dds/DataOverDDSModule.cpp +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Includes -#include "DataOverDDSModule.h" -#include "LoggingModule.h" -#include "dds/CameraDataPublisher.h" -#include "dds/CameraDataSubscriber.h" -#include -#include -#include -namespace Aws -{ -namespace IoTFleetWise -{ -namespace DataInspection -{ - -DataOverDDSModule::~DataOverDDSModule() -{ - // To make sure the thread stops during teardown of tests. - if ( mThread.isValid() && mThread.isActive() ) - { - stop(); - } -} - -bool -DataOverDDSModule::init( const DDSDataSourcesConfig &ddsDataSourcesConfig ) -{ - // For each source config provided, create exactly one subscriber - // and one Publisher. - // We need to store the sourceID for each Publisher to be able to find it - // when we receive a new event. - // Currently, each subscriber and publisher have their own DDS Participant. - // We could think of consolidating these into 1 single participant, but this - // seoeration is currently intended, because we don't want this layer - // of the stack to know about specific DDS Lib supplier types, and rather want - // to stay abstract. - - DDSPublisherPtr publisher; - DDSSubscriberPtr subscriber; - for ( auto &config : ddsDataSourcesConfig ) - { - switch ( config.sourceType ) - { - case SensorSourceType::CAMERA: - publisher = std::make_unique(); - subscriber = std::make_unique(); - if ( ( !publisher->init( config ) ) || ( !subscriber->init( config ) ) ) - { - FWE_LOG_ERROR( "Failed to init the Publisher/Subscriber" ); - return false; - } - else - { - // Store these nodes - { - std::lock_guard lock( mPubSubMutex ); - mPublishers.emplace( config.sourceID, std::move( publisher ) ); - mSubscribers.emplace_back( std::move( subscriber ) ); - } - - FWE_LOG_INFO( "Camera Publisher/Subscriber successfully initialised" ); - } - break; - - default: - FWE_LOG_WARN( "Not supported SensorType" ); - break; - } - } - return ( !mPublishers.empty() ) && ( !mSubscribers.empty() ) && ( mPublishers.size() == mSubscribers.size() ); -} - -bool -DataOverDDSModule::start() -{ - // Prevent concurrent stop/init - std::lock_guard lock( mThreadMutex ); - // On multi core systems the shared variable mShouldStop must be updated for - // all cores before starting the thread otherwise thread will directly end - mShouldStop.store( false ); - if ( !mThread.create( doWork, this ) ) - { - FWE_LOG_TRACE( "DataOverDDSModule Thread failed to start" ); - } - else - { - FWE_LOG_TRACE( "DataOverDDSModule Thread started" ); - mThread.setThreadName( "fwDIDDSModule" ); - } - - return mThread.isActive() && mThread.isValid(); -} - -bool -DataOverDDSModule::stop() -{ - std::lock_guard lock( mThreadMutex ); - mShouldStop.store( true, std::memory_order_relaxed ); - mWait.notify(); - mThread.release(); - mShouldStop.store( false, std::memory_order_relaxed ); - return !mThread.isActive(); -} - -bool -DataOverDDSModule::shouldStop() const -{ - return mShouldStop.load( std::memory_order_relaxed ); -} - -void -DataOverDDSModule::doWork( void *data ) -{ - DataOverDDSModule *DDSModule = static_cast( data ); - while ( !DDSModule->shouldStop() ) - { - // Always go to a busy wait state and stay there till a notify comes. - DDSModule->mWait.wait( Platform::Linux::Signal::WaitWithPredicate ); - // We are now awake because there is an event notification from the inspection Engine. - // We need to interpret the event metadata and find out which Source we need to - // communicate with, and then invoke the corresponding publisher. - // Make sure we don't do anything during a shutdown cycle - if ( DDSModule->mNewEventReceived.load() ) - { - { - // We don't want a disconnect or connect to change the content of our container. - std::lock_guard lockPubSub( DDSModule->mPubSubMutex ); - // We need to iterate through all the items in the event and request every device - // listed in there. - // Critical section only to make sure the request content - // is atomic e.g. not changed when this thread wakes up. - std::lock_guard lockEvent( DDSModule->mEventMetaMutex ); - for ( const auto &eventItem : DDSModule->mEventMetatdata ) - { - - auto publishIterator = DDSModule->mPublishers.find( eventItem.sourceID ); - // Something went wrong... The sourceID requested is not available - if ( publishIterator == DDSModule->mPublishers.end() ) - { - // Hmm, we have received a notification to request data from a source that's - // not configured. We should log an error and skip the event. - FWE_LOG_ERROR( "Received an event for a Source that's not configured, Source ID: " + - std::to_string( eventItem.sourceID ) ); - } - else - { - // Okey, now we know there is a new event, forward it to the publisher - DDSDataRequest request = {}; - request.eventID = eventItem.eventID; - request.negativeOffsetMs = eventItem.negativeOffsetMs; - request.positiveOffsetMs = eventItem.positiveOffsetMs; - - // Go ahead and request the data from the underlying source - publishIterator->second->publishDataRequest( request ); - FWE_LOG_TRACE( - - "Send a request to the DDS Network upon eventID: " + std::to_string( eventItem.eventID ) + - " to DeviceID " + std::to_string( eventItem.sourceID ) ); - } - } - } - // Reset the event received and go back to a wait state - DDSModule->mNewEventReceived.store( false ); - } - } -} - -bool -DataOverDDSModule::connect() -{ - - { - std::lock_guard lock( mPubSubMutex ); - // First connect the Subscribers and the Publishers - for ( auto &sub : mSubscribers ) - { - // Register the module as a listener of the Subscriber - if ( ( !sub->subscribeListener( this ) ) || ( !sub->connect() ) ) - { - FWE_LOG_ERROR( "Failed to connect Subscriber" ); - return false; - } - else - { - FWE_LOG_TRACE( "Subscriber connected" ); - } - } - - for ( auto &pub : mPublishers ) - { - if ( !pub.second->connect() ) - { - FWE_LOG_ERROR( "Failed to connect Publisher" ); - return false; - } - else - { - FWE_LOG_TRACE( "Publisher connected" ); - } - } - } - - // Then start the worker thread - return start(); -} - -bool -DataOverDDSModule::disconnect() -{ - { - std::lock_guard lock( mPubSubMutex ); - // First disconnect the Subscribers and the Publishers - for ( auto &sub : mSubscribers ) - { - if ( ( !sub->unSubscribeListener( this ) ) || ( !sub->disconnect() ) ) - { - FWE_LOG_ERROR( "Failed to disconnect Subscriber" ); - return false; - } - else - { - FWE_LOG_TRACE( "Subscriber disconnected" ); - } - } - - for ( auto &pub : mPublishers ) - { - if ( !pub.second->disconnect() ) - { - FWE_LOG_ERROR( "Failed to disconnect Publisher" ); - return false; - } - else - { - FWE_LOG_TRACE( "Publisher disconnected" ); - } - } - } - // Stop the worker thread - return stop(); -} - -bool -DataOverDDSModule::isAlive() -{ - // Check that all Subscribers and Publishers are alive - std::lock_guard lock( mPubSubMutex ); - { - for ( auto &sub : mSubscribers ) - { - if ( !sub->isAlive() ) - { - FWE_LOG_ERROR( "Subscriber not alive" ); - return false; - } - } - - for ( auto &pub : mPublishers ) - { - if ( !pub.second->isAlive() ) - { - FWE_LOG_ERROR( "Publisher not alive" ); - return false; - } - } - } - - // This thread should be alive - return ( mThread.isValid() && mThread.isActive() ); -} - -void -DataOverDDSModule::onEventOfInterestDetected( const std::vector &eventMetadata ) -{ - // This runs in the context of the Inspection thread. - // We don't have an event loop at we manage a single event at a time. - // We don't need to guard for thread safety as this notification comes - // from the Inspection thread only, but we still guard as the main loop - // might be running an ongoing request. - FWE_LOG_TRACE( "Received a new event" ); - std::lock_guard lock( mEventMetaMutex ); - { - mEventMetatdata = eventMetadata; - mNewEventReceived.store( true ); - } - // Wake up the thread - mWait.notify(); -} - -void -DataOverDDSModule::onSensorArtifactAvailable( const SensorArtifactMetadata &artifactMetadata ) -{ - static_cast( artifactMetadata ); -} - -} // namespace DataInspection -} // namespace IoTFleetWise -} // namespace Aws diff --git a/src/datamanagement/datainspection/src/diag/OBDOverCANECU.cpp b/src/datamanagement/datainspection/src/diag/OBDOverCANECU.cpp index c700b7c4..89916f77 100644 --- a/src/datamanagement/datainspection/src/diag/OBDOverCANECU.cpp +++ b/src/datamanagement/datainspection/src/diag/OBDOverCANECU.cpp @@ -168,7 +168,7 @@ OBDOverCANECU::pushPIDs( const EmissionInfo &info, if ( !signalBufferPtr->push( collectedSignal ) ) { TraceModule::get().decrementAtomicVariable( TraceAtomicVariable::QUEUE_CONSUMER_TO_INSPECTION_SIGNALS ); - FWE_LOG_WARN( "Signal Buffer full with ECU " + streamRxID ); + FWE_LOG_WARN( "Signal buffer full with ECU " + streamRxID ); } } } diff --git a/src/datamanagement/datainspection/src/diag/OBDOverCANModule.cpp b/src/datamanagement/datainspection/src/diag/OBDOverCANModule.cpp index 9bb1be75..f512dda2 100644 --- a/src/datamanagement/datainspection/src/diag/OBDOverCANModule.cpp +++ b/src/datamanagement/datainspection/src/diag/OBDOverCANModule.cpp @@ -174,7 +174,7 @@ OBDOverCANModule::doWork( void *data ) // As we haven't detected ECUs, wait for 1 second and try again else { - FWE_LOG_TRACE( "Waiting for :" + std::to_string( SLEEP_TIME_SECS ) + " seconds" ); + FWE_LOG_TRACE( "Waiting for: " + std::to_string( SLEEP_TIME_SECS ) + " seconds" ); obdModule->mWait.wait( static_cast( SLEEP_TIME_SECS * 1000 ) ); } } @@ -513,7 +513,7 @@ OBDOverCANModule::assignPIDsToECUs() mPIDAssigned.clear(); for ( auto &ecu : mECUs ) { - // Get supported PIDs. Edge agent will either request it from ECU or get it from the buffer + // Get supported PIDs. FWE will either request it from ECU or get it from the buffer auto numRequests = ecu->requestReceiveSupportedPIDs( SID::CURRENT_STATS ); flush( numRequests, ecu ); // Allocate PID to each ECU to request. Note that if the PID has been already assigned, it will not be diff --git a/src/datamanagement/datainspection/src/vehicledatasource/CANDataConsumer.cpp b/src/datamanagement/datainspection/src/vehicledatasource/CANDataConsumer.cpp index 9751b0eb..b2c0603b 100644 --- a/src/datamanagement/datainspection/src/vehicledatasource/CANDataConsumer.cpp +++ b/src/datamanagement/datainspection/src/vehicledatasource/CANDataConsumer.cpp @@ -156,7 +156,7 @@ CANDataConsumer::processMessage( CANChannelNumericID channelId, { TraceModule::get().decrementAtomicVariable( TraceAtomicVariable::QUEUE_CONSUMER_TO_INSPECTION_SIGNALS ); - FWE_LOG_WARN( "Signal Buffer Full" ); + FWE_LOG_WARN( "Signal buffer full" ); } else { diff --git a/src/datamanagement/datainspection/test/DataOverDDSModuleTest.cpp b/src/datamanagement/datainspection/test/DataOverDDSModuleTest.cpp deleted file mode 100644 index 0c951261..00000000 --- a/src/datamanagement/datainspection/test/DataOverDDSModuleTest.cpp +++ /dev/null @@ -1,647 +0,0 @@ - -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -#include "DataOverDDSModule.h" -#include "CollectionInspectionEngine.h" -#include "Testing.h" -#include "WaitUntil.h" -#include "dds/CameraDataPublisher.h" -#include "dds/CameraDataSubscriber.h" -#include "dds/DDSDataTypes.h" -#include -#include -#include -#include -#include -#include - -using namespace Aws::IoTFleetWise::DataInspection; -using namespace Aws::IoTFleetWise::TestingSupport; - -class TestDDSModule : public DataOverDDSModule -{ - -public: - TestDDSModule() - : receivedArtifact( false ) - { - } - - ~TestDDSModule() - { - } - void - onSensorArtifactAvailable( const SensorArtifactMetadata &artifactMetadata ) override - { - receivedArtifact = true; - artifact = artifactMetadata; - } - -public: - bool receivedArtifact; - SensorArtifactMetadata artifact; -}; - -class TestDDSModuleInspection : public DataOverDDSModule -{ - -public: - TestDDSModuleInspection() - : receivedEvent( false ) - { - } - - ~TestDDSModuleInspection() - { - } - - void - onEventOfInterestDetected( const std::vector &eventMetadata ) override - { - receivedEvent = true; - event = eventMetadata; - } - -public: - bool receivedEvent; - std::vector event; -}; -class TestSubscriber : public DataReaderListener -{ - -public: - TestSubscriber() - : mTestParticipant( nullptr ) - , mTestSubscriber( nullptr ) - , mTestTopic( nullptr ) - , mTestReader( nullptr ) - , mTestType( new CameraDataRequestPubSubType() ) - { - } - - ~TestSubscriber() - { - if ( mTestReader != nullptr ) - { - mTestSubscriber->delete_datareader( mTestReader ); - } - if ( mTestTopic != nullptr ) - { - mTestParticipant->delete_topic( mTestTopic ); - } - if ( mTestSubscriber != nullptr ) - { - mTestParticipant->delete_subscriber( mTestSubscriber ); - } - - DomainParticipantFactory::get_instance()->delete_participant( mTestParticipant ); - } - - //! Initialize the publisher - bool - init( DDSTransportType type ) - { - - DomainParticipantQos participantQos; - participantQos.name( "TestSubscriber" ); - participantQos.transport().use_builtin_transports = false; - if ( type == DDSTransportType::UDP ) - { - auto udpTransport = std::make_shared(); - udpTransport->sendBufferSize = SEND_BUFFER_SIZE_BYTES; - udpTransport->receiveBufferSize = RECEIVE_BUFFER_SIZE_BYTES; - udpTransport->non_blocking_send = true; - participantQos.transport().user_transports.push_back( udpTransport ); - } - else if ( type == DDSTransportType::SHM ) - { - std::shared_ptr shm_transport = - std::make_shared(); - // Link the Transport Layer to the Participant. - participantQos.transport().user_transports.push_back( shm_transport ); - } - - mTestParticipant = DomainParticipantFactory::get_instance()->create_participant( 0, participantQos ); - - if ( mTestParticipant == nullptr ) - { - return false; - } - - // Register the Type - mTestType.register_type( mTestParticipant ); - - // Create the publications Topic - mTestTopic = mTestParticipant->create_topic( "testRequestTopic", mTestType.get_type_name(), TOPIC_QOS_DEFAULT ); - - if ( mTestTopic == nullptr ) - { - return false; - } - - // Create the Subscriber - mTestSubscriber = mTestParticipant->create_subscriber( SUBSCRIBER_QOS_DEFAULT ); - - if ( mTestSubscriber == nullptr ) - { - return false; - } - - // Create the DataReader - mTestReader = mTestSubscriber->create_datareader( mTestTopic, DATAREADER_QOS_DEFAULT, this ); - - if ( mTestReader == nullptr ) - { - return false; - } - return true; - } - - void - on_data_available( DataReader *reader ) override - { - SampleInfo info; - reader->take_next_sample( &dataItem, &info ); - } - -private: - DomainParticipant *mTestParticipant; - - Subscriber *mTestSubscriber; - - Topic *mTestTopic; - - DataReader *mTestReader; - - TypeSupport mTestType; - -public: - CameraDataRequest dataItem; -}; - -class TestPublisher -{ - -public: - TestPublisher() - : mTestParticipant( nullptr ) - , mTestPublisher( nullptr ) - , mTestTopic( nullptr ) - , mTestWriter( nullptr ) - , mTestType( new CameraDataItemPubSubType() ) - { - } - - ~TestPublisher() - { - if ( mTestWriter != nullptr ) - { - mTestPublisher->delete_datawriter( mTestWriter ); - } - if ( mTestPublisher != nullptr ) - { - mTestParticipant->delete_publisher( mTestPublisher ); - } - if ( mTestTopic != nullptr ) - { - mTestParticipant->delete_topic( mTestTopic ); - } - DomainParticipantFactory::get_instance()->delete_participant( mTestParticipant ); - } - - //! Initialize the publisher - bool - init( DDSTransportType type ) - { - - DomainParticipantQos participantQos; - participantQos.name( "TestPublisher" ); - participantQos.transport().use_builtin_transports = false; - if ( type == DDSTransportType::UDP ) - { - auto udpTransport = std::make_shared(); - udpTransport->sendBufferSize = SEND_BUFFER_SIZE_BYTES; - udpTransport->receiveBufferSize = RECEIVE_BUFFER_SIZE_BYTES; - udpTransport->non_blocking_send = true; - participantQos.transport().user_transports.push_back( udpTransport ); - } - else if ( type == DDSTransportType::SHM ) - { - std::shared_ptr shmTransport = - std::make_shared(); - // Link the Transport Layer to the Participant. - participantQos.transport().user_transports.push_back( shmTransport ); - } - - mTestParticipant = DomainParticipantFactory::get_instance()->create_participant( 0, participantQos ); - - if ( mTestParticipant == nullptr ) - { - return false; - } - - // Register the Type - mTestType.register_type( mTestParticipant ); - - // Create the publications Topic - mTestTopic = - mTestParticipant->create_topic( "testResponseTopic", mTestType.get_type_name(), TOPIC_QOS_DEFAULT ); - - if ( mTestTopic == nullptr ) - { - return false; - } - - // Create the Publisher - mTestPublisher = mTestParticipant->create_publisher( PUBLISHER_QOS_DEFAULT ); - - if ( mTestPublisher == nullptr ) - { - return false; - } - - // Create the DataWriter - mTestWriter = mTestPublisher->create_datawriter( mTestTopic, DATAWRITER_QOS_DEFAULT ); - - if ( mTestWriter == nullptr ) - { - return false; - } - return true; - } - - void - publishTestData( const std::string &id ) - { - mTestItem.dataItemId( id ); - mTestWriter->write( &mTestItem ); - } - -private: - CameraDataItem mTestItem; - - DomainParticipant *mTestParticipant; - - Publisher *mTestPublisher; - - Topic *mTestTopic; - - DataWriter *mTestWriter; - - TypeSupport mTestType; -}; - -/** @brief Test to verify that the module would fail to init - * if a subscriber or a publisher failed to init. - */ -TEST( DataOverDDSModuleTest, DataOverDDSModuleInitFailure ) -{ - // Create one source config with invalid Transport - DDSDataSourcesConfig configList; - DDSDataSourceConfig config = { 1, - SensorSourceType::CAMERA, - 0, - "testTopic", - "testTopic", - "TOPIC_QOS_DEFAULT", - "testReader", - "TestWriter", - "/tmp/camera/test/", - DDSTransportType::INVALID_TRANSPORT }; - configList.emplace_back( config ); - DataOverDDSModule testModule; - ASSERT_FALSE( testModule.init( configList ) ); - // Try with an unsupported device type e.g. RADAR - configList.clear(); - config = { 1, - SensorSourceType::RADAR, - 0, - "testTopic", - "testTopic", - "TOPIC_QOS_DEFAULT", - "testReader", - "TestWriter", - "/tmp/camera/test/", - DDSTransportType::SHM }; - ASSERT_FALSE( testModule.init( configList ) ); -} - -/** @brief Test to verify that the module would success to init - * if a subscriber and the publisher both succeeded to do so. - * The difference here is that we are passing a correct config - * to the module. - */ -TEST( DataOverDDSModuleTest, DataOverDDSModuleInitSuccess ) -{ - // Create one source config with valid Transport and correct - // device type. - DDSDataSourcesConfig configList; - DDSDataSourceConfig config = { 1, - SensorSourceType::CAMERA, - 0, - "testRequestTopic", - "testResponseTopic", - "TOPIC_QOS_DEFAULT", - "testReader", - "TestWriter", - "/tmp/camera/test/", - DDSTransportType::SHM }; - configList.emplace_back( config ); - DataOverDDSModule testModule; - ASSERT_TRUE( testModule.init( configList ) ); -} - -/** @brief Test to verify that the module would success to init - * if a subscriber and the publisher both succeeded to do so, and - * also connect both to the DDS Network. However, the module will not - * be alive if the peer nodes are not registered. - */ -TEST( DataOverDDSModuleTest, DataOverDDSModuleInitAndConnectSuccessButNotAlive ) -{ - // Create one source config with valid Transport and correct - // device type but the remote DDS Node is not available, so - // the module should not be alive - DDSDataSourcesConfig configList; - DDSDataSourceConfig config = { 1, - SensorSourceType::CAMERA, - 0, - "testRequestTopic", - "testResponseTopic", - "TOPIC_QOS_DEFAULT", - "testReader", - "TestWriter", - "/tmp/camera/test/", - DDSTransportType::SHM }; - configList.emplace_back( config ); - DataOverDDSModule testModule; - ASSERT_TRUE( testModule.init( configList ) ); - ASSERT_TRUE( testModule.connect() ); - ASSERT_FALSE( testModule.isAlive() ); - ASSERT_TRUE( testModule.disconnect() ); -} - -/** @brief Test to verify that the module would succeed to init - * if a subscriber and the publisher both succeeded to do so, and - * also connect both to the DDS Network. By registering some peer nodes - * on the network, the module should be alive and ready to pub/messages. - * This test case used SHM as a transport. - */ -TEST( DataOverDDSModuleTest, DataOverDDSModuleInitAndConnectSuccessAndIsAliveSHM ) -{ - DDSDataSourcesConfig configList; - DDSDataSourceConfig config = { 1, - SensorSourceType::CAMERA, - 0, - "testRequestTopic", - "testResponseTopic", - "TOPIC_QOS_DEFAULT", - "testReader", - "TestWriter", - "/tmp/camera/test/", - DDSTransportType::SHM }; - configList.emplace_back( config ); - DataOverDDSModule testModule; - // Create a mock Subscriber node on the request topic - TestSubscriber testSub; - ASSERT_TRUE( testSub.init( DDSTransportType::SHM ) ); - // Create a mock Publisher node on the response topic - TestPublisher testPub; - ASSERT_TRUE( testPub.init( DDSTransportType::SHM ) ); - - // The module should be now alive - // Init the module and connect it - ASSERT_TRUE( testModule.init( configList ) ); - ASSERT_TRUE( testModule.connect() ); - // Give some time till the threads are warmed up - - WAIT_ASSERT_TRUE( testModule.isAlive() ); - ASSERT_TRUE( testModule.disconnect() ); -} - -/** @brief Test to verify that the module would success to init - * if a subscriber and the publisher both succeeded to do so, and - * also connect both to the DDS Network. By registering some peer nodes - * on the network, the module should be alive and ready to pub/messages. - * This test case used UDP as a transport. - */ -TEST( DataOverDDSModuleTest, DataOverDDSModuleInitAndConnectSuccessAndIsAliveUDP ) -{ - DDSDataSourcesConfig configList; - DDSDataSourceConfig config = { 1, - SensorSourceType::CAMERA, - 0, - "testRequestTopic", - "testResponseTopic", - "TOPIC_QOS_DEFAULT", - "testReader", - "TestWriter", - "/tmp/camera/test/", - DDSTransportType::UDP }; - configList.emplace_back( config ); - DataOverDDSModule testModule; - // Create a mock Subscriber node on the request topic - TestSubscriber testSub; - ASSERT_TRUE( testSub.init( DDSTransportType::UDP ) ); - // Create a mock Publisher node on the response topic - TestPublisher testPub; - ASSERT_TRUE( testPub.init( DDSTransportType::UDP ) ); - // Init the module and connect it - ASSERT_TRUE( testModule.init( configList ) ); - ASSERT_TRUE( testModule.connect() ); - // Give some time till the threads are warmed up - - // The module should be now alive - WAIT_ASSERT_TRUE( testModule.isAlive() ); - ASSERT_TRUE( testModule.disconnect() ); -} - -/** @brief Test to verify that the module upon a reception of - * an event from a mocked inspection engine, would react and - * send a DDS message. - */ -TEST( DataOverDDSModuleTest, DataOverDDSModuleSendaRequest ) -{ - // Create one source config with valid Transport and correct - // device type but the remote DDS Node is not available, so - // the module should not be alive - DDSDataSourcesConfig configList; - DDSDataSourceConfig config = { 1, - SensorSourceType::CAMERA, - 0, - "testRequestTopic", - "testResponseTopic", - "TOPIC_QOS_DEFAULT", - "testReader", - "TestWriter", - "/tmp/camera/test/", - DDSTransportType::SHM }; - configList.emplace_back( config ); - DataOverDDSModule testModule; - // Create a mock Subscriber node on the request topic - TestSubscriber testSub; - ASSERT_TRUE( testSub.init( DDSTransportType::SHM ) ); - // Create a mock Publisher node on the response topic - TestPublisher testPub; - ASSERT_TRUE( testPub.init( DDSTransportType::SHM ) ); - // Init the module and connect it - ASSERT_TRUE( testModule.init( configList ) ); - ASSERT_TRUE( testModule.connect() ); - // Give some time till the threads are warmed up - - WAIT_ASSERT_TRUE( testModule.isAlive() ); - // Create a notification about an event. - // An event of ID 123 on sourceID 1, with 1 ms as positive and negative offsets - std::vector mockedEvent; - mockedEvent.emplace_back( 123, 1, 1, 1 ); - // Notify the Module about this event - testModule.onEventOfInterestDetected( mockedEvent ); - // Wait a bit till the thread wakes up and sends the request over DDS to the Subscriber - - // Check that the same event has been propagated to the peer DDS Node - WAIT_ASSERT_EQ( testSub.dataItem.dataItemId(), 123U ); - ASSERT_EQ( testSub.dataItem.negativeOffsetMs(), 1 ); - ASSERT_EQ( testSub.dataItem.positiveOffsetMs(), 1 ); - ASSERT_TRUE( testModule.disconnect() ); -} - -/** @brief Test to verify that the module upon a reception of - * an event from a mocked inspection engine, would react and - * send a DDS message. It will also receive a response from the DDS Node. - */ -TEST( DataOverDDSModuleTest, DataOverDDSModuleSendaRequestReceiveResponse ) -{ - // Create one source config with valid Transport and correct - // device type but the remote DDS Node is not available, so - // the module should not be alive - DDSDataSourcesConfig configList; - DDSDataSourceConfig config = { 1, - SensorSourceType::CAMERA, - 0, - "testRequestTopic", - "testResponseTopic", - "TOPIC_QOS_DEFAULT", - "testReader", - "TestWriter", - "/tmp/", - DDSTransportType::SHM }; - configList.emplace_back( config ); - TestDDSModule testModule; - // Create a mock Subscriber node on the request topic - TestSubscriber testSub; - ASSERT_TRUE( testSub.init( DDSTransportType::SHM ) ); - // Create a mock Publisher node on the response topic - TestPublisher testPub; - ASSERT_TRUE( testPub.init( DDSTransportType::SHM ) ); - // Init the module and connect it - ASSERT_TRUE( testModule.init( configList ) ); - ASSERT_TRUE( testModule.connect() ); - // Give some time till the threads are warmed up - - // The module should be now alive - WAIT_ASSERT_TRUE( testModule.isAlive() ); - // Create a notification about an event. - // An event of ID 123 on sourceID 1, with 1 ms as positive and negative offsets - std::vector mockedEvent; - mockedEvent.emplace_back( 123, 1, 1, 1 ); - // Notify the Module about this event - testModule.onEventOfInterestDetected( mockedEvent ); - // Wait a bit till the thread wakes up and sends the request over DDS to the Subscriber - - // Check that the same event has been propagated to the peer DDS Node - WAIT_ASSERT_EQ( testSub.dataItem.dataItemId(), 123U ); - ASSERT_EQ( testSub.dataItem.negativeOffsetMs(), 1 ); - ASSERT_EQ( testSub.dataItem.positiveOffsetMs(), 1 ); - // Send a response from the DDS Node back. - testPub.publishTestData( "123" ); - // Give it some time till the artifact is received - - WAIT_ASSERT_TRUE( testModule.receivedArtifact ); - WAIT_ASSERT_EQ( testModule.artifact.sourceID, config.sourceID ); - ASSERT_EQ( testModule.artifact.path, config.temporaryCacheLocation + "123" ); - ASSERT_TRUE( testModule.disconnect() ); -} - -/** @brief Test to validate that if a collectionScheme is set on the inspection - * engine that includes a conditition requiring Image capture, - * the DDS Module is informed when the condition is met. - */ -TEST( DataOverDDSModuleTest, DataOverDDSModuleReceiveNotificationFromInspectionEngine ) -{ - // Create one source config with valid Transport and correct - // device type but the remote DDS Node is not available, so - // the module should not be alive - DDSDataSourcesConfig configList; - DDSDataSourceConfig config = { 1, - SensorSourceType::CAMERA, - 0, - "testRequestTopic", - "testResponseTopic", - "TOPIC_QOS_DEFAULT", - "testReader", - "TestWriter", - "/tmp/camera/test/", - DDSTransportType::SHM }; - configList.emplace_back( config ); - // Init the module and connect it - TestDDSModuleInspection testModule; - ASSERT_TRUE( testModule.init( configList ) ); - ASSERT_TRUE( testModule.connect() ); - - // Prepare the Inspection Engine and the inspection matrix - InspectionMatrix matrix; - InspectionMatrixSignalCollectionInfo matrixCollectInfo; - ConditionWithCollectedData condition; - matrixCollectInfo.signalID = 1234; - matrixCollectInfo.sampleBufferSize = 50; - matrixCollectInfo.minimumSampleIntervalMs = 10; - matrixCollectInfo.fixedWindowPeriod = 77777; - matrixCollectInfo.isConditionOnlySignal = true; - condition.signals.push_back( matrixCollectInfo ); - condition.afterDuration = 3; - condition.minimumPublishIntervalMs = 0; - condition.includeImageCapture = true; - // Create the Image data capture setting with 1 device - InspectionMatrixImageCollectionInfo imageCollectionInfoItem = { - config.sourceID, 0, InspectionMatrixImageCollectionType::TIME_BASED, 3 }; - condition.imageCollectionInfos.emplace_back( imageCollectionInfoItem ); - condition.probabilityToSend = 1.0; - condition.includeActiveDtcs = false; - condition.triggerOnlyOnRisingEdge = false; - // Node - ExpressionNode node; - node.nodeType = ExpressionNodeType::BOOLEAN; - node.booleanValue = true; - condition.condition = &node; - // add the condition to the matrix - matrix.conditions.emplace_back( condition ); - // Engine - CollectionInspectionEngine engine; - // Register the DDS Module as a listener so to receive notifications - ASSERT_TRUE( engine.subscribeListener( &testModule ) ); - // Set the matrix - engine.onChangeInspectionMatrix( std::make_shared( matrix ) ); - // Start the inspection - TimePoint timestamp = { 160000000, 100 }; - uint32_t waitTimeMs = 0; - ASSERT_TRUE( engine.evaluateConditions( timestamp ) ); - // Wait for the afterDuration before checking - timestamp += condition.afterDuration; - engine.collectNextDataToSend( timestamp, waitTimeMs ); - // Since the condition has to trigger image collection - // we must have received a notification - ASSERT_TRUE( testModule.receivedEvent ); - // One event of size one should have been received - WAIT_ASSERT_EQ( testModule.event.size(), 1U ); - ASSERT_EQ( testModule.event[0].sourceID, config.sourceID ); - // we need a total of condition.afterDuration + condition.imageCollectionInfo.beforeDurationMs - // from the camera module. As we have yet to consolidate the Inspection Engine evaluate and - // collectNextData, we request eventually the whole time but as a negative offset. - // refer to evaluateAndTriggerRichSensorCapture in CollectionInspectionEngine - // for further info. - ASSERT_EQ( testModule.event[0].positiveOffsetMs, 0 ); - ASSERT_EQ( testModule.event[0].negativeOffsetMs, - condition.imageCollectionInfos[0].beforeDurationMs + condition.afterDuration ); - ASSERT_TRUE( testModule.disconnect() ); - engine.unSubscribeListener( &testModule ); -} diff --git a/src/datamanagement/datainspection/test/OBDOverCANModuleTest.cpp b/src/datamanagement/datainspection/test/OBDOverCANModuleTest.cpp index a00e451f..7fac2500 100644 --- a/src/datamanagement/datainspection/test/OBDOverCANModuleTest.cpp +++ b/src/datamanagement/datainspection/test/OBDOverCANModuleTest.cpp @@ -155,7 +155,7 @@ struct ECUMock Thread mThread; }; -// This function will be run in a separate thread to mock ECU response to Edge Agent OBD requests +// This function will be run in a separate thread to mock ECU response to FWE OBD requests void ecuResponse( void *ecuMock ) { @@ -189,12 +189,12 @@ ecuResponse( void *ecuMock ) } else if ( rxPDUData == std::vector{ 0x01, 0x00, 0x20, 0x40, 0x60, 0x80, 0xA0 } ) { - // Edge Agent is querying supported PIDs + // FWE is querying supported PIDs ecuMockPtr->mPhysicalSenderReceiver.sendPDU( ecuMockPtr->mSupportedPIDResponse1 ); } else if ( rxPDUData == std::vector{ 0x01, 0xC0, 0xE0 } ) { - // Edge Agent is querying supported PIDs + // FWE is querying supported PIDs ecuMockPtr->mPhysicalSenderReceiver.sendPDU( ecuMockPtr->mSupportedPIDResponse2 ); } else if ( rxPDUData == ecuMockPtr->mRequestPID1 ) @@ -399,7 +399,7 @@ TEST_P( OBDOverCANModuleTestWithAllSignalTypes, RequestPIDFromNotExtendedIDECUTe // In this test scenario, the decoder dictionary will request signals from PIDs 0x04, 0x14, 0x0D, 0x15, 0x16, 0x17 // Engine ECU supports PIDs 0x04,0x05,0x09,0x11,0x12,0x13,0x14 // Transmission ECU supports PIDs 0x0D, 0xC1, -// Hence Edge Agent will only request 0x04, 0x14 to Engine ECU and 0x0D, 0xC1 to Transmission ECU +// Hence FWE will only request 0x04, 0x14 to Engine ECU and 0x0D, 0xC1 to Transmission ECU TEST_F( OBDOverCANModuleTest, RequestPartialPIDFromNotExtendedIDECUTest ) { // Setup ECU Mock diff --git a/src/datamanagement/datamanager/CMakeLists.txt b/src/datamanagement/datamanager/CMakeLists.txt index 237ea729..edf12730 100644 --- a/src/datamanagement/datamanager/CMakeLists.txt +++ b/src/datamanagement/datamanager/CMakeLists.txt @@ -40,6 +40,7 @@ install(TARGETS ${libraryTargetName} DESTINATION lib) install( FILES include/ICollectionSchemeManager.h + include/IActiveCollectionSchemesListener.h include/CollectionSchemeManagementListener.h include/CollectionSchemeManager.h include/Schema.h diff --git a/src/datamanagement/datamanager/include/CollectionSchemeManager.h b/src/datamanagement/datamanager/include/CollectionSchemeManager.h index 1bfce317..84e3889c 100644 --- a/src/datamanagement/datamanager/include/CollectionSchemeManager.h +++ b/src/datamanagement/datamanager/include/CollectionSchemeManager.h @@ -7,6 +7,7 @@ #include "CANInterfaceIDTranslator.h" #include "ClockHandler.h" #include "CollectionSchemeManagementListener.h" +#include "IActiveCollectionSchemesListener.h" #include "IActiveConditionProcessor.h" #include "IActiveDecoderDictionaryListener.h" #include "ICollectionSchemeList.h" @@ -56,16 +57,19 @@ struct TimeData * 5. Extract Inspection Matrix and propagate to Inspection Engine; * 6. Delete expired collectionSchemes from Enabled list, or removed collectionScheme from existing list per Cloud request. + * 7. Notify other components about currently Enabled CollectionSchemes. */ class CollectionSchemeManager : public ICollectionSchemeManager, public CollectionSchemeManagementListener, public ThreadListeners, - public ThreadListeners + public ThreadListeners, + public ThreadListeners { public: using ThreadListeners::subscribeListener; using ThreadListeners::subscribeListener; + using ThreadListeners::subscribeListener; CollectionSchemeManager() = default; @@ -91,7 +95,7 @@ class CollectionSchemeManager : public ICollectionSchemeManager, * @return True if successful. False otherwise. */ bool init( uint32_t checkinIntervalMsec, - const std::shared_ptr &schemaPersistencyPtr, + const std::shared_ptr &schemaPersistencyPtr, CANInterfaceIDTranslator &canIDTranslator ); /** * @brief Sets up connection with CollectionScheme Ingestion and start main thread. @@ -155,6 +159,7 @@ class CollectionSchemeManager : public ICollectionSchemeManager, private: using ThreadListeners::notifyListeners; using ThreadListeners::notifyListeners; + using ThreadListeners::notifyListeners; /** * @brief Starts main thread @@ -254,7 +259,7 @@ class CollectionSchemeManager : public ICollectionSchemeManager, * different network types */ void decoderDictionaryExtractor( - std::map> &decoderDictionaryMap ); + std::map> &decoderDictionaryMap ); /** * @brief This function invoke all the listener for decoder dictionary update. The listener can be any types of @@ -264,7 +269,7 @@ class CollectionSchemeManager : public ICollectionSchemeManager, * different network types */ void decoderDictionaryUpdater( - std::map> &decoderDictionaryMap ); + std::map> &decoderDictionaryMap ); void inspectionMatrixExtractor( const std::shared_ptr &inspectionMatrix ) override; @@ -360,8 +365,8 @@ class CollectionSchemeManager : public ICollectionSchemeManager, bool mProcessCollectionScheme{ false }; // flag used by main thread to check if DM needs to be processed bool mProcessDecoderManifest{ false }; - // CacheAndPersist object passed from K-Engine - std::shared_ptr mSchemaPersistency; + // CacheAndPersist object passed from IoTFleetWiseEngine + std::shared_ptr mSchemaPersistency; CANInterfaceIDTranslator mCANIDTranslator; }; diff --git a/src/datamanagement/datamanager/include/IActiveCollectionSchemesListener.h b/src/datamanagement/datamanager/include/IActiveCollectionSchemesListener.h new file mode 100644 index 00000000..22d346eb --- /dev/null +++ b/src/datamanagement/datamanager/include/IActiveCollectionSchemesListener.h @@ -0,0 +1,38 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +#include "ICollectionSchemeList.h" + +namespace Aws +{ +namespace IoTFleetWise +{ +namespace DataManagement +{ +/** + * @brief Interface for components interested in the currently active collection schemes. Used to prepare senders based + * on campaign data before the data is collected and selected for the upload. + * + */ + +struct ActiveCollectionSchemes +{ + std::vector activeCollectionSchemes; +}; + +class IActiveCollectionSchemesListener +{ +public: + /** + * @brief process the change of active collection schemes + * + * */ + virtual void onChangeCollectionSchemeList( + const std::shared_ptr &activeCollectionSchemes ) = 0; + + virtual ~IActiveCollectionSchemesListener() = default; +}; +} // namespace DataManagement +} // namespace IoTFleetWise +} // namespace Aws diff --git a/src/datamanagement/datamanager/include/ICollectionSchemeManager.h b/src/datamanagement/datamanager/include/ICollectionSchemeManager.h index 0fc56ba6..d66c2e32 100644 --- a/src/datamanagement/datamanager/include/ICollectionSchemeManager.h +++ b/src/datamanagement/datamanager/include/ICollectionSchemeManager.h @@ -4,9 +4,9 @@ #pragma once // Includes +#include "CacheAndPersist.h" #include "CollectionInspectionAPITypes.h" #include "CollectionSchemeManagementListener.h" -#include "ICacheAndPersist.h" #include "ICollectionScheme.h" namespace Aws { diff --git a/src/datamanagement/datamanager/src/CollectionSchemeManager.cpp b/src/datamanagement/datamanager/src/CollectionSchemeManager.cpp index 3aaa8f79..00863975 100644 --- a/src/datamanagement/datamanager/src/CollectionSchemeManager.cpp +++ b/src/datamanagement/datamanager/src/CollectionSchemeManager.cpp @@ -223,21 +223,30 @@ CollectionSchemeManager::doWork( void *data ) * * the propagate the output to Vehicle Data Consumers */ - std::map> decoderDictionaryMap; + std::map> decoderDictionaryMap; collectionSchemeManager->decoderDictionaryExtractor( decoderDictionaryMap ); // Publish decoder dictionaries update to all listeners collectionSchemeManager->decoderDictionaryUpdater( decoderDictionaryMap ); // coverity[check_return : SUPPRESS] + auto canDecoderDictionaryPtr = std::dynamic_pointer_cast( + decoderDictionaryMap[VehicleDataSourceProtocol::RAW_SOCKET] ); std::string decoderCanChannels = std::to_string( ( decoderDictionaryMap.find( VehicleDataSourceProtocol::RAW_SOCKET ) != decoderDictionaryMap.end() && - decoderDictionaryMap[VehicleDataSourceProtocol::RAW_SOCKET] != nullptr ) - ? decoderDictionaryMap[VehicleDataSourceProtocol::RAW_SOCKET]->canMessageDecoderMethod.size() + std::dynamic_pointer_cast( + decoderDictionaryMap[VehicleDataSourceProtocol::RAW_SOCKET] ) != nullptr ) + ? std::dynamic_pointer_cast( + decoderDictionaryMap[VehicleDataSourceProtocol::RAW_SOCKET] ) + ->canMessageDecoderMethod.size() : 0 ); std::string obdPids = std::to_string( ( ( decoderDictionaryMap.find( VehicleDataSourceProtocol::OBD ) != decoderDictionaryMap.end() ) && - ( decoderDictionaryMap[VehicleDataSourceProtocol::OBD] != nullptr ) && - ( !decoderDictionaryMap[VehicleDataSourceProtocol::OBD]->canMessageDecoderMethod.empty() ) ) - ? decoderDictionaryMap[VehicleDataSourceProtocol::OBD] + ( std::dynamic_pointer_cast( + decoderDictionaryMap[VehicleDataSourceProtocol::OBD] ) != nullptr ) && + ( !std::dynamic_pointer_cast( + decoderDictionaryMap[VehicleDataSourceProtocol::OBD] ) + ->canMessageDecoderMethod.empty() ) ) + ? std::dynamic_pointer_cast( + decoderDictionaryMap[VehicleDataSourceProtocol::OBD] ) ->canMessageDecoderMethod.cbegin() ->second.size() : 0 ); @@ -330,7 +339,7 @@ CollectionSchemeManager::updateAvailable() */ bool CollectionSchemeManager::init( uint32_t checkinIntervalMsec, - const std::shared_ptr &schemaPersistencyPtr, + const std::shared_ptr &schemaPersistencyPtr, CANInterfaceIDTranslator &canIDTranslator ) { mCANIDTranslator = canIDTranslator; @@ -479,6 +488,10 @@ CollectionSchemeManager::rebuildMapsandTimeLine( const TimePoint &currTime ) { return false; } + // Create vector of active collection schemes to notify interested components about new schemes + std::shared_ptr activeCollectionSchemesOutput = + std::make_shared(); + collectionSchemeList = mCollectionSchemeList->getCollectionSchemes(); /* Separate collectionSchemes into Enabled and Idle bucket */ for ( auto const &collectionScheme : collectionSchemeList ) @@ -512,8 +525,14 @@ CollectionSchemeManager::rebuildMapsandTimeLine( const TimePoint &currTime ) mEnabledCollectionSchemeMap[id] = collectionScheme; mTimeLine.push( { calculateMonotonicTime( currTime, stopTime ), id } ); ret = true; + + activeCollectionSchemesOutput->activeCollectionSchemes.emplace_back( collectionScheme ); } } + + notifyListeners &>( + &IActiveCollectionSchemesListener::onChangeCollectionSchemeList, activeCollectionSchemesOutput ); + std::string enableStr; std::string idleStr; printExistingCollectionSchemes( enableStr, idleStr ); diff --git a/src/datamanagement/datamanager/src/DecoderDictionaryExtractor.cpp b/src/datamanagement/datamanager/src/DecoderDictionaryExtractor.cpp index 604b7b15..e47ead1f 100644 --- a/src/datamanagement/datamanager/src/DecoderDictionaryExtractor.cpp +++ b/src/datamanagement/datamanager/src/DecoderDictionaryExtractor.cpp @@ -17,7 +17,7 @@ namespace DataManagement void CollectionSchemeManager::decoderDictionaryExtractor( - std::map> &decoderDictionaryMap ) + std::map> &decoderDictionaryMap ) { // Initialize the dictionary map with nullptr for each protocol, so that protocols are disabled if // none of the collection schemes collect data for that protocol @@ -53,14 +53,20 @@ CollectionSchemeManager::decoderDictionaryExtractor( auto canRawFrameID = mDecoderManifest->getCANFrameAndInterfaceID( signalInfo.signalID ).first; auto interfaceId = mDecoderManifest->getCANFrameAndInterfaceID( signalInfo.signalID ).second; + auto canDecoderDictionaryPtr = + std::dynamic_pointer_cast( decoderDictionaryMap[networkType] ); auto canChannelID = mCANIDTranslator.getChannelNumericID( interfaceId ); if ( canChannelID == INVALID_CAN_SOURCE_NUMERIC_ID ) { FWE_LOG_WARN( "Invalid Interface ID provided: " + interfaceId ); } + else if ( !canDecoderDictionaryPtr ) + { + FWE_LOG_WARN( "Can not cast dictionary to CANDecoderDictionary for CAN Signal ID: " + + std::to_string( signalInfo.signalID ) ); + } else { - auto &canDecoderDictionaryPtr = decoderDictionaryMap[networkType]; // Add signalID to the set of this decoder dictionary canDecoderDictionaryPtr->signalIDsToCollect.insert( signalInfo.signalID ); // firstly check if we have canChannelID entry at dictionary top layer @@ -102,43 +108,52 @@ CollectionSchemeManager::decoderDictionaryExtractor( // There's only one VehicleDataSourceProtocol::OBD Channel, this is just a place holder to maintain the // generic dictionary structure CANChannelNumericID canChannelID = 0; - auto &obdPidCanDecoderDictionaryPtr = decoderDictionaryMap[networkType]; - obdPidCanDecoderDictionaryPtr->signalIDsToCollect.insert( signalInfo.signalID ); - obdPidCanDecoderDictionaryPtr->canMessageDecoderMethod.emplace( - canChannelID, std::unordered_map() ); - if ( obdPidCanDecoderDictionaryPtr->canMessageDecoderMethod.find( canChannelID ) == - obdPidCanDecoderDictionaryPtr->canMessageDecoderMethod.end() ) + auto obdPidCanDecoderDictionaryPtr = + std::dynamic_pointer_cast( decoderDictionaryMap[networkType] ); + if ( !obdPidCanDecoderDictionaryPtr ) { - // create an entry for canChannelID if it's not existed yet - obdPidCanDecoderDictionaryPtr->canMessageDecoderMethod[canChannelID] = - std::unordered_map(); + FWE_LOG_WARN( "Can not cast dictionary to CANDecoderDictionary for OBD Signal ID: " + + std::to_string( signalInfo.signalID ) ); } - if ( obdPidCanDecoderDictionaryPtr->canMessageDecoderMethod.at( canChannelID ) - .find( pidDecoderFormat.mPID ) == - obdPidCanDecoderDictionaryPtr->canMessageDecoderMethod.at( canChannelID ).end() ) + else { - // There's no Dictionary Entry created for this PID yet, create one - obdPidCanDecoderDictionaryPtr->canMessageDecoderMethod.at( canChannelID ) - .emplace( pidDecoderFormat.mPID, CANMessageDecoderMethod() ); - obdPidCanDecoderDictionaryPtr->canMessageDecoderMethod.at( canChannelID ) - .at( pidDecoderFormat.mPID ) - .format.mMessageID = pidDecoderFormat.mPID; + obdPidCanDecoderDictionaryPtr->signalIDsToCollect.insert( signalInfo.signalID ); + obdPidCanDecoderDictionaryPtr->canMessageDecoderMethod.emplace( + canChannelID, std::unordered_map() ); + if ( obdPidCanDecoderDictionaryPtr->canMessageDecoderMethod.find( canChannelID ) == + obdPidCanDecoderDictionaryPtr->canMessageDecoderMethod.end() ) + { + // create an entry for canChannelID if it's not existed yet + obdPidCanDecoderDictionaryPtr->canMessageDecoderMethod[canChannelID] = + std::unordered_map(); + } + if ( obdPidCanDecoderDictionaryPtr->canMessageDecoderMethod.at( canChannelID ) + .find( pidDecoderFormat.mPID ) == + obdPidCanDecoderDictionaryPtr->canMessageDecoderMethod.at( canChannelID ).end() ) + { + // There's no Dictionary Entry created for this PID yet, create one + obdPidCanDecoderDictionaryPtr->canMessageDecoderMethod.at( canChannelID ) + .emplace( pidDecoderFormat.mPID, CANMessageDecoderMethod() ); + obdPidCanDecoderDictionaryPtr->canMessageDecoderMethod.at( canChannelID ) + .at( pidDecoderFormat.mPID ) + .format.mMessageID = pidDecoderFormat.mPID; + obdPidCanDecoderDictionaryPtr->canMessageDecoderMethod.at( canChannelID ) + .at( pidDecoderFormat.mPID ) + .format.mSizeInBytes = static_cast( pidDecoderFormat.mPidResponseLength ); + } + // Below is the OBD Signal format represented in generic Signal Format + CANSignalFormat format; + format.mSignalID = signalInfo.signalID; + format.mFirstBitPosition = static_cast( pidDecoderFormat.mStartByte * BYTE_SIZE + + pidDecoderFormat.mBitRightShift ); + format.mSizeInBits = static_cast( ( pidDecoderFormat.mByteLength - 1 ) * BYTE_SIZE + + pidDecoderFormat.mBitMaskLength ); + format.mFactor = pidDecoderFormat.mScaling; + format.mOffset = pidDecoderFormat.mOffset; obdPidCanDecoderDictionaryPtr->canMessageDecoderMethod.at( canChannelID ) .at( pidDecoderFormat.mPID ) - .format.mSizeInBytes = static_cast( pidDecoderFormat.mPidResponseLength ); + .format.mSignals.emplace_back( format ); } - // Below is the OBD Signal format represented in generic Signal Format - CANSignalFormat format; - format.mSignalID = signalInfo.signalID; - format.mFirstBitPosition = - static_cast( pidDecoderFormat.mStartByte * BYTE_SIZE + pidDecoderFormat.mBitRightShift ); - format.mSizeInBits = static_cast( ( pidDecoderFormat.mByteLength - 1 ) * BYTE_SIZE + - pidDecoderFormat.mBitMaskLength ); - format.mFactor = pidDecoderFormat.mScaling; - format.mOffset = pidDecoderFormat.mOffset; - obdPidCanDecoderDictionaryPtr->canMessageDecoderMethod.at( canChannelID ) - .at( pidDecoderFormat.mPID ) - .format.mSignals.emplace_back( format ); } } // Next let's iterate through the CAN Frames that collectionScheme wants to collect. @@ -150,42 +165,50 @@ CollectionSchemeManager::decoderDictionaryExtractor( // Currently we don't have decoder dictionary for this type of network protocol, create one decoderDictionaryMap[VehicleDataSourceProtocol::RAW_SOCKET] = std::make_shared(); } - auto &canDecoderDictionaryPtr = decoderDictionaryMap[VehicleDataSourceProtocol::RAW_SOCKET]; - for ( const auto &canFrameInfo : collectionSchemePtr->getCollectRawCanFrames() ) + auto canDecoderDictionaryPtr = std::dynamic_pointer_cast( + decoderDictionaryMap[VehicleDataSourceProtocol::RAW_SOCKET] ); + if ( !canDecoderDictionaryPtr ) { - auto canChannelID = mCANIDTranslator.getChannelNumericID( canFrameInfo.interfaceID ); - if ( canChannelID == INVALID_CAN_SOURCE_NUMERIC_ID ) - { - FWE_LOG_WARN( "Invalid Interface ID provided:" + canFrameInfo.interfaceID ); - } - else + FWE_LOG_WARN( "Can not cast dictionary to CANDecoderDictionary for CAN RAW_SOCKET" ); + } + else + { + for ( const auto &canFrameInfo : collectionSchemePtr->getCollectRawCanFrames() ) { - if ( canDecoderDictionaryPtr->canMessageDecoderMethod.find( canChannelID ) == - canDecoderDictionaryPtr->canMessageDecoderMethod.end() ) - { - // create an entry for canChannelID if the dictionary doesn't have one - canDecoderDictionaryPtr->canMessageDecoderMethod[canChannelID] = - std::unordered_map(); - } - // check if we already have entry for CAN Frame. If not, it means this CAN Frame doesn't contain any - // Signals to decode, hence the collectType will be RAW only. - auto decoderMethod = - canDecoderDictionaryPtr->canMessageDecoderMethod[canChannelID].find( canFrameInfo.frameID ); - if ( decoderMethod == canDecoderDictionaryPtr->canMessageDecoderMethod[canChannelID].end() ) + auto canChannelID = mCANIDTranslator.getChannelNumericID( canFrameInfo.interfaceID ); + if ( canChannelID == INVALID_CAN_SOURCE_NUMERIC_ID ) { - // there's entry for CANChannelNumericID but no corresponding canFrameID - CANMessageDecoderMethod canMessageDecoderMethod; - canMessageDecoderMethod.collectType = CANMessageCollectType::RAW; - canDecoderDictionaryPtr->canMessageDecoderMethod[canChannelID][canFrameInfo.frameID] = - canMessageDecoderMethod; + FWE_LOG_WARN( "Invalid Interface ID provided:" + canFrameInfo.interfaceID ); } else { - if ( decoderMethod->second.collectType == CANMessageCollectType::DECODE ) + if ( canDecoderDictionaryPtr->canMessageDecoderMethod.find( canChannelID ) == + canDecoderDictionaryPtr->canMessageDecoderMethod.end() ) + { + // create an entry for canChannelID if the dictionary doesn't have one + canDecoderDictionaryPtr->canMessageDecoderMethod[canChannelID] = + std::unordered_map(); + } + // check if we already have entry for CAN Frame. If not, it means this CAN Frame doesn't contain + // any Signals to decode, hence the collectType will be RAW only. + auto decoderMethod = + canDecoderDictionaryPtr->canMessageDecoderMethod[canChannelID].find( canFrameInfo.frameID ); + if ( decoderMethod == canDecoderDictionaryPtr->canMessageDecoderMethod[canChannelID].end() ) + { + // there's entry for CANChannelNumericID but no corresponding canFrameID + CANMessageDecoderMethod canMessageDecoderMethod; + canMessageDecoderMethod.collectType = CANMessageCollectType::RAW; + canDecoderDictionaryPtr->canMessageDecoderMethod[canChannelID][canFrameInfo.frameID] = + canMessageDecoderMethod; + } + else { - // This CAN Frame contains signal to be decoded. As we need to collect both CAN Frame and - // signal, set the collectType as RAW_AND_DECODE - decoderMethod->second.collectType = CANMessageCollectType::RAW_AND_DECODE; + if ( decoderMethod->second.collectType == CANMessageCollectType::DECODE ) + { + // This CAN Frame contains signal to be decoded. As we need to collect both CAN Frame + // and signal, set the collectType as RAW_AND_DECODE + decoderMethod->second.collectType = CANMessageCollectType::RAW_AND_DECODE; + } } } } @@ -194,10 +217,9 @@ CollectionSchemeManager::decoderDictionaryExtractor( } } -// TODO: The collection scheme manager shall support generic decoder dictionary other than only can void CollectionSchemeManager::decoderDictionaryUpdater( - std::map> &decoderDictionaryMap ) + std::map> &decoderDictionaryMap ) { for ( auto const &dict : decoderDictionaryMap ) { diff --git a/src/datamanagement/datamanager/src/InspectionMatrixExtractor.cpp b/src/datamanagement/datamanager/src/InspectionMatrixExtractor.cpp index 3b1f9cbf..8f63cf27 100644 --- a/src/datamanagement/datamanager/src/InspectionMatrixExtractor.cpp +++ b/src/datamanagement/datamanager/src/InspectionMatrixExtractor.cpp @@ -60,35 +60,12 @@ CollectionSchemeManager::addConditionData( const ICollectionSchemePtr &collectio conditionData.canFrames.emplace_back( CANFrame ); } } - // Image capture data - const std::vector &imageCollectionInfos = collectionScheme->getImageCaptureData(); - for ( const auto &imageInfo : imageCollectionInfos ) - { - InspectionMatrixImageCollectionInfo imageSettings = {}; - imageSettings.deviceID = imageInfo.deviceID; - switch ( imageInfo.collectionType ) - { - case ImageCollectionType::TIME_BASED: - imageSettings.collectionType = InspectionMatrixImageCollectionType::TIME_BASED; - imageSettings.beforeDurationMs = imageInfo.beforeDurationMs; - break; - case ImageCollectionType::FRAME_BASED: - imageSettings.collectionType = InspectionMatrixImageCollectionType::FRAME_BASED; - break; - - default: - break; - } - imageSettings.imageFormat = imageInfo.imageFormat; - conditionData.imageCollectionInfos.emplace_back( imageSettings ); - } - conditionData.includeImageCapture = !conditionData.imageCollectionInfos.empty(); // The rest - conditionData.metaData.compress = collectionScheme->isCompressionNeeded(); - conditionData.metaData.persist = collectionScheme->isPersistNeeded(); - conditionData.metaData.priority = collectionScheme->getPriority(); - conditionData.metaData.decoderID = collectionScheme->getDecoderManifestID(); - conditionData.metaData.collectionSchemeID = collectionScheme->getCollectionSchemeID(); + conditionData.metadata.compress = collectionScheme->isCompressionNeeded(); + conditionData.metadata.persist = collectionScheme->isPersistNeeded(); + conditionData.metadata.priority = collectionScheme->getPriority(); + conditionData.metadata.decoderID = collectionScheme->getDecoderManifestID(); + conditionData.metadata.collectionSchemeID = collectionScheme->getCollectionSchemeID(); } void diff --git a/src/datamanagement/datamanager/test/CheckinAndPersistencyTest.cpp b/src/datamanagement/datamanager/test/CheckinAndPersistencyTest.cpp index 9d60db78..7a79fd76 100644 --- a/src/datamanagement/datamanager/test/CheckinAndPersistencyTest.cpp +++ b/src/datamanagement/datamanager/test/CheckinAndPersistencyTest.cpp @@ -73,10 +73,10 @@ TEST( CollectionSchemeManagerTest2, storeTest ) .WillOnce( ReturnRef( dataEmpty ) ) .WillOnce( ReturnRef( dataDM ) ) .WillOnce( ReturnRef( dataDM ) ); - EXPECT_CALL( *testPersistency, write( _, _, DataType::COLLECTION_SCHEME_LIST ) ) + EXPECT_CALL( *testPersistency, write( _, _, DataType::COLLECTION_SCHEME_LIST, _ ) ) .WillOnce( Return( ErrorCode::SUCCESS ) ) .WillOnce( Return( ErrorCode::MEMORY_FULL ) ); - EXPECT_CALL( *testPersistency, write( _, _, DataType::DECODER_MANIFEST ) ) + EXPECT_CALL( *testPersistency, write( _, _, DataType::DECODER_MANIFEST, _ ) ) .WillOnce( Return( ErrorCode::SUCCESS ) ) .WillOnce( Return( ErrorCode::MEMORY_FULL ) ); @@ -115,18 +115,18 @@ TEST( CollectionSchemeManagerTest2, retrieveTest ) std::shared_ptr testPL = std::make_shared(); NiceMock gmocktest( strDecoderManifestID1 ); - EXPECT_CALL( *testPersistency, getSize( DataType::COLLECTION_SCHEME_LIST ) ) + EXPECT_CALL( *testPersistency, getSize( DataType::COLLECTION_SCHEME_LIST, _ ) ) .WillOnce( Return( 0 ) ) .WillOnce( Return( 100 ) ) .WillOnce( Return( 100 ) ); - EXPECT_CALL( *testPersistency, getSize( DataType::DECODER_MANIFEST ) ) + EXPECT_CALL( *testPersistency, getSize( DataType::DECODER_MANIFEST, _ ) ) .WillOnce( Return( 0 ) ) .WillOnce( Return( 100 ) ) .WillOnce( Return( 100 ) ); - EXPECT_CALL( *testPersistency, read( _, _, DataType::COLLECTION_SCHEME_LIST ) ) + EXPECT_CALL( *testPersistency, read( _, _, DataType::COLLECTION_SCHEME_LIST, _ ) ) .WillOnce( Return( ErrorCode::FILESYSTEM_ERROR ) ) .WillOnce( Return( ErrorCode::SUCCESS ) ); - EXPECT_CALL( *testPersistency, read( _, _, DataType::DECODER_MANIFEST ) ) + EXPECT_CALL( *testPersistency, read( _, _, DataType::DECODER_MANIFEST, _ ) ) .WillOnce( Return( ErrorCode::FILESYSTEM_ERROR ) ) .WillOnce( Return( ErrorCode::SUCCESS ) ); EXPECT_CALL( *testPL, copyData( _, 100 ) ).WillOnce( Return( true ) ); @@ -162,7 +162,8 @@ TEST( CollectionSchemeManagerTest2, StoreAndRetrieve ) { int ret = std::system( "mkdir ./testPersist" ); ASSERT_FALSE( WIFEXITED( ret ) == 0 ); - std::shared_ptr testPersistency = std::make_shared( "./testPersist", 4096 ); + std::shared_ptr testPersistency = std::make_shared( "./testPersist", 4096 ); + ASSERT_TRUE( testPersistency->init() ); std::string dataPL = "if the destructor for an automatic object is explicitly invoked, and the block is subsequently left in a " "manner that would ordinarily invoke implicit destruction of the object, the behavior is undefined"; diff --git a/src/datamanagement/datamanager/test/DecoderDictionaryExtractorTest.cpp b/src/datamanagement/datamanager/test/DecoderDictionaryExtractorTest.cpp index 777d8bac..58acd6f8 100644 --- a/src/datamanagement/datamanager/test/DecoderDictionaryExtractorTest.cpp +++ b/src/datamanagement/datamanager/test/DecoderDictionaryExtractorTest.cpp @@ -265,11 +265,12 @@ TEST( CollectionSchemeManagerTest, DecoderDictionaryExtractorTest ) // Both collectionScheme1 and collectionScheme2 are expected to be enabled ASSERT_TRUE( test.updateMapsandTimeLine( currTime ) ); // Invoke Decoder Dictionary Extractor function - std::map> decoderDictionaryMap; + std::map> decoderDictionaryMap; test.decoderDictionaryExtractor( decoderDictionaryMap ); ASSERT_TRUE( decoderDictionaryMap.find( VehicleDataSourceProtocol::RAW_SOCKET ) != decoderDictionaryMap.end() ); ASSERT_TRUE( decoderDictionaryMap.find( VehicleDataSourceProtocol::OBD ) != decoderDictionaryMap.end() ); - auto &decoderDictionary = decoderDictionaryMap[VehicleDataSourceProtocol::RAW_SOCKET]; + auto decoderDictionary = + std::dynamic_pointer_cast( decoderDictionaryMap[VehicleDataSourceProtocol::RAW_SOCKET] ); // Below section exam decoder dictionary // First, check whether dictionary has two top layer index: Channel1 and Channel2 auto firstChannelId = canIDTranslator.getChannelNumericID( "10" ); @@ -330,7 +331,9 @@ TEST( CollectionSchemeManagerTest, DecoderDictionaryExtractorTest ) } ASSERT_EQ( decoderDictionaryMap[VehicleDataSourceProtocol::OBD]->signalIDsToCollect.size(), 4 ); - const auto &obdPidDecoderDictionary = decoderDictionaryMap[VehicleDataSourceProtocol::OBD]->canMessageDecoderMethod; + const auto &obdPidDecoderDictionary = + std::dynamic_pointer_cast( decoderDictionaryMap[VehicleDataSourceProtocol::OBD] ) + ->canMessageDecoderMethod; // Verify OBD PID Signals have correct decoder dictionary ASSERT_TRUE( obdPidDecoderDictionary.find( 0 ) != obdPidDecoderDictionary.end() ); ASSERT_TRUE( obdPidDecoderDictionary.at( 0 ).find( 0x14 ) != obdPidDecoderDictionary.at( 0 ).end() ); @@ -369,17 +372,19 @@ TEST( CollectionSchemeManagerTest, DecoderDictionaryExtractorTest ) // enabled ASSERT_TRUE( test.updateMapsandTimeLine( currTime + SECOND_TO_MILLISECOND( 6 ) ) ); // decoder dictionary map is a local variable in PM worker thread, create a new one - std::map> decoderDictionaryMapNew; + std::map> decoderDictionaryMapNew; test.decoderDictionaryExtractor( decoderDictionaryMapNew ); ASSERT_TRUE( decoderDictionaryMapNew.find( VehicleDataSourceProtocol::RAW_SOCKET ) != decoderDictionaryMapNew.end() ); // OBD is only included in CollectionScheme 2 and it's already expired. Hence it will be an empty decoder dictionary // for OBD ASSERT_TRUE( decoderDictionaryMapNew.find( VehicleDataSourceProtocol::OBD ) != decoderDictionaryMapNew.end() ); - decoderDictionary = decoderDictionaryMapNew[VehicleDataSourceProtocol::OBD]; + decoderDictionary = + std::dynamic_pointer_cast( decoderDictionaryMapNew[VehicleDataSourceProtocol::OBD] ); ASSERT_EQ( decoderDictionary, nullptr ); - decoderDictionary = decoderDictionaryMapNew[VehicleDataSourceProtocol::RAW_SOCKET]; + decoderDictionary = std::dynamic_pointer_cast( + decoderDictionaryMapNew[VehicleDataSourceProtocol::RAW_SOCKET] ); // Now dictionary shall not contain anything for Node 10 as CollectionScheme1 is retired ASSERT_TRUE( decoderDictionary->canMessageDecoderMethod.find( firstChannelId ) == decoderDictionary->canMessageDecoderMethod.end() ); @@ -431,7 +436,7 @@ TEST( CollectionSchemeManagerTest, DecoderDictionaryExtractorTest ) // Both collectionScheme1 and collectionScheme2 are expected to be enabled ASSERT_TRUE( test2.updateMapsandTimeLine( { 1635951061244, 100 } ) ); // Invoke Decoder Dictionary Extractor function - std::map> decoderDictionaryMap2; + std::map> decoderDictionaryMap2; test2.decoderDictionaryExtractor( decoderDictionaryMap2 ); ASSERT_TRUE( decoderDictionaryMap2.find( VehicleDataSourceProtocol::RAW_SOCKET ) != decoderDictionaryMap2.end() ); ASSERT_TRUE( decoderDictionaryMap2.find( VehicleDataSourceProtocol::OBD ) != decoderDictionaryMap2.end() ); @@ -490,7 +495,7 @@ TEST( CollectionSchemeManagerTest, DecoderDictionaryExtractorNoSignalsTest ) // Both collectionScheme1 and collectionScheme2 are expected to be enabled ASSERT_TRUE( test.updateMapsandTimeLine( currTime ) ); // Invoke Decoder Dictionary Extractor function - std::map> decoderDictionaryMap; + std::map> decoderDictionaryMap; test.decoderDictionaryExtractor( decoderDictionaryMap ); ASSERT_TRUE( decoderDictionaryMap.find( VehicleDataSourceProtocol::RAW_SOCKET ) != decoderDictionaryMap.end() ); } @@ -578,16 +583,14 @@ TEST( CollectionSchemeManagerTest, DecoderDictionaryExtractorFirstRawFrameThenSi // Both collectionScheme1 and collectionScheme2 are expected to be enabled ASSERT_TRUE( test.updateMapsandTimeLine( currTime ) ); // Invoke Decoder Dictionary Extractor function - std::map> decoderDictionaryMap; + std::map> decoderDictionaryMap; test.decoderDictionaryExtractor( decoderDictionaryMap ); ASSERT_TRUE( decoderDictionaryMap.find( VehicleDataSourceProtocol::RAW_SOCKET ) != decoderDictionaryMap.end() ); - - ASSERT_TRUE( - decoderDictionaryMap.find( VehicleDataSourceProtocol::RAW_SOCKET ) - ->second->canMessageDecoderMethod.find( canIDTranslator.getChannelNumericID( "10" ) ) != - decoderDictionaryMap.find( VehicleDataSourceProtocol::RAW_SOCKET )->second->canMessageDecoderMethod.cend() ); - auto decoderMethod = decoderDictionaryMap.find( VehicleDataSourceProtocol::RAW_SOCKET ) - ->second->canMessageDecoderMethod.find( canIDTranslator.getChannelNumericID( "10" ) ); + auto decoderDictionary = + std::dynamic_pointer_cast( decoderDictionaryMap[VehicleDataSourceProtocol::RAW_SOCKET] ); + ASSERT_TRUE( decoderDictionary->canMessageDecoderMethod.find( canIDTranslator.getChannelNumericID( "10" ) ) != + decoderDictionary->canMessageDecoderMethod.cend() ); + auto decoderMethod = decoderDictionary->canMessageDecoderMethod.find( canIDTranslator.getChannelNumericID( "10" ) ); ASSERT_TRUE( decoderMethod->second.find( 0x100 ) != decoderMethod->second.cend() ); ASSERT_EQ( decoderMethod->second.find( 0x100 )->second.collectType, CANMessageCollectType::RAW_AND_DECODE ); ASSERT_EQ( decoderMethod->second.find( 0x100 )->second.format.mSignals[0].mOffset, 17 ); diff --git a/src/datamanagement/datamanager/test/InspectionMatrixExtractorTest.cpp b/src/datamanagement/datamanager/test/InspectionMatrixExtractorTest.cpp index 1bb5c692..bbfafdf2 100644 --- a/src/datamanagement/datamanager/test/InspectionMatrixExtractorTest.cpp +++ b/src/datamanagement/datamanager/test/InspectionMatrixExtractorTest.cpp @@ -185,12 +185,10 @@ TEST( CollectionSchemeManager, InpsectionMatrixExtractorConditionDataTest ) CANFrame.interfaceID = "102"; CANFrame.sampleBufferSize = 103; CANFrame.minimumSampleIntervalMs = 104; - std::vector imageSettings; - imageSettings.emplace_back( 1, 1, ImageCollectionType::TIME_BASED, 3 ); std::vector testSignals = { Signals }; std::vector testCANFrames = { CANFrame }; - ICollectionSchemePtr collectionScheme = std::make_shared( - "COLLECTIONSCHEME1", "DM1", 0, 10, testSignals, testCANFrames, imageSettings ); + ICollectionSchemePtr collectionScheme = + std::make_shared( "COLLECTIONSCHEME1", "DM1", 0, 10, testSignals, testCANFrames ); std::vector list1; list1.emplace_back( collectionScheme ); CollectionSchemeManagerTest test( "DM1" ); @@ -220,14 +218,8 @@ TEST( CollectionSchemeManager, InpsectionMatrixExtractorConditionDataTest ) ASSERT_EQ( CANFrame.interfaceID, canIDTranslator.getInterfaceID( conditionData.canFrames[0].channelID ) ); ASSERT_EQ( CANFrame.sampleBufferSize, conditionData.canFrames[0].sampleBufferSize ); ASSERT_EQ( CANFrame.minimumSampleIntervalMs, conditionData.canFrames[0].minimumSampleIntervalMs ); - // Image Capture - ASSERT_TRUE( conditionData.includeImageCapture ); - ASSERT_EQ( conditionData.imageCollectionInfos.size(), 1 ); - ASSERT_EQ( conditionData.imageCollectionInfos[0].beforeDurationMs, imageSettings[0].beforeDurationMs ); - ASSERT_EQ( conditionData.imageCollectionInfos[0].deviceID, imageSettings[0].deviceID ); - ASSERT_EQ( conditionData.imageCollectionInfos[0].imageFormat, imageSettings[0].imageFormat ); // Decoder and CollectionScheme IDs - ASSERT_EQ( conditionData.metaData.decoderID, collectionScheme->getDecoderManifestID() ); - ASSERT_EQ( conditionData.metaData.collectionSchemeID, collectionScheme->getCollectionSchemeID() ); + ASSERT_EQ( conditionData.metadata.decoderID, collectionScheme->getDecoderManifestID() ); + ASSERT_EQ( conditionData.metadata.collectionSchemeID, collectionScheme->getCollectionSchemeID() ); } } diff --git a/src/datamanagement/datamanager/test/SchemaTest.cpp b/src/datamanagement/datamanager/test/SchemaTest.cpp index 994e04b0..6fc25fe5 100644 --- a/src/datamanagement/datamanager/test/SchemaTest.cpp +++ b/src/datamanagement/datamanager/test/SchemaTest.cpp @@ -58,17 +58,30 @@ class MockSender : public ISender } return mCallback( buf, size ); } + + ConnectivityError + sendFile( const std::string &filePath, + size_t size, + struct CollectionSchemeParams collectionSchemeParams = CollectionSchemeParams() ) override + { + static_cast( filePath ); + static_cast( size ); + static_cast( collectionSchemeParams ); + return ConnectivityError::TypeNotSupported; + } }; TEST( CollectionSchemeIngestionTest, CollectionSchemeIngestionClass ) { // Create a dummy AwsIotConnectivityModule object so that we can create dummy IReceiver objects to pass to the // constructor. Note that the MQTT callback aspect of CollectionSchemeProtoBuilder will not be used in this test. - std::shared_ptr awsIotModule = std::make_shared(); + std::shared_ptr awsIotModule = + std::make_shared( "", "", "", "", "", nullptr ); - Schema collectionSchemeIngestion( std::make_shared( awsIotModule.get(), nullptr ), - std::make_shared( awsIotModule.get(), nullptr ), - std::make_shared( awsIotModule.get(), nullptr ) ); + Schema collectionSchemeIngestion( + std::make_shared( awsIotModule.get(), nullptr, awsIotModule.get()->mConnection ), + std::make_shared( awsIotModule.get(), nullptr, awsIotModule.get()->mConnection ), + std::make_shared( awsIotModule.get(), nullptr, awsIotModule.get()->mConnection ) ); auto dummyDecoderManifest = std::make_shared(); auto dummyCollectionSchemeList = std::make_shared(); @@ -124,14 +137,15 @@ class CheckinTest : public ::testing::Test TEST( CollectionSchemeIngestionTest, Checkins ) { // Create a dummy AwsIotConnectivityModule object so that we can create dummy IReceiver objects - auto awsIotModule = std::make_shared(); + auto awsIotModule = std::make_shared( "", "", "", "", "", nullptr ); // Create a mock Sender auto mockSender = std::make_shared(); - Schema collectionSchemeIngestion( std::make_shared( awsIotModule.get(), nullptr ), - std::make_shared( awsIotModule.get(), nullptr ), - mockSender ); + Schema collectionSchemeIngestion( + std::make_shared( awsIotModule.get(), nullptr, awsIotModule.get()->mConnection ), + std::make_shared( awsIotModule.get(), nullptr, awsIotModule.get()->mConnection ), + mockSender ); std::shared_ptr clock = ClockHandler::getClock(); Timestamp timeBeforeCheckin = clock->systemTimeSinceEpochMs(); @@ -591,62 +605,148 @@ TEST( SchemaTest, SchemaCollectionEventBased ) message->set_condition_trigger_mode( CollectionSchemesMsg::ConditionBasedCollectionScheme_ConditionTriggerMode_TRIGGER_ALWAYS ); - // Build the AST Tree shown here: + // Build the AST Tree: + //---------- + auto *root = new CollectionSchemesMsg::ConditionNode(); + message->set_allocated_condition_tree( root ); auto *rootOp = new CollectionSchemesMsg::ConditionNode_NodeOperator(); + root->set_allocated_node_operator( rootOp ); rootOp->set_operator_( CollectionSchemesMsg::ConditionNode_NodeOperator_Operator_LOGICAL_AND ); - auto *right_child = new CollectionSchemesMsg::ConditionNode(); - auto *right_childOp = new CollectionSchemesMsg::ConditionNode_NodeOperator(); - right_childOp->set_operator_( CollectionSchemesMsg::ConditionNode_NodeOperator_Operator_COMPARE_SMALLER ); - - auto *left_child = new CollectionSchemesMsg::ConditionNode(); - auto *left_childOp = new CollectionSchemesMsg::ConditionNode_NodeOperator(); - left_childOp->set_operator_( CollectionSchemesMsg::ConditionNode_NodeOperator_Operator_LOGICAL_AND ); - - auto *left_child_left_child = new CollectionSchemesMsg::ConditionNode(); - auto *left_child_left_childOp = new CollectionSchemesMsg::ConditionNode_NodeOperator(); - left_child_left_childOp->set_operator_( CollectionSchemesMsg::ConditionNode_NodeOperator_Operator_COMPARE_EQUAL ); - - auto *left_child_right_child = new CollectionSchemesMsg::ConditionNode(); - auto *left_child_right_childOp = new CollectionSchemesMsg::ConditionNode_NodeOperator(); - left_child_right_childOp->set_operator_( CollectionSchemesMsg::ConditionNode_NodeOperator_Operator_COMPARE_EQUAL ); - - auto *left_child_left_child_left_child = new CollectionSchemesMsg::ConditionNode(); - left_child_left_child_left_child->set_node_signal_id( 19 ); - auto *left_child_left_child_right_child = new CollectionSchemesMsg::ConditionNode(); - - left_child_left_child_right_child->set_node_double_value( 1 ); - left_child_left_childOp->set_allocated_left_child( left_child_left_child_left_child ); - left_child_left_childOp->set_allocated_right_child( left_child_left_child_right_child ); - left_child_left_child->set_allocated_node_operator( left_child_left_childOp ); - - auto *left_child_right_child_left_child = new CollectionSchemesMsg::ConditionNode(); - left_child_right_child_left_child->set_node_signal_id( 17 ); - auto *left_child_right_child_right_child = new CollectionSchemesMsg::ConditionNode(); - left_child_right_child_right_child->set_node_double_value( 1 ); - left_child_right_childOp->set_allocated_left_child( left_child_right_child_left_child ); - left_child_right_childOp->set_allocated_right_child( left_child_right_child_right_child ); - left_child_right_child->set_allocated_node_operator( left_child_right_childOp ); - - left_childOp->set_allocated_right_child( left_child_right_child ); - left_childOp->set_allocated_left_child( left_child_left_child ); - left_child->set_allocated_node_operator( left_childOp ); - - // Build the Right Side of the AST - auto *right_child_left_child = new CollectionSchemesMsg::ConditionNode(); - right_child_left_child->set_node_signal_id( 3 ); - auto *right_child_right_child = new CollectionSchemesMsg::ConditionNode(); - right_child_right_child->set_node_double_value( 30 ); - right_childOp->set_allocated_left_child( right_child_left_child ); - right_childOp->set_allocated_right_child( right_child_right_child ); - right_child->set_allocated_node_operator( right_childOp ); - - // connect to root - rootOp->set_allocated_right_child( right_child ); - rootOp->set_allocated_left_child( left_child ); - root->set_allocated_node_operator( rootOp ); - message->set_allocated_condition_tree( root ); + //---------- + + auto *left = new CollectionSchemesMsg::ConditionNode(); + rootOp->set_allocated_left_child( left ); + auto *leftOp = new CollectionSchemesMsg::ConditionNode_NodeOperator(); + left->set_allocated_node_operator( leftOp ); + leftOp->set_operator_( CollectionSchemesMsg::ConditionNode_NodeOperator_Operator_LOGICAL_OR ); + + auto *right = new CollectionSchemesMsg::ConditionNode(); + rootOp->set_allocated_right_child( right ); + auto *rightOp = new CollectionSchemesMsg::ConditionNode_NodeOperator(); + right->set_allocated_node_operator( rightOp ); + rightOp->set_operator_( CollectionSchemesMsg::ConditionNode_NodeOperator_Operator_COMPARE_SMALLER ); + + //---------- + + auto *left_left = new CollectionSchemesMsg::ConditionNode(); + leftOp->set_allocated_left_child( left_left ); + auto *left_leftOp = new CollectionSchemesMsg::ConditionNode_NodeOperator(); + left_left->set_allocated_node_operator( left_leftOp ); + left_leftOp->set_operator_( CollectionSchemesMsg::ConditionNode_NodeOperator_Operator_COMPARE_BIGGER ); + + auto *left_right = new CollectionSchemesMsg::ConditionNode(); + leftOp->set_allocated_right_child( left_right ); + auto *left_rightOp = new CollectionSchemesMsg::ConditionNode_NodeOperator(); + left_right->set_allocated_node_operator( left_rightOp ); + left_rightOp->set_operator_( CollectionSchemesMsg::ConditionNode_NodeOperator_Operator_COMPARE_NOT_EQUAL ); + + auto *right_left = new CollectionSchemesMsg::ConditionNode(); + rightOp->set_allocated_left_child( right_left ); + auto *right_leftOp = new CollectionSchemesMsg::ConditionNode_NodeOperator(); + right_left->set_allocated_node_operator( right_leftOp ); + right_leftOp->set_operator_( CollectionSchemesMsg::ConditionNode_NodeOperator_Operator_COMPARE_SMALLER_EQUAL ); + + auto *right_right = new CollectionSchemesMsg::ConditionNode(); + rightOp->set_allocated_right_child( right_right ); + auto *right_rightOp = new CollectionSchemesMsg::ConditionNode_NodeOperator(); + right_right->set_allocated_node_operator( right_rightOp ); + right_rightOp->set_operator_( CollectionSchemesMsg::ConditionNode_NodeOperator_Operator_COMPARE_SMALLER ); + + //---------- + + auto *left_left_left = new CollectionSchemesMsg::ConditionNode(); + left_leftOp->set_allocated_left_child( left_left_left ); + left_left_left->set_node_signal_id( 19 ); + + auto *left_left_right = new CollectionSchemesMsg::ConditionNode(); + left_leftOp->set_allocated_right_child( left_left_right ); + left_left_right->set_node_double_value( 1 ); + + auto *left_right_left = new CollectionSchemesMsg::ConditionNode(); + left_rightOp->set_allocated_left_child( left_right_left ); + auto *left_right_leftOp = new CollectionSchemesMsg::ConditionNode_NodeOperator(); + left_right_left->set_allocated_node_operator( left_right_leftOp ); + left_right_leftOp->set_operator_( CollectionSchemesMsg::ConditionNode_NodeOperator_Operator_ARITHMETIC_MULTIPLY ); + + auto *left_right_right = new CollectionSchemesMsg::ConditionNode(); + left_rightOp->set_allocated_right_child( left_right_right ); + auto *left_right_rightOp = new CollectionSchemesMsg::ConditionNode_NodeOperator(); + left_right_right->set_allocated_node_operator( left_right_rightOp ); + left_right_rightOp->set_operator_( CollectionSchemesMsg::ConditionNode_NodeOperator_Operator_ARITHMETIC_DIVIDE ); + + auto *right_left_left = new CollectionSchemesMsg::ConditionNode(); + right_leftOp->set_allocated_left_child( right_left_left ); + auto *right_left_leftOp = new CollectionSchemesMsg::ConditionNode_NodeOperator(); + right_left_left->set_allocated_node_operator( right_left_leftOp ); + right_left_leftOp->set_operator_( CollectionSchemesMsg::ConditionNode_NodeOperator_Operator_LOGICAL_NOT ); + + auto *right_left_right = new CollectionSchemesMsg::ConditionNode(); + right_leftOp->set_allocated_right_child( right_left_right ); + auto *right_left_rightOp = new CollectionSchemesMsg::ConditionNode_NodeOperator(); + right_left_right->set_allocated_node_operator( right_left_rightOp ); + right_left_rightOp->set_operator_( CollectionSchemesMsg::ConditionNode_NodeOperator_Operator_ARITHMETIC_PLUS ); + + auto *right_right_left = new CollectionSchemesMsg::ConditionNode(); + right_rightOp->set_allocated_left_child( right_right_left ); + auto *right_right_leftOp = new CollectionSchemesMsg::ConditionNode_NodeOperator(); + right_right_left->set_allocated_node_operator( right_right_leftOp ); + right_right_leftOp->set_operator_( CollectionSchemesMsg::ConditionNode_NodeOperator_Operator_ARITHMETIC_MINUS ); + + auto *right_right_right = new CollectionSchemesMsg::ConditionNode(); + right_rightOp->set_allocated_right_child( right_right_right ); + auto *right_right_rightOp = new CollectionSchemesMsg::ConditionNode_NodeOperator(); + right_right_right->set_allocated_node_operator( right_right_rightOp ); + right_right_rightOp->set_operator_( CollectionSchemesMsg::ConditionNode_NodeOperator_Operator_ARITHMETIC_MINUS ); + + //---------- + + auto *left_right_left_left = new CollectionSchemesMsg::ConditionNode(); + left_right_leftOp->set_allocated_left_child( left_right_left_left ); + left_right_left_left->set_node_signal_id( 19 ); + + auto *left_right_left_right = new CollectionSchemesMsg::ConditionNode(); + left_right_leftOp->set_allocated_right_child( left_right_left_right ); + left_right_left_right->set_node_double_value( 1 ); + + auto *left_right_right_left = new CollectionSchemesMsg::ConditionNode(); + left_right_rightOp->set_allocated_left_child( left_right_right_left ); + left_right_right_left->set_node_signal_id( 19 ); + + auto *left_right_right_right = new CollectionSchemesMsg::ConditionNode(); + left_right_rightOp->set_allocated_right_child( left_right_right_right ); + left_right_right_right->set_node_double_value( 1 ); + + auto *right_left_left_left = new CollectionSchemesMsg::ConditionNode(); + right_left_leftOp->set_allocated_left_child( right_left_left_left ); + right_left_left_left->set_node_signal_id( 19 ); + + auto *right_left_right_left = new CollectionSchemesMsg::ConditionNode(); + right_left_rightOp->set_allocated_left_child( right_left_right_left ); + right_left_right_left->set_node_signal_id( 19 ); + + auto *right_left_right_right = new CollectionSchemesMsg::ConditionNode(); + right_left_rightOp->set_allocated_right_child( right_left_right_right ); + right_left_right_right->set_node_double_value( 1 ); + + auto *right_right_left_left = new CollectionSchemesMsg::ConditionNode(); + right_right_leftOp->set_allocated_left_child( right_right_left_left ); + right_right_left_left->set_node_signal_id( 19 ); + + auto *right_right_left_right = new CollectionSchemesMsg::ConditionNode(); + right_right_leftOp->set_allocated_right_child( right_right_left_right ); + right_right_left_right->set_node_double_value( 1 ); + + auto *right_right_right_left = new CollectionSchemesMsg::ConditionNode(); + right_right_rightOp->set_allocated_left_child( right_right_right_left ); + right_right_right_left->set_node_signal_id( 19 ); + + auto *right_right_right_right = new CollectionSchemesMsg::ConditionNode(); + right_right_rightOp->set_allocated_right_child( right_right_right_right ); + right_right_right_right->set_node_double_value( 1 ); + + //---------- collectionSchemeTestMessage.set_after_duration_ms( 0 ); collectionSchemeTestMessage.set_include_active_dtcs( true ); @@ -654,19 +754,6 @@ TEST( SchemaTest, SchemaCollectionEventBased ) collectionSchemeTestMessage.set_compress_collected_data( true ); collectionSchemeTestMessage.set_priority( 5 ); - // Add image data - // First device with Time based request type - CollectionSchemesMsg::ImageData *imageCaptureData = collectionSchemeTestMessage.add_image_data(); - imageCaptureData->set_image_type( CollectionSchemesMsg::ImageData_ImageType::ImageData_ImageType_COMPRESSED_JPG ); - imageCaptureData->set_image_source_node_id( 1 ); - CollectionSchemesMsg::ImageData::TimeBasedImageData *timeImageCaptureData = - imageCaptureData->mutable_time_based_image_data(); - timeImageCaptureData->set_before_duration_ms( 3 ); - // Second device with an unsupported collection type - CollectionSchemesMsg::ImageData *imageCaptureData2 = collectionSchemeTestMessage.add_image_data(); - imageCaptureData2->set_image_type( CollectionSchemesMsg::ImageData_ImageType::ImageData_ImageType_COMPRESSED_JPG ); - imageCaptureData2->set_image_source_node_id( 2 ); - // Add 3 Signals CollectionSchemesMsg::SignalInformation *signal1 = collectionSchemeTestMessage.add_signal_information(); signal1->set_signal_id( 19 ); @@ -742,13 +829,6 @@ TEST( SchemaTest, SchemaCollectionEventBased ) ASSERT_TRUE( collectionSchemeTest.getAfterDurationMs() == 0 ); ASSERT_TRUE( collectionSchemeTest.isActiveDTCsIncluded() == true ); ASSERT_TRUE( collectionSchemeTest.isTriggerOnlyOnRisingEdge() == false ); - // Check the Image Capture settings - // Only 1 image capture setting will be placed as the second one is unsupported - ASSERT_EQ( collectionSchemeTest.getImageCaptureData().size(), 1 ); - ASSERT_EQ( collectionSchemeTest.getImageCaptureData()[0].beforeDurationMs, 3 ); - ASSERT_EQ( collectionSchemeTest.getImageCaptureData()[0].collectionType, ImageCollectionType::TIME_BASED ); - ASSERT_EQ( collectionSchemeTest.getImageCaptureData()[0].deviceID, 1 ); - ASSERT_EQ( collectionSchemeTest.getImageCaptureData()[0].imageFormat, 0 ); // Enum is mapped to int // Signals ASSERT_TRUE( collectionSchemeTest.getCollectSignals().size() == 3 ); ASSERT_TRUE( collectionSchemeTest.getCollectSignals().at( 0 ).signalID == 19 ); @@ -783,36 +863,79 @@ TEST( SchemaTest, SchemaCollectionEventBased ) ASSERT_TRUE( collectionSchemeTest.getMinimumPublishIntervalMs() == 650 ); // Verify the AST - // Verify Left Side - ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().size(), 22 ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().size(), 52 ); + //---------- ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).nodeType, ExpressionNodeType::OPERATOR_LOGICAL_AND ); + //---------- ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->nodeType, - ExpressionNodeType::OPERATOR_LOGICAL_AND ); + ExpressionNodeType::OPERATOR_LOGICAL_OR ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->nodeType, + ExpressionNodeType::OPERATOR_SMALLER ); + //---------- ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->left->nodeType, - ExpressionNodeType::OPERATOR_EQUAL ); + ExpressionNodeType::OPERATOR_BIGGER ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->right->nodeType, + ExpressionNodeType::OPERATOR_NOT_EQUAL ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->left->nodeType, + ExpressionNodeType::OPERATOR_SMALLER_EQUAL ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->right->nodeType, + ExpressionNodeType::OPERATOR_SMALLER ); + //---------- ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->left->left->nodeType, ExpressionNodeType::SIGNAL ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->left->left->signalID, 19 ); ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->left->right->nodeType, ExpressionNodeType::FLOAT ); - ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->left->left->signalID, 19 ); ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->left->right->floatingValue, 1 ); - ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->right->nodeType, - ExpressionNodeType::OPERATOR_EQUAL ); ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->right->left->nodeType, - ExpressionNodeType::SIGNAL ); + ExpressionNodeType::OPERATOR_ARITHMETIC_MULTIPLY ); ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->right->right->nodeType, + ExpressionNodeType::OPERATOR_ARITHMETIC_DIVIDE ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->left->left->nodeType, + ExpressionNodeType::OPERATOR_LOGICAL_NOT ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->left->right->nodeType, + ExpressionNodeType::OPERATOR_ARITHMETIC_PLUS ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->right->left->nodeType, + ExpressionNodeType::OPERATOR_ARITHMETIC_MINUS ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->right->right->nodeType, + ExpressionNodeType::OPERATOR_ARITHMETIC_MINUS ); + //---------- + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->right->left->left->nodeType, + ExpressionNodeType::SIGNAL ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->right->left->left->signalID, 19 ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->right->left->right->nodeType, ExpressionNodeType::FLOAT ); - ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->right->left->signalID, 17 ); - ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->right->right->floatingValue, 1 ); - // Verify Right Side - ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->nodeType, - ExpressionNodeType::OPERATOR_SMALLER ); - ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->right->nodeType, ExpressionNodeType::FLOAT ); - ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->right->floatingValue, 30 ); - ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->left->nodeType, ExpressionNodeType::SIGNAL ); - ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->left->signalID, 3 ); - + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->right->left->right->floatingValue, 1 ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->right->right->left->nodeType, + ExpressionNodeType::SIGNAL ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->right->right->left->signalID, 19 ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->right->right->right->nodeType, + ExpressionNodeType::FLOAT ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).left->right->right->right->floatingValue, 1 ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->left->left->left->nodeType, + ExpressionNodeType::SIGNAL ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->left->left->left->signalID, 19 ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->left->left->right, nullptr ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->left->right->left->nodeType, + ExpressionNodeType::SIGNAL ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->left->right->left->signalID, 19 ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->left->right->right->nodeType, + ExpressionNodeType::FLOAT ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->left->right->right->floatingValue, 1 ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->right->left->left->nodeType, + ExpressionNodeType::SIGNAL ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->right->left->left->signalID, 19 ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->right->left->right->nodeType, + ExpressionNodeType::FLOAT ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->right->left->right->floatingValue, 1 ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->right->right->left->nodeType, + ExpressionNodeType::SIGNAL ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->right->right->left->signalID, 19 ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->right->right->right->nodeType, + ExpressionNodeType::FLOAT ); + ASSERT_EQ( collectionSchemeTest.getAllExpressionNodes().at( 0 ).right->right->right->right->floatingValue, 1 ); + //---------- ASSERT_TRUE( collectionSchemeTest.getCondition()->booleanValue == false ); } diff --git a/src/datamanagement/datamanager/test/include/CollectionSchemeManagerMock.h b/src/datamanagement/datamanager/test/include/CollectionSchemeManagerMock.h index d29ad95c..a532b71f 100644 --- a/src/datamanagement/datamanager/test/include/CollectionSchemeManagerMock.h +++ b/src/datamanagement/datamanager/test/include/CollectionSchemeManagerMock.h @@ -51,7 +51,7 @@ class mockCollectionSchemeManagerTest : public CollectionSchemeManager MOCK_METHOD( void, inspectionMatrixUpdater, (const sharedPtrConstInspectionMatrix &)); void - setCollectionSchemePersistency( const std::shared_ptr &collectionSchemePersistency ) + setCollectionSchemePersistency( const std::shared_ptr &collectionSchemePersistency ) { CollectionSchemeManager::mSchemaPersistency = collectionSchemePersistency; } @@ -176,16 +176,12 @@ class mockCollectionSchemeList : public CollectionSchemeIngestionList class mockCacheAndPersist : public CacheAndPersist { public: - mockCacheAndPersist() - : CacheAndPersist( "", 0 ) - { - } - // ErrorCode write( const uint8_t *bufPtr, size_t size, DataType dataType ); - MOCK_METHOD( ErrorCode, write, ( const uint8_t *, size_t, DataType ) ); + // ErrorCode write( const uint8_t *bufPtr, size_t size, DataType dataType, const std::string &filename ); + MOCK_METHOD( ErrorCode, write, (const uint8_t *, size_t, DataType, const std::string &)); - // size_t getSize( DataType dataType ); - MOCK_METHOD( size_t, getSize, ( DataType ) ); + // size_t getSize( DataType dataType, const std::string &filename ); + MOCK_METHOD( size_t, getSize, (DataType, const std::string &)); - // ErrorCode read( uint8_t *const readBufPtr, size_t size, DataType dataType ); - MOCK_METHOD( ErrorCode, read, ( uint8_t *const, size_t, DataType ) ); + // ErrorCode read( uint8_t *const readBufPtr, size_t size, DataType dataType, const std::string &filename ); + MOCK_METHOD( ErrorCode, read, (uint8_t *const, size_t, DataType, const std::string &)); }; diff --git a/src/datamanagement/datamanager/test/include/CollectionSchemeManagerTest.h b/src/datamanagement/datamanager/test/include/CollectionSchemeManagerTest.h index 267fdd70..4fa1f929 100644 --- a/src/datamanagement/datamanager/test/include/CollectionSchemeManagerTest.h +++ b/src/datamanagement/datamanager/test/include/CollectionSchemeManagerTest.h @@ -126,23 +126,6 @@ class ICollectionSchemeTest : public CollectionSchemeIngestion , root( nullptr ) { } - ICollectionSchemeTest( std::string collectionSchemeID, - std::string DMID, - uint64_t start, - uint64_t stop, - Signals_t signalsIn, - RawCanFrames_t rawCanFrmsIn, - ImagesDataType imagesDataIn ) - : collectionSchemeID( collectionSchemeID ) - , decoderManifestID( DMID ) - , startTime( start ) - , expiryTime( stop ) - , signals( signalsIn ) - , rawCanFrms( rawCanFrmsIn ) - , imagesData( imagesDataIn ) - , root( nullptr ) - { - } ICollectionSchemeTest( std::string collectionSchemeID, std::string DMID, uint64_t start, uint64_t stop ) : collectionSchemeID( collectionSchemeID ) , decoderManifestID( DMID ) @@ -190,11 +173,6 @@ class ICollectionSchemeTest : public CollectionSchemeIngestion { return rawCanFrms; } - const ImagesDataType & - getImageCaptureData() const - { - return imagesData; - } const struct ExpressionNode * getCondition() const { @@ -213,7 +191,6 @@ class ICollectionSchemeTest : public CollectionSchemeIngestion uint64_t expiryTime; Signals_t signals; RawCanFrames_t rawCanFrms; - ImagesDataType imagesData; ExpressionNode *root; }; @@ -369,13 +346,13 @@ class CollectionSchemeManagerTest : public CollectionSchemeManager } void decoderDictionaryExtractor( - std::map> &decoderDictionaryMap ) + std::map> &decoderDictionaryMap ) { return CollectionSchemeManager::decoderDictionaryExtractor( decoderDictionaryMap ); } void decoderDictionaryUpdater( - std::map> &decoderDictionaryMap ) + std::map> &decoderDictionaryMap ) { return CollectionSchemeManager::decoderDictionaryUpdater( decoderDictionaryMap ); } @@ -395,7 +372,7 @@ class CollectionSchemeManagerTest : public CollectionSchemeManager CollectionSchemeManager::mCollectionSchemeList = pl; } void - setCollectionSchemePersistency( std::shared_ptr pp ) + setCollectionSchemePersistency( std::shared_ptr pp ) { CollectionSchemeManager::mSchemaPersistency = pp; } diff --git a/src/datamanagement/datasender/CMakeLists.txt b/src/datamanagement/datasender/CMakeLists.txt new file mode 100644 index 00000000..07b0cce6 --- /dev/null +++ b/src/datamanagement/datasender/CMakeLists.txt @@ -0,0 +1,96 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +set(libraryTargetName iotfleetwise.datasender) + +# The alias name is what other targets will use as a dependency +set(libraryAliasName IoTFleetWise::DataSender) + +set(SRCS + src/DataSenderManagerWorkerThread.cpp + src/DataSenderManager.cpp + src/DataSenderProtoWriter.cpp +) + +add_library( + ${libraryTargetName} + # STATIC or SHARED left out to depend on BUILD_SHARED_LIBS + ${SRCS} +) + +target_include_directories(${libraryTargetName} PUBLIC include) + +target_link_libraries( + ${libraryTargetName} + IoTFleetWise::DataManagementTypes + IoTFleetWise::Platform::Linux + IoTFleetWise::DataInspection + IoTFleetWise::DataManager + IoTFleetWise::OffboardConnectivityAwsIot +) + +add_library(${libraryAliasName} ALIAS ${libraryTargetName}) + +### Install ### + +install(TARGETS ${libraryTargetName} DESTINATION lib) + +install( + FILES + include/DataSenderManager.h + include/DataSenderManagerWorkerThread.h + include/DataSenderProtoWriter.h + DESTINATION include +) + +if(${BUILD_TESTING}) + message(STATUS "Building tests for ${libraryTargetName}") + + find_library(GMOCK_LIB + NAMES + gmock) + + find_library(GMOCK_MAIN_LIBRARY + NAMES + gmock_main) + + file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/test/dm-collection-scheme-example.json + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + + + set( + testSources + test/DataSenderProtoWriterTest.cpp + ) + + find_package(Boost 1.65.1 REQUIRED COMPONENTS filesystem) + + # Add the executable targets + foreach(testSource ${testSources}) + # Need a name for each exec so use filename w/o extension + get_filename_component(testName ${testSource} NAME_WE) + + add_executable(${testName} ${testSource}) + + # Link to the project library and testing library main + target_link_libraries( + ${testName} + PRIVATE + ${libraryTargetName} + Boost::filesystem + IoTFleetWise::TestingSupport + IoTFleetWise::OffboardConnectivityAwsIot + ${GMOCK_LIB} + ) + + target_include_directories( + ${testName} + PRIVATE + include + test/include) + + add_unit_test(${testName}) + add_valgrind_test(${testName}) + install(TARGETS ${testName} RUNTIME DESTINATION bin/tests) + + endforeach() + +endif() diff --git a/src/datamanagement/datasender/include/DataSenderManager.h b/src/datamanagement/datasender/include/DataSenderManager.h new file mode 100644 index 00000000..2f574f2d --- /dev/null +++ b/src/datamanagement/datasender/include/DataSenderManager.h @@ -0,0 +1,116 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "DataSenderProtoWriter.h" +#include "ICollectionScheme.h" +#include "ICollectionSchemeList.h" +#include "ISender.h" +#include "PayloadManager.h" +namespace Aws +{ +namespace IoTFleetWise +{ +namespace DataSender +{ + +using namespace Aws::IoTFleetWise::OffboardConnectivityAwsIot; +using namespace Aws::IoTFleetWise::OffboardConnectivity; +using namespace Aws::IoTFleetWise::DataManagement; + +/** + * @brief Class that implements data sender logic: data preprocessing and upload + * + * This class is not multithreading safe to the caller needs to ensure that the different functions + * are called only from one thread. This class will be instantiated and used from the Data Sender + * Manager Worker thread + */ +class DataSenderManager +{ + +public: + DataSenderManager( std::shared_ptr mqttSender, + std::shared_ptr payloadManager, + CANInterfaceIDTranslator &canIDTranslator, + unsigned transmitThreshold ); + + /** + * @brief Process collection scheme parameters and prepare telemetry data and rich sensor data for the upload + */ + void processCollectedData( const TriggeredCollectionSchemeDataPtr triggeredCollectionSchemeDataPtr ); + + /** + * @brief Retrieve all the persisted data and hand it over to the correct sender + */ + void checkAndSendRetrievedData(); + +private: + std::shared_ptr mMQTTSender; + std::shared_ptr mPayloadManager; + DataSenderProtoWriter mProtoWriter; + CollectionSchemeParams mCollectionSchemeParams; + std::string mCollectionSchemeID; + + std::string mProtoOutput; + std::string mCompressedProtoOutput; + + unsigned mTransmitThreshold{ 0 }; // max number of messages that can be sent to cloud at one time + + /** + * @brief Set up collectionSchemeParams struct + * @param triggeredCollectionSchemeDataPtr collected data + */ + void setCollectionSchemeParameters( const TriggeredCollectionSchemeDataPtr &triggeredCollectionSchemeDataPtr ); + + /** + * @brief Put collected telemetry data into protobuf in chunks. Initiates serialization, compression, and + * upload for each partition. + * @param triggeredCollectionSchemeDataPtr collected data + */ + void transformTelemetryDataToProto( const TriggeredCollectionSchemeDataPtr &triggeredCollectionSchemeDataPtr ); + + /** + * @brief Serializes, compresses, and uploads proto output. + */ + void uploadProto(); + + /** + * @brief Serializes data + * @param output Output string + * @return True if serialization succeeds + */ + bool serialize( std::string &output ); + + /** + * @brief Compresses data + * @param input Input data string + * @return True if compression succeeds + */ + bool compress( std::string &input ); + + /** + * @brief Forwards data from buffer to the provided sender + * @param data Data to send + * @param size Buffer size + * @param sender sender to use for the upload + * @return Success if upload succeeds + */ + ConnectivityError send( const std::uint8_t *data, size_t size, std::shared_ptr sender ); + + /** + * @brief Upload file from persistency folder + * @param filename File to send + * @param size File size + * @param collectionSchemeParams object containing collectionScheme related metadata for data persistency and + * transmission + * @return Success if upload succeeds + */ + ConnectivityError uploadPersistedFile( const std::string &filename, + size_t size, + CollectionSchemeParams collectionSchemeParams ); +}; + +} // namespace DataSender +} // namespace IoTFleetWise +} // namespace Aws diff --git a/src/datamanagement/datasender/include/DataSenderManagerWorkerThread.h b/src/datamanagement/datasender/include/DataSenderManagerWorkerThread.h new file mode 100644 index 00000000..96cb1b3d --- /dev/null +++ b/src/datamanagement/datasender/include/DataSenderManagerWorkerThread.h @@ -0,0 +1,100 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "DataSenderManager.h" +#include "IActiveCollectionSchemesListener.h" +#include "IConnectivityModule.h" +#include "IDataReadyToPublishListener.h" +#include "Thread.h" +#include "Timer.h" + +namespace Aws +{ +namespace IoTFleetWise +{ +namespace DataSender +{ + +class DataSenderManagerWorkerThread : public IDataReadyToPublishListener, public IActiveCollectionSchemesListener +{ +public: + DataSenderManagerWorkerThread( CANInterfaceIDTranslator &canIDTranslator, + std::shared_ptr connectivityModule, + std::shared_ptr mqttSender, + std::shared_ptr payloadManager, + unsigned maxMessageCount, + uint64_t persistencyUploadRetryInterval ); + ~DataSenderManagerWorkerThread() override; + + DataSenderManagerWorkerThread( const DataSenderManagerWorkerThread & ) = delete; + DataSenderManagerWorkerThread &operator=( const DataSenderManagerWorkerThread & ) = delete; + DataSenderManagerWorkerThread( DataSenderManagerWorkerThread && ) = delete; + DataSenderManagerWorkerThread &operator=( DataSenderManagerWorkerThread && ) = delete; + + static const uint32_t MAX_NUMBER_OF_SIGNAL_TO_TRACE_LOG; + + /** + * @brief Callback from the Inspection Engine to wake up this thread and + * publish the data to the cloud. + */ + void onDataReadyToPublish() override; + + // Inherited from IActiveConditionProcessor + void onChangeCollectionSchemeList( + const std::shared_ptr &activeCollectionSchemes ) override; + + /** + * @brief Initialize the component by handing over all queues + * @param collectedDataQueue this thread will empty this queue of data ready to publish + * @return true if initialization was successful + * */ + bool init( const std::shared_ptr &collectedDataQueue ); + + /** + * @brief Stops the internal thread if started and wait until it finishes + * + * @return true if the stop was successful + */ + bool stop(); + + /** + * @brief Starts the internal thread + * + * @return true if the start was successful + */ + bool start(); + + /** + * @brief Checks that the worker thread is healthy and consuming data. + */ + bool isAlive(); + +private: + // Stop the thread + bool shouldStop() const; + + static void doWork( void *data ); + + std::shared_ptr mCollectedDataQueue; + + Thread mThread; + std::atomic mShouldStop{ false }; + std::mutex mThreadMutex; + Platform::Linux::Signal mWait; + DataSenderManager mDataSenderManager; + std::shared_ptr mActiveCollectionSchemes; + std::mutex mActiveCollectionSchemesMutex; + std::atomic mUpdatedCollectionSchemeListAvailable{ false }; + + std::shared_ptr mConnectivityModule; + + Timer mTimer; + Timer mRetrySendingPersistedDataTimer; + uint64_t mPersistencyUploadRetryIntervalMs{ 0 }; +}; + +} // namespace DataSender +} // namespace IoTFleetWise +} // namespace Aws diff --git a/src/datamanagement/datacollection/include/DataCollectionProtoWriter.h b/src/datamanagement/datasender/include/DataSenderProtoWriter.h similarity index 83% rename from src/datamanagement/datacollection/include/DataCollectionProtoWriter.h rename to src/datamanagement/datasender/include/DataSenderProtoWriter.h index ad2d644f..0c13c4c6 100644 --- a/src/datamanagement/datacollection/include/DataCollectionProtoWriter.h +++ b/src/datamanagement/datasender/include/DataSenderProtoWriter.h @@ -16,7 +16,7 @@ namespace Aws { namespace IoTFleetWise { -namespace DataManagement +namespace DataSender { using namespace Aws::IoTFleetWise::Schemas; using namespace Aws::IoTFleetWise::DataInspection; @@ -25,23 +25,23 @@ using namespace Aws::IoTFleetWise::DataInspection; * @brief Class that does the protobuf setup for the collected data * and serializes the edge to cloud data */ -class DataCollectionProtoWriter +class DataSenderProtoWriter { public: /** - * @brief Constructor. Setup the DataCollectionProtoWriter. + * @brief Constructor. Setup the DataSenderProtoWriter. */ - DataCollectionProtoWriter( CANInterfaceIDTranslator &canIDTranslator ); + DataSenderProtoWriter( CANInterfaceIDTranslator &canIDTranslator ); /** * @brief Destructor. */ - ~DataCollectionProtoWriter(); + ~DataSenderProtoWriter(); - DataCollectionProtoWriter( const DataCollectionProtoWriter & ) = delete; - DataCollectionProtoWriter &operator=( const DataCollectionProtoWriter & ) = delete; - DataCollectionProtoWriter( DataCollectionProtoWriter && ) = delete; - DataCollectionProtoWriter &operator=( DataCollectionProtoWriter && ) = delete; + DataSenderProtoWriter( const DataSenderProtoWriter & ) = delete; + DataSenderProtoWriter &operator=( const DataSenderProtoWriter & ) = delete; + DataSenderProtoWriter( DataSenderProtoWriter && ) = delete; + DataSenderProtoWriter &operator=( DataSenderProtoWriter && ) = delete; /** * @brief Does the protobuf set up for the data collected by inspection engine @@ -110,6 +110,6 @@ class DataCollectionProtoWriter VehicleDataMsg::VehicleData mVehicleData{}; CANInterfaceIDTranslator mIDTranslator; }; -} // namespace DataManagement +} // namespace DataSender } // namespace IoTFleetWise } // namespace Aws diff --git a/src/datamanagement/datasender/src/DataSenderManager.cpp b/src/datamanagement/datasender/src/DataSenderManager.cpp new file mode 100644 index 00000000..ba649b3a --- /dev/null +++ b/src/datamanagement/datasender/src/DataSenderManager.cpp @@ -0,0 +1,255 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#include "DataSenderManager.h" +#include "CacheAndPersist.h" +#include "LoggingModule.h" +#include "TraceModule.h" +#include +#include +#include + +using namespace Aws::IoTFleetWise::DataSender; +using namespace Aws::IoTFleetWise::DataManagement; +using namespace Aws::IoTFleetWise::Platform::Linux::PersistencyManagement; + +DataSenderManager::DataSenderManager( std::shared_ptr mqttSender, + std::shared_ptr payloadManager, + CANInterfaceIDTranslator &canIDTranslator, + unsigned transmitThreshold ) + : mMQTTSender( std::move( mqttSender ) ) + , mPayloadManager( std::move( payloadManager ) ) + , mProtoWriter( canIDTranslator ) +{ + mTransmitThreshold = ( transmitThreshold > 0U ) ? transmitThreshold : UINT_MAX; +} + +void +DataSenderManager::processCollectedData( const TriggeredCollectionSchemeDataPtr triggeredCollectionSchemeDataPtr ) +{ + if ( triggeredCollectionSchemeDataPtr == nullptr ) + { + FWE_LOG_WARN( "Nothing to send as the input is empty" ); + return; + } + + setCollectionSchemeParameters( triggeredCollectionSchemeDataPtr ); + + transformTelemetryDataToProto( triggeredCollectionSchemeDataPtr ); +} + +void +DataSenderManager::setCollectionSchemeParameters( + const TriggeredCollectionSchemeDataPtr &triggeredCollectionSchemeDataPtr ) +{ + mCollectionSchemeParams.persist = triggeredCollectionSchemeDataPtr->metadata.persist; + mCollectionSchemeParams.compression = triggeredCollectionSchemeDataPtr->metadata.compress; + mCollectionSchemeParams.priority = triggeredCollectionSchemeDataPtr->metadata.priority; + mCollectionSchemeParams.eventID = triggeredCollectionSchemeDataPtr->eventID; + mCollectionSchemeParams.triggerTime = triggeredCollectionSchemeDataPtr->triggerTime; + mCollectionSchemeID = triggeredCollectionSchemeDataPtr->metadata.collectionSchemeID; +} + +void +DataSenderManager::transformTelemetryDataToProto( + const TriggeredCollectionSchemeDataPtr &triggeredCollectionSchemeDataPtr ) +{ + // Clear old data and setup metadata + mProtoWriter.setupVehicleData( triggeredCollectionSchemeDataPtr, mCollectionSchemeParams.eventID ); + + // Iterate through all the signals and add to the protobuf + for ( const auto &signal : triggeredCollectionSchemeDataPtr->signals ) + { + { + mProtoWriter.append( signal ); + if ( mProtoWriter.getVehicleDataMsgCount() >= mTransmitThreshold ) + { + uploadProto(); + // Setup the next payload chunk + mProtoWriter.setupVehicleData( triggeredCollectionSchemeDataPtr, mCollectionSchemeParams.eventID ); + } + } + } + + // Iterate through all the raw CAN frames and add to the protobuf + for ( const auto &canFrame : triggeredCollectionSchemeDataPtr->canFrames ) + { + mProtoWriter.append( canFrame ); + if ( mProtoWriter.getVehicleDataMsgCount() >= mTransmitThreshold ) + { + uploadProto(); + // Setup the next payload chunk + mProtoWriter.setupVehicleData( triggeredCollectionSchemeDataPtr, mCollectionSchemeParams.eventID ); + } + } + + // Add DTC info to the payload + if ( triggeredCollectionSchemeDataPtr->mDTCInfo.hasItems() ) + { + mProtoWriter.setupDTCInfo( triggeredCollectionSchemeDataPtr->mDTCInfo ); + const auto &dtcCodes = triggeredCollectionSchemeDataPtr->mDTCInfo.mDTCCodes; + + // Iterate through all the DTC codes and add to the protobuf + for ( const auto &dtc : dtcCodes ) + { + mProtoWriter.append( dtc ); + + if ( mProtoWriter.getVehicleDataMsgCount() >= mTransmitThreshold ) + { + uploadProto(); + // Setup the next payload chunk + mProtoWriter.setupVehicleData( triggeredCollectionSchemeDataPtr, mCollectionSchemeParams.eventID ); + } + } + } + + // Add Geohash to the payload + if ( triggeredCollectionSchemeDataPtr->mGeohashInfo.hasItems() ) + { + mProtoWriter.append( triggeredCollectionSchemeDataPtr->mGeohashInfo ); + if ( mProtoWriter.getVehicleDataMsgCount() >= mTransmitThreshold ) + { + uploadProto(); + // Setup the next payload chunk + mProtoWriter.setupVehicleData( triggeredCollectionSchemeDataPtr, mCollectionSchemeParams.eventID ); + } + } + + // Serialize and transmit any remaining messages + if ( mProtoWriter.getVehicleDataMsgCount() >= 1U ) + { + FWE_LOG_TRACE( "Queuing message for upload" ); + uploadProto(); + } +} + +bool +DataSenderManager::serialize( std::string &output ) +{ + // Note: a class member is used to store the serialized proto output to avoid heap fragmentation + if ( !mProtoWriter.serializeVehicleData( &output ) ) + { + FWE_LOG_ERROR( "Serialization failed" ); + return false; + } + return true; +} + +bool +DataSenderManager::compress( std::string &input ) +{ + if ( mCollectionSchemeParams.compression ) + { + FWE_LOG_TRACE( "Compress the payload before transmitting since compression flag is true" ); + if ( snappy::Compress( input.data(), input.size(), &mCompressedProtoOutput ) == 0U ) + { + FWE_LOG_TRACE( "Error in compressing the payload" ); + return false; + } + } + return true; +} + +ConnectivityError +DataSenderManager::send( const std::uint8_t *data, size_t size, std::shared_ptr sender ) +{ + if ( sender == nullptr ) + { + FWE_LOG_ERROR( "No sender provided" ); + return ConnectivityError::NotConfigured; + } + + ConnectivityError ret = sender->sendBuffer( data, size, mCollectionSchemeParams ); + if ( ret != ConnectivityError::Success ) + { + FWE_LOG_ERROR( "Failed to send vehicle data with error: " + std::to_string( static_cast( ret ) ) ); + } + else + { + TraceModule::get().sectionEnd( TraceSection::COLLECTION_SCHEME_CHANGE_TO_FIRST_DATA ); + TraceModule::get().incrementVariable( TraceVariable::MQTT_SIGNAL_MESSAGES_SENT_OUT ); + FWE_LOG_INFO( "A Payload of size: " + std::to_string( size ) + " bytes has been uploaded" ); + } + return ret; +} + +void +DataSenderManager::uploadProto() +{ + if ( !serialize( mProtoOutput ) ) + { + FWE_LOG_ERROR( "Data cannot be uploaded due to serialization failure" ); + return; + } + if ( mCollectionSchemeParams.compression ) + { + if ( !compress( mProtoOutput ) ) + { + FWE_LOG_ERROR( "Data cannot be uploaded due to compression failure" ); + return; + } + static_cast( send( reinterpret_cast( mCompressedProtoOutput.data() ), + mCompressedProtoOutput.size(), + mMQTTSender ) ); + } + else + { + static_cast( + send( reinterpret_cast( mProtoOutput.data() ), mProtoOutput.size(), mMQTTSender ) ); + } +} + +void +DataSenderManager::checkAndSendRetrievedData() +{ + // Retrieve the metadata from persistency library + Json::Value files; + ErrorCode status = mPayloadManager->retrievePayloadMetadata( files ); + + if ( status == ErrorCode::SUCCESS ) + { + FWE_LOG_TRACE( "Number of Payloads to transmit : " + std::to_string( files.size() ) ); + for ( const auto &file : files ) + { + // Retrieve the payload data from persistency library + std::string filename = file["filename"].asString(); + + CollectionSchemeParams collectionSchemeParams; + collectionSchemeParams.compression = file["compressionRequired"].asBool(); + collectionSchemeParams.persist = true; + + size_t payloadSize = + sizeof( size_t ) >= sizeof( uint64_t ) ? file["payloadSize"].asUInt64() : file["payloadSize"].asUInt(); + if ( uploadPersistedFile( filename, payloadSize, collectionSchemeParams ) == ConnectivityError::Success ) + { + FWE_LOG_TRACE( "Payload from file " + filename + " has been successfully sent to the backend" ); + } + else + { + FWE_LOG_ERROR( "Payload transmission for file " + filename + " failed" ); + } + } + FWE_LOG_INFO( "Upload of persisted payloads is finished" ); + } + else if ( status == ErrorCode::EMPTY ) + { + FWE_LOG_TRACE( "No Payloads to Retrieve" ); + } + else + { + FWE_LOG_ERROR( "Payload Metadata Retrieval Failed" ); + } +} + +ConnectivityError +DataSenderManager::uploadPersistedFile( const std::string &filename, + size_t size, + CollectionSchemeParams collectionSchemeParams ) +{ + auto res = mMQTTSender->sendFile( filename, size, collectionSchemeParams ); + if ( res != ConnectivityError::Success ) + { + FWE_LOG_ERROR( "offboardconnectivity error " + std::to_string( static_cast( res ) ) ); + } + return res; +} diff --git a/src/datamanagement/datasender/src/DataSenderManagerWorkerThread.cpp b/src/datamanagement/datasender/src/DataSenderManagerWorkerThread.cpp new file mode 100644 index 00000000..f06246da --- /dev/null +++ b/src/datamanagement/datasender/src/DataSenderManagerWorkerThread.cpp @@ -0,0 +1,249 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#include "DataSenderManagerWorkerThread.h" +#include "ICollectionScheme.h" +#include "LoggingModule.h" +#include "TraceModule.h" + +using namespace Aws::IoTFleetWise::DataSender; + +const uint32_t DataSenderManagerWorkerThread::MAX_NUMBER_OF_SIGNAL_TO_TRACE_LOG = 6; + +DataSenderManagerWorkerThread::DataSenderManagerWorkerThread( CANInterfaceIDTranslator &canIDTranslator, + std::shared_ptr connectivityModule, + std::shared_ptr mqttSender, + std::shared_ptr payloadManager, + unsigned maxMessageCount, + uint64_t persistencyUploadRetryInterval ) + : mDataSenderManager( std::move( mqttSender ), std::move( payloadManager ), canIDTranslator, maxMessageCount ) + , mConnectivityModule( std::move( connectivityModule ) ) +{ + mPersistencyUploadRetryIntervalMs = persistencyUploadRetryInterval; +} + +bool +DataSenderManagerWorkerThread::init( const std::shared_ptr &collectedDataQueue ) +{ + mCollectedDataQueue = collectedDataQueue; + return true; +} + +bool +DataSenderManagerWorkerThread::start() +{ + // Prevent concurrent stop/init + std::lock_guard lock( mThreadMutex ); + mShouldStop.store( false ); + if ( !mThread.create( doWork, this ) ) + { + FWE_LOG_TRACE( "Data Sender Manager Thread failed to start" ); + } + else + { + FWE_LOG_TRACE( "Data Sender Manager Thread started" ); + mThread.setThreadName( "fwDSDataSendMng" ); + } + + return mThread.isActive() && mThread.isValid(); +} + +bool +DataSenderManagerWorkerThread::stop() +{ + // It might take several seconds to finish all running S3 async PutObject requests + if ( ( !mThread.isValid() ) || ( !mThread.isActive() ) ) + { + return true; + } + std::lock_guard lock( mThreadMutex ); + mShouldStop.store( true, std::memory_order_relaxed ); + FWE_LOG_TRACE( "Request stop" ); + mWait.notify(); + mThread.release(); + FWE_LOG_TRACE( "Stop finished" ); + mShouldStop.store( false, std::memory_order_relaxed ); + return !mThread.isActive(); +} + +bool +DataSenderManagerWorkerThread::shouldStop() const +{ + return mShouldStop.load( std::memory_order_relaxed ); +} + +void +DataSenderManagerWorkerThread::doWork( void *data ) +{ + DataSenderManagerWorkerThread *sender = static_cast( data ); + + bool uploadedPersistedDataOnce = false; + + while ( !sender->shouldStop() ) + { + sender->mTimer.reset(); + uint64_t minTimeToWaitMs = UINT64_MAX; + + // Check if new collection scheme is available to update available senders + if ( sender->mUpdatedCollectionSchemeListAvailable ) + { + std::lock_guard lock( sender->mActiveCollectionSchemesMutex ); + sender->mUpdatedCollectionSchemeListAvailable = false; + } + if ( sender->mPersistencyUploadRetryIntervalMs > 0 ) + { + uint64_t timeToWaitMs = + sender->mPersistencyUploadRetryIntervalMs - + std::min( static_cast( sender->mRetrySendingPersistedDataTimer.getElapsedMs().count() ), + sender->mPersistencyUploadRetryIntervalMs ); + minTimeToWaitMs = std::min( minTimeToWaitMs, timeToWaitMs ); + } + + if ( minTimeToWaitMs < UINT64_MAX ) + { + FWE_LOG_TRACE( "Waiting for: " + std::to_string( minTimeToWaitMs ) + " ms. Persistency " + + std::to_string( sender->mPersistencyUploadRetryIntervalMs ) + " configured, " + + std::to_string( sender->mRetrySendingPersistedDataTimer.getElapsedMs().count() ) + + " timer." ); + sender->mWait.wait( static_cast( minTimeToWaitMs ) ); + } + else + { + sender->mWait.wait( Platform::Linux::Signal::WaitWithPredicate ); + auto elapsedTimeMs = sender->mTimer.getElapsedMs().count(); + FWE_LOG_TRACE( "Event arrived. Time elapsed waiting for the event: " + std::to_string( elapsedTimeMs ) + + " ms" ); + } + + // Dequeues the collected data queue and sends the data to cloud + auto consumedElements = sender->mCollectedDataQueue->consume_all( + [&]( const TriggeredCollectionSchemeDataPtr triggeredCollectionSchemeDataPtr ) { + // Only used for trace logging + std::string firstSignalValues = "["; + uint32_t signalPrintCounter = 0; + std::string firstSignalTimestamp; + for ( auto &s : triggeredCollectionSchemeDataPtr->signals ) + { + if ( firstSignalTimestamp.empty() ) + { + firstSignalTimestamp = " first signal timestamp: " + std::to_string( s.receiveTime ); + } + signalPrintCounter++; + if ( signalPrintCounter > MAX_NUMBER_OF_SIGNAL_TO_TRACE_LOG ) + { + firstSignalValues += " ..."; + break; + } + auto signalValue = s.getValue(); + firstSignalValues += std::to_string( s.signalID ) + ":"; + switch ( signalValue.getType() ) + { + case SignalType::UINT8: + firstSignalValues += std::to_string( signalValue.value.uint8Val ) + ","; + break; + case SignalType::INT8: + firstSignalValues += std::to_string( signalValue.value.int8Val ) + ","; + break; + case SignalType::UINT16: + firstSignalValues += std::to_string( signalValue.value.uint16Val ) + ","; + break; + case SignalType::INT16: + firstSignalValues += std::to_string( signalValue.value.int16Val ) + ","; + break; + case SignalType::UINT32: + firstSignalValues += std::to_string( signalValue.value.uint32Val ) + ","; + break; + case SignalType::INT32: + firstSignalValues += std::to_string( signalValue.value.int32Val ) + ","; + break; + case SignalType::UINT64: + firstSignalValues += std::to_string( signalValue.value.uint64Val ) + ","; + break; + case SignalType::INT64: + firstSignalValues += std::to_string( signalValue.value.int64Val ) + ","; + break; + case SignalType::FLOAT: + firstSignalValues += std::to_string( signalValue.value.floatVal ) + ","; + break; + case SignalType::DOUBLE: + firstSignalValues += std::to_string( signalValue.value.doubleVal ) + ","; + break; + case SignalType::BOOLEAN: + firstSignalValues += std::to_string( static_cast( signalValue.value.boolVal ) ) + ","; + break; + } + } + firstSignalValues += "]"; + // Avoid invoking Data Collection Sender if there is nothing to send. + if ( triggeredCollectionSchemeDataPtr->signals.empty() && + triggeredCollectionSchemeDataPtr->canFrames.empty() && + triggeredCollectionSchemeDataPtr->mDTCInfo.mDTCCodes.empty() && + ( !triggeredCollectionSchemeDataPtr->mGeohashInfo.hasItems() ) ) + { + FWE_LOG_INFO( + "The trigger for Campaign: " + triggeredCollectionSchemeDataPtr->metadata.collectionSchemeID + + " activated eventID: " + std::to_string( triggeredCollectionSchemeDataPtr->eventID ) + + " but no data is available to ingest" ); + } + else + { + FWE_LOG_INFO( + "FWE data ready to send with eventID " + + std::to_string( triggeredCollectionSchemeDataPtr->eventID ) + " from " + + triggeredCollectionSchemeDataPtr->metadata.collectionSchemeID + + " Signals:" + std::to_string( triggeredCollectionSchemeDataPtr->signals.size() ) + " " + + firstSignalValues + firstSignalTimestamp + + " trigger timestamp: " + std::to_string( triggeredCollectionSchemeDataPtr->triggerTime ) + + " raw CAN frames:" + std::to_string( triggeredCollectionSchemeDataPtr->canFrames.size() ) + + " DTCs:" + std::to_string( triggeredCollectionSchemeDataPtr->mDTCInfo.mDTCCodes.size() ) + + " Geohash:" + triggeredCollectionSchemeDataPtr->mGeohashInfo.mGeohashString ); + sender->mDataSenderManager.processCollectedData( triggeredCollectionSchemeDataPtr ); + } + } ); + TraceModule::get().setVariable( TraceVariable::QUEUE_INSPECTION_TO_SENDER, consumedElements ); + if ( ( !uploadedPersistedDataOnce ) || + ( ( sender->mPersistencyUploadRetryIntervalMs > 0 ) && + ( static_cast( sender->mRetrySendingPersistedDataTimer.getElapsedMs().count() ) >= + sender->mPersistencyUploadRetryIntervalMs ) ) ) + { + sender->mRetrySendingPersistedDataTimer.reset(); + if ( sender->mConnectivityModule->isAlive() ) + { + sender->mDataSenderManager.checkAndSendRetrievedData(); + uploadedPersistedDataOnce = true; + } + } + } +} + +bool +DataSenderManagerWorkerThread::isAlive() +{ + return mThread.isValid() && mThread.isActive(); +} + +void +DataSenderManagerWorkerThread::onDataReadyToPublish() +{ + mWait.notify(); +} + +void +DataSenderManagerWorkerThread::onChangeCollectionSchemeList( + const std::shared_ptr &activeCollectionSchemes ) +{ + std::lock_guard lock( mActiveCollectionSchemesMutex ); + mActiveCollectionSchemes = activeCollectionSchemes; + mUpdatedCollectionSchemeListAvailable = true; + FWE_LOG_TRACE( "New list of active collections schemes was handed over" ); + mWait.notify(); +} + +DataSenderManagerWorkerThread::~DataSenderManagerWorkerThread() +{ + // To make sure the thread stops during teardown of tests. + if ( isAlive() ) + { + stop(); + } +} diff --git a/src/datamanagement/datacollection/src/DataCollectionProtoWriter.cpp b/src/datamanagement/datasender/src/DataSenderProtoWriter.cpp similarity index 80% rename from src/datamanagement/datacollection/src/DataCollectionProtoWriter.cpp rename to src/datamanagement/datasender/src/DataSenderProtoWriter.cpp index e0d783ff..30d03c42 100644 --- a/src/datamanagement/datacollection/src/DataCollectionProtoWriter.cpp +++ b/src/datamanagement/datasender/src/DataSenderProtoWriter.cpp @@ -2,41 +2,41 @@ // SPDX-License-Identifier: Apache-2.0 // Includes -#include "DataCollectionProtoWriter.h" +#include "DataSenderProtoWriter.h" namespace Aws { namespace IoTFleetWise { -namespace DataManagement +namespace DataSender { -DataCollectionProtoWriter::DataCollectionProtoWriter( CANInterfaceIDTranslator &canIDTranslator ) +DataSenderProtoWriter::DataSenderProtoWriter( CANInterfaceIDTranslator &canIDTranslator ) : mTriggerTime( 0U ) , mIDTranslator( canIDTranslator ) { } -DataCollectionProtoWriter::~DataCollectionProtoWriter() +DataSenderProtoWriter::~DataSenderProtoWriter() { google::protobuf::ShutdownProtobufLibrary(); } void -DataCollectionProtoWriter::setupVehicleData( const TriggeredCollectionSchemeDataPtr triggeredCollectionSchemeData, - uint32_t collectionEventID ) +DataSenderProtoWriter::setupVehicleData( const TriggeredCollectionSchemeDataPtr triggeredCollectionSchemeData, + uint32_t collectionEventID ) { mVehicleDataMsgCount = 0U; mVehicleData.Clear(); - mVehicleData.set_campaign_sync_id( triggeredCollectionSchemeData->metaData.collectionSchemeID ); - mVehicleData.set_decoder_sync_id( triggeredCollectionSchemeData->metaData.decoderID ); + mVehicleData.set_campaign_sync_id( triggeredCollectionSchemeData->metadata.collectionSchemeID ); + mVehicleData.set_decoder_sync_id( triggeredCollectionSchemeData->metadata.decoderID ); mVehicleData.set_collection_event_id( collectionEventID ); mTriggerTime = triggeredCollectionSchemeData->triggerTime; mVehicleData.set_collection_event_time_ms_epoch( mTriggerTime ); } void -DataCollectionProtoWriter::append( const CollectedSignal &msg ) +DataSenderProtoWriter::append( const CollectedSignal &msg ) { auto capturedSignals = mVehicleData.add_captured_signals(); mVehicleDataMsgCount++; @@ -90,7 +90,7 @@ DataCollectionProtoWriter::append( const CollectedSignal &msg ) } void -DataCollectionProtoWriter::append( const CollectedCanRawFrame &msg ) +DataSenderProtoWriter::append( const CollectedCanRawFrame &msg ) { auto rawCanFrames = mVehicleData.add_can_frames(); mVehicleDataMsgCount++; @@ -102,14 +102,14 @@ DataCollectionProtoWriter::append( const CollectedCanRawFrame &msg ) } void -DataCollectionProtoWriter::setupDTCInfo( const DTCInfo &msg ) +DataSenderProtoWriter::setupDTCInfo( const DTCInfo &msg ) { auto dtcData = mVehicleData.mutable_dtc_data(); dtcData->set_relative_time_ms( static_cast( msg.receiveTime ) - static_cast( mTriggerTime ) ); } void -DataCollectionProtoWriter::append( const std::string &dtc ) +DataSenderProtoWriter::append( const std::string &dtc ) { auto dtcData = mVehicleData.mutable_dtc_data(); mVehicleDataMsgCount++; @@ -117,7 +117,7 @@ DataCollectionProtoWriter::append( const std::string &dtc ) } void -DataCollectionProtoWriter::append( const GeohashInfo &geohashInfo ) +DataSenderProtoWriter::append( const GeohashInfo &geohashInfo ) { auto geohashProto = mVehicleData.mutable_geohash(); mVehicleDataMsgCount++; @@ -126,17 +126,17 @@ DataCollectionProtoWriter::append( const GeohashInfo &geohashInfo ) } unsigned -DataCollectionProtoWriter::getVehicleDataMsgCount() const +DataSenderProtoWriter::getVehicleDataMsgCount() const { return mVehicleDataMsgCount; } bool -DataCollectionProtoWriter::serializeVehicleData( std::string *out ) const +DataSenderProtoWriter::serializeVehicleData( std::string *out ) const { return mVehicleData.SerializeToString( out ); } -} // namespace DataManagement +} // namespace DataSender } // namespace IoTFleetWise } // namespace Aws diff --git a/src/datamanagement/datasender/test/.clang-tidy b/src/datamanagement/datasender/test/.clang-tidy new file mode 100644 index 00000000..48f16d51 --- /dev/null +++ b/src/datamanagement/datasender/test/.clang-tidy @@ -0,0 +1,2 @@ +# Enable only one dummy check +Checks: "-*,llvm-twine-local" diff --git a/src/datamanagement/datacollection/test/DataCollectionProtoWriterTest.cpp b/src/datamanagement/datasender/test/DataSenderProtoWriterTest.cpp similarity index 80% rename from src/datamanagement/datacollection/test/DataCollectionProtoWriterTest.cpp rename to src/datamanagement/datasender/test/DataSenderProtoWriterTest.cpp index 5596e039..96dc0ca2 100644 --- a/src/datamanagement/datacollection/test/DataCollectionProtoWriterTest.cpp +++ b/src/datamanagement/datasender/test/DataSenderProtoWriterTest.cpp @@ -1,17 +1,17 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -#include "DataCollectionProtoWriter.h" +#include "DataSenderProtoWriter.h" #include "ClockHandler.h" #include "Testing.h" #include #include using namespace Aws::IoTFleetWise::TestingSupport; -using namespace Aws::IoTFleetWise::DataManagement; +using namespace Aws::IoTFleetWise::DataSender; using namespace Aws::IoTFleetWise::Platform::Linux; -class DataCollectionProtoWriterTest : public ::testing::Test +class DataSenderProtoWriterTest : public ::testing::Test { public: std::string @@ -29,17 +29,17 @@ class DataCollectionProtoWriterTest : public ::testing::Test }; // Test the edge to cloud payload fields in the proto -TEST_F( DataCollectionProtoWriterTest, TestVehicleData ) +TEST_F( DataSenderProtoWriterTest, TestVehicleData ) { CANInterfaceIDTranslator canIDTranslator; - DataCollectionProtoWriter protoWriter( canIDTranslator ); + DataSenderProtoWriter protoWriter( canIDTranslator ); std::shared_ptr triggeredCollectionSchemeDataPtr = std::make_shared(); - triggeredCollectionSchemeDataPtr->metaData.persist = false; - triggeredCollectionSchemeDataPtr->metaData.compress = false; - triggeredCollectionSchemeDataPtr->metaData.priority = 0; - triggeredCollectionSchemeDataPtr->metaData.collectionSchemeID = "123"; - triggeredCollectionSchemeDataPtr->metaData.decoderID = "456"; + triggeredCollectionSchemeDataPtr->metadata.persist = false; + triggeredCollectionSchemeDataPtr->metadata.compress = false; + triggeredCollectionSchemeDataPtr->metadata.priority = 0; + triggeredCollectionSchemeDataPtr->metadata.collectionSchemeID = "123"; + triggeredCollectionSchemeDataPtr->metadata.decoderID = "456"; // Set the trigger time to current time auto testClock = ClockHandler::getClock(); Timestamp testTriggerTime = testClock->systemTimeSinceEpochMs(); @@ -76,18 +76,18 @@ TEST_F( DataCollectionProtoWriterTest, TestVehicleData ) } // Test the DTC fields in the proto for the edge to cloud payload -TEST_F( DataCollectionProtoWriterTest, TestDTCData ) +TEST_F( DataSenderProtoWriterTest, TestDTCData ) { CANInterfaceIDTranslator canIDTranslator; - DataCollectionProtoWriter protoWriter( canIDTranslator ); + DataSenderProtoWriter protoWriter( canIDTranslator ); std::shared_ptr triggeredCollectionSchemeDataPtr = std::make_shared(); - triggeredCollectionSchemeDataPtr->metaData.persist = false; - triggeredCollectionSchemeDataPtr->metaData.compress = false; - triggeredCollectionSchemeDataPtr->metaData.priority = 0; - triggeredCollectionSchemeDataPtr->metaData.collectionSchemeID = "123"; - triggeredCollectionSchemeDataPtr->metaData.decoderID = "456"; + triggeredCollectionSchemeDataPtr->metadata.persist = false; + triggeredCollectionSchemeDataPtr->metadata.compress = false; + triggeredCollectionSchemeDataPtr->metadata.priority = 0; + triggeredCollectionSchemeDataPtr->metadata.collectionSchemeID = "123"; + triggeredCollectionSchemeDataPtr->metadata.decoderID = "456"; // Set the trigger time to current time auto testClock = ClockHandler::getClock(); Timestamp testTriggerTime = testClock->systemTimeSinceEpochMs(); @@ -133,18 +133,18 @@ TEST_F( DataCollectionProtoWriterTest, TestDTCData ) } // Test the Geohash fields in the proto for the edge to cloud payload -TEST_F( DataCollectionProtoWriterTest, TestGeohash ) +TEST_F( DataSenderProtoWriterTest, TestGeohash ) { CANInterfaceIDTranslator canIDTranslator; - DataCollectionProtoWriter protoWriter( canIDTranslator ); + DataSenderProtoWriter protoWriter( canIDTranslator ); std::shared_ptr triggeredCollectionSchemeDataPtr = std::make_shared(); - triggeredCollectionSchemeDataPtr->metaData.persist = false; - triggeredCollectionSchemeDataPtr->metaData.compress = false; - triggeredCollectionSchemeDataPtr->metaData.priority = 0; - triggeredCollectionSchemeDataPtr->metaData.collectionSchemeID = "123"; - triggeredCollectionSchemeDataPtr->metaData.decoderID = "456"; + triggeredCollectionSchemeDataPtr->metadata.persist = false; + triggeredCollectionSchemeDataPtr->metadata.compress = false; + triggeredCollectionSchemeDataPtr->metadata.priority = 0; + triggeredCollectionSchemeDataPtr->metadata.collectionSchemeID = "123"; + triggeredCollectionSchemeDataPtr->metadata.decoderID = "456"; // Set the trigger time to current time auto testClock = ClockHandler::getClock(); Timestamp testTriggerTime = testClock->systemTimeSinceEpochMs(); diff --git a/src/datamanagement/datacollection/test/dm-collection-scheme-example.json b/src/datamanagement/datasender/test/dm-collection-scheme-example.json similarity index 100% rename from src/datamanagement/datacollection/test/dm-collection-scheme-example.json rename to src/datamanagement/datasender/test/dm-collection-scheme-example.json diff --git a/src/datamanagement/datasender/test/valgrind.supp b/src/datamanagement/datasender/test/valgrind.supp new file mode 100644 index 00000000..17d9a8d5 --- /dev/null +++ b/src/datamanagement/datasender/test/valgrind.supp @@ -0,0 +1,32 @@ +{ + boost::lockfree::do_push + Memcheck:Cond + ... + fun:_ZN5boost8lockfree5queue* + ... +} + +{ + + Memcheck:Param + write(buf) + ... + fun:sem_open + ... +} + +{ + fastrptps_bug_remove_persistence_guid + Memcheck:Addr2 + ... + fun:*remove_persistence_guid* + ... +} + +{ + fastrptps_bug_add_persistence_guid + Memcheck:Addr2 + ... + fun:*add_persistence_guid* + ... +} diff --git a/src/datamanagement/types/CMakeLists.txt b/src/datamanagement/types/CMakeLists.txt index b12fd451..6ea3d5ef 100644 --- a/src/datamanagement/types/CMakeLists.txt +++ b/src/datamanagement/types/CMakeLists.txt @@ -36,7 +36,6 @@ install( include/GeohashInfo.h include/MessageTypes.h include/OBDDataTypes.h - include/SensorTypes.h include/SignalTypes.h DESTINATION include) diff --git a/src/datamanagement/types/include/CollectionInspectionAPITypes.h b/src/datamanagement/types/include/CollectionInspectionAPITypes.h index cfbe53eb..24d59063 100644 --- a/src/datamanagement/types/include/CollectionInspectionAPITypes.h +++ b/src/datamanagement/types/include/CollectionInspectionAPITypes.h @@ -7,7 +7,6 @@ #include "GeohashInfo.h" #include "MessageTypes.h" #include "OBDDataTypes.h" -#include "SensorTypes.h" #include "SignalTypes.h" // multi producer queue: #include @@ -44,7 +43,7 @@ static constexpr double MAX_PROBABILITY = 1.0; // The following structs describe an inspection view on all active collection conditions // As a start these structs are mainly a copy of the data defined in ICollectionScheme -struct PassThroughMetaData +struct PassThroughMetadata { bool compress{ false }; bool persist{ false }; @@ -53,27 +52,6 @@ struct PassThroughMetaData std::string collectionSchemeID; }; -// Camera Data collection related types -enum class InspectionMatrixImageCollectionType -{ - TIME_BASED, - FRAME_BASED, - NONE -}; - -struct InspectionMatrixImageCollectionInfo -{ - ImageDeviceID deviceID; // Unique Identifier of the image sensor in the system - uint32_t imageFormat; // Image format expected from the System e.g. PNG. - // Exact ids of the type will end up in an enum in the - // CollectionScheme decoder. - InspectionMatrixImageCollectionType collectionType; // Whether Images are collected from the device based on - // a timewindow or based on frame number. - uint32_t beforeDurationMs; // Amount of time in ms to be collected from the - // image sensor buffer. This time is counted before the - // condition is met. This is not relevant when the -}; - // As a start these structs are mainly a copy of the data defined in ICollectionScheme but as plain old data structures struct InspectionMatrixSignalCollectionInfo { @@ -107,9 +85,7 @@ struct ConditionWithCollectedData bool includeActiveDtcs; bool triggerOnlyOnRisingEdge; double probabilityToSend; - PassThroughMetaData metaData; - std::vector imageCollectionInfos; - bool includeImageCapture; + PassThroughMetadata metadata; }; struct InspectionMatrix @@ -362,7 +338,7 @@ using ActiveDTCBufferPtr = std::shared_ptr; struct TriggeredCollectionSchemeData { - PassThroughMetaData metaData; + PassThroughMetadata metadata; Timestamp triggerTime; std::vector signals; std::vector canFrames; diff --git a/src/datamanagement/types/include/Geohash.h b/src/datamanagement/types/include/Geohash.h index 3578e6cb..6f719a8d 100644 --- a/src/datamanagement/types/include/Geohash.h +++ b/src/datamanagement/types/include/Geohash.h @@ -21,6 +21,7 @@ namespace DataInspection */ // coverity[cert_dcl60_cpp_violation] false positive - class only defined once // coverity[autosar_cpp14_m3_2_2_violation] false positive - class only defined once +// coverity[misra_cpp_2008_rule_3_2_2_violation] false positive - class only defined once class Geohash { public: diff --git a/src/datamanagement/types/include/SensorTypes.h b/src/datamanagement/types/include/SensorTypes.h deleted file mode 100644 index de773093..00000000 --- a/src/datamanagement/types/include/SensorTypes.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include - -namespace Aws -{ -namespace IoTFleetWise -{ -namespace DataManagement -{ -/** - * @brief Identifier of an Image Sensor device. - */ -using ImageDeviceID = uint32_t; - -} // namespace DataManagement -} // namespace IoTFleetWise -} // namespace Aws diff --git a/src/executionmanagement/CMakeLists.txt b/src/executionmanagement/CMakeLists.txt index a80cac59..43d76d2a 100644 --- a/src/executionmanagement/CMakeLists.txt +++ b/src/executionmanagement/CMakeLists.txt @@ -22,6 +22,7 @@ target_link_libraries( IoTFleetWise::DataCollection IoTFleetWise::DataInspection IoTFleetWise::DataDecoding + IoTFleetWise::DataSender IoTFleetWise::Platform::Linux IoTFleetWise::OffboardConnectivityAwsIot $<$:IoTFleetWise::CustomDataSource> @@ -29,7 +30,7 @@ target_link_libraries( add_library(${libraryAliasName} ALIAS ${libraryTargetName}) -### AWS IoT FleetWise Edge Executable ### +### FWE Executable ### if(FWE_BUILD_EXECUTABLE) add_executable(aws-iot-fleetwise-edge src/main.cpp) diff --git a/src/executionmanagement/include/IoTFleetWiseEngine.h b/src/executionmanagement/include/IoTFleetWiseEngine.h index df8e6763..2e5a75b5 100644 --- a/src/executionmanagement/include/IoTFleetWiseEngine.h +++ b/src/executionmanagement/include/IoTFleetWiseEngine.h @@ -4,26 +4,31 @@ #pragma once // Includes +#ifdef FWE_FEATURE_AAOS_VHAL +#include "AaosVhalSource.h" +#endif +#ifdef FWE_FEATURE_GREENGRASSV2 +#include "AwsGGConnectivityModule.h" +#endif #include "AwsIotChannel.h" #include "AwsIotConnectivityModule.h" +#include "AwsSDKMemoryManager.h" #include "CANDataSource.h" #include "CANInterfaceIDTranslator.h" #include "CacheAndPersist.h" #include "ClockHandler.h" #include "CollectionInspectionWorkerThread.h" #include "CollectionSchemeManager.h" -#include "DataCollectionSender.h" -#ifdef FWE_FEATURE_CAMERA -#include "DataOverDDSModule.h" -#endif // FWE_FEATURE_CAMERA -#ifdef FWE_FEATURE_IWAVE_GPS -#include "IWaveGpsSource.h" -#endif +#include "DataSenderManagerWorkerThread.h" #ifdef FWE_FEATURE_EXTERNAL_GPS #include "ExternalGpsSource.h" #endif #include "ExternalCANDataSource.h" -#include "IDataReadyToPublishListener.h" +#include "IConnectivityChannel.h" +#include "IConnectivityModule.h" +#ifdef FWE_FEATURE_IWAVE_GPS +#include "IWaveGpsSource.h" +#endif #include "OBDOverCANModule.h" #include "RemoteProfiler.h" #include "Schema.h" @@ -43,9 +48,11 @@ namespace ExecutionManagement { using namespace Aws::IoTFleetWise::DataManagement; using namespace Aws::IoTFleetWise::DataInspection; +using namespace Aws::IoTFleetWise::DataSender; using namespace Aws::IoTFleetWise::Platform::Linux; using namespace Aws::IoTFleetWise::Platform::Linux::PersistencyManagement; using namespace Aws::IoTFleetWise::VehicleNetwork; +using namespace Aws::IoTFleetWise::OffboardConnectivity; using namespace Aws::IoTFleetWise::OffboardConnectivityAwsIot; /** @@ -54,19 +61,13 @@ using namespace Aws::IoTFleetWise::OffboardConnectivityAwsIot; * 2- Initializes the Inspection Engine * 3- Initializes the CollectionScheme Ingestion & Management modules * 5- Initializes the Vehicle Network module. - * The bootstrap executes a worker thread that listens to the Inspection - * Engine notification, upon which the offboardconnectivity module is invoked to - * send the data to the cloud. + * 6- Initializes the DataSenderManager module. */ -class IoTFleetWiseEngine : public IDataReadyToPublishListener +class IoTFleetWiseEngine { public: - static const uint32_t MAX_NUMBER_OF_SIGNAL_TO_TRACE_LOG; - static const uint64_t FAST_RETRY_UPLOAD_PERSISTED_INTERVAL_MS; // retry every second - static const uint64_t DEFAULT_RETRY_UPLOAD_PERSISTED_INTERVAL_MS; // retry every 10 second - IoTFleetWiseEngine(); - ~IoTFleetWiseEngine() override; + ~IoTFleetWiseEngine(); IoTFleetWiseEngine( const IoTFleetWiseEngine & ) = delete; IoTFleetWiseEngine &operator=( const IoTFleetWiseEngine & ) = delete; @@ -79,19 +80,6 @@ class IoTFleetWiseEngine : public IDataReadyToPublishListener bool disconnect(); bool isAlive(); - /** - * @brief Callback from the Inspection Engine to wake up this thread and - * publish the data to the cloud. - */ - void onDataReadyToPublish() override; - - /** - * @brief Check if the data was persisted in the last cycle due to no offboardconnectivity, - * retrieve all the data and send - * @return true if either no data persisted or all persisted data was handed over to connectivity - */ - bool checkAndSendRetrievedData(); - /** * @brief Gets a list of OBD PIDs to request externally */ @@ -123,6 +111,26 @@ class IoTFleetWiseEngine : public IDataReadyToPublishListener void setExternalGpsLocation( double latitude, double longitude ); #endif +#ifdef FWE_FEATURE_AAOS_VHAL + /** + * Returns a vector of vehicle property info + * + * @return Vehicle property info, with each member containing an array with 4 values: + * - Vehicle property ID + * - Area index + * - Result index + * - Signal ID + */ + std::vector> getVehiclePropertyInfo(); + + /** + * @brief Sets an Android Automotive vehicle property + * @param signalId Signal ID + * @param value Vehicle property value + */ + void setVehicleProperty( uint32_t signalId, double value ); +#endif + /** * @brief Return a status summary, including the MQTT connection status, the campaign ARNs, * and the number of payloads sent. @@ -142,15 +150,14 @@ class IoTFleetWiseEngine : public IDataReadyToPublishListener std::shared_ptr mPersistDecoderManifestCollectionSchemesAndData; private: - static constexpr uint64_t DEFAULT_PERSISTENCY_UPLOAD_RETRY_INTERVAL_MS = 0; + static constexpr uint64_t DEFAULT_RETRY_UPLOAD_PERSISTED_INTERVAL_MS = 10000; + Thread mThread; std::atomic mShouldStop{ false }; mutable std::mutex mThreadMutex; Platform::Linux::Signal mWait; Timer mTimer; - Timer mRetrySendingPersistedDataTimer; - uint64_t mPersistencyUploadRetryIntervalMs{ DEFAULT_PERSISTENCY_UPLOAD_RETRY_INTERVAL_MS }; Timer mPrintMetricsCyclicTimer; uint64_t mPrintMetricsCyclicPeriodMs{ 0 }; // default to 0 which means no cyclic printing @@ -162,13 +169,12 @@ class IoTFleetWiseEngine : public IDataReadyToPublishListener std::vector> mCANDataSources; std::unique_ptr mExternalCANDataSource; std::unique_ptr mCANDataConsumer; - std::shared_ptr mDataCollectionSender; - std::shared_ptr mAwsIotModule; - std::shared_ptr mAwsIotChannelSendCanData; - std::shared_ptr mAwsIotChannelSendCheckin; - std::shared_ptr mAwsIotChannelReceiveCollectionSchemeList; - std::shared_ptr mAwsIotChannelReceiveDecoderManifest; + std::shared_ptr mConnectivityModule; + std::shared_ptr mConnectivityChannelSendVehicleData; + std::shared_ptr mConnectivityChannelSendCheckin; + std::shared_ptr mConnectivityChannelReceiveCollectionSchemeList; + std::shared_ptr mConnectivityChannelReceiveDecoderManifest; std::shared_ptr mPayloadManager; std::shared_ptr mSchemaPtr; @@ -176,19 +182,21 @@ class IoTFleetWiseEngine : public IDataReadyToPublishListener std::shared_ptr mCollectionInspectionWorkerThread; + std::shared_ptr mDataSenderManagerWorkerThread; + std::unique_ptr mRemoteProfiler; - std::shared_ptr mAwsIotChannelMetricsUpload; - std::shared_ptr mAwsIotChannelLogsUpload; -#ifdef FWE_FEATURE_CAMERA - // DDS Module - std::shared_ptr mDataOverDDSModule; -#endif // FWE_FEATURE_CAMERA + std::shared_ptr mConnectivityChannelMetricsUpload; + std::shared_ptr mConnectivityChannelLogsUpload; + #ifdef FWE_FEATURE_IWAVE_GPS std::shared_ptr mIWaveGpsSource; #endif #ifdef FWE_FEATURE_EXTERNAL_GPS std::shared_ptr mExternalGpsSource; #endif +#ifdef FWE_FEATURE_AAOS_VHAL + std::shared_ptr mAaosVhalSource; +#endif }; } // namespace ExecutionManagement } // namespace IoTFleetWise diff --git a/src/executionmanagement/src/IoTFleetWiseEngine.cpp b/src/executionmanagement/src/IoTFleetWiseEngine.cpp index cee643f5..5f3eb53c 100644 --- a/src/executionmanagement/src/IoTFleetWiseEngine.cpp +++ b/src/executionmanagement/src/IoTFleetWiseEngine.cpp @@ -21,10 +21,6 @@ using namespace Aws::IoTFleetWise::Platform::Linux::PersistencyManagement; using Aws::IoTFleetWise::OffboardConnectivity::CollectionSchemeParams; using Aws::IoTFleetWise::OffboardConnectivity::ConnectivityError; -const uint32_t IoTFleetWiseEngine::MAX_NUMBER_OF_SIGNAL_TO_TRACE_LOG = 6; -const uint64_t IoTFleetWiseEngine::FAST_RETRY_UPLOAD_PERSISTED_INTERVAL_MS = 1000; -const uint64_t IoTFleetWiseEngine::DEFAULT_RETRY_UPLOAD_PERSISTED_INTERVAL_MS = 10000; - static const std::string CAN_INTERFACE_TYPE = "canInterface"; static const std::string EXTERNAL_CAN_INTERFACE_TYPE = "externalCanInterface"; static const std::string OBD_INTERFACE_TYPE = "obdInterface"; @@ -104,14 +100,11 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) { FWE_LOG_ERROR( "Failed to init persistency library" ); } + uint64_t persistencyUploadRetryIntervalMs = DEFAULT_RETRY_UPLOAD_PERSISTED_INTERVAL_MS; if ( config["staticConfig"]["persistency"].isMember( "persistencyUploadRetryIntervalMs" ) ) { - mPersistencyUploadRetryIntervalMs = - static_cast( config["staticConfig"]["persistencyUploadRetryIntervalMs"].asInt() ); - } - else - { - mPersistencyUploadRetryIntervalMs = DEFAULT_RETRY_UPLOAD_PERSISTED_INTERVAL_MS; + persistencyUploadRetryIntervalMs = static_cast( + config["staticConfig"]["persistency"]["persistencyUploadRetryIntervalMs"].asInt() ); } // Payload Manager for offline data management mPayloadManager = std::make_shared( mPersistDecoderManifestCollectionSchemesAndData ); @@ -148,24 +141,107 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) { mCANIDTranslator.add( "EXTERNAL-GPS-CAN" ); } +#endif +#ifdef FWE_FEATURE_AAOS_VHAL + if ( config["staticConfig"].isMember( "aaosVhalExample" ) ) + { + mCANIDTranslator.add( + config["staticConfig"]["aaosVhalExample"][AaosVhalSource::CAN_CHANNEL_NUMBER].asString() ); + } + else + { + mCANIDTranslator.add( "AAOS-VHAL-CAN" ); + } #endif /*************************CAN InterfaceID to InternalID Translator end*********/ /**************************Connectivity bootstrap begin*******************************/ + // Pass on the AWS SDK Bootstrap handle to the IoTModule. + auto bootstrapPtr = AwsBootstrap::getInstance().getClientBootStrap(); + std::size_t maxAwsSdkHeapMemoryBytes = 0U; + if ( config["staticConfig"]["internalParameters"]["maximumAwsSdkHeapMemoryBytes"] ) + { + maxAwsSdkHeapMemoryBytes = + config["staticConfig"]["internalParameters"]["maximumAwsSdkHeapMemoryBytes"].asUInt(); + if ( ( maxAwsSdkHeapMemoryBytes != 0U ) && + AwsSDKMemoryManager::getInstance().setLimit( maxAwsSdkHeapMemoryBytes ) ) + { + FWE_LOG_INFO( "Maximum AWS SDK Heap Memory Bytes has been configured:" + + std::to_string( maxAwsSdkHeapMemoryBytes ) ); + } + else + { + FWE_LOG_TRACE( "Maximum AWS SDK Heap Memory Bytes will use default value" ); + } + } + else + { + FWE_LOG_TRACE( "Maximum AWS SDK Heap Memory Bytes will use default value" ); + } - mAwsIotModule = std::make_shared(); +#ifdef FWE_FEATURE_GREENGRASSV2 + if ( config["staticConfig"]["mqttConnection"]["connectionType"].asString() == "iotGreengrassV2" ) + { + FWE_LOG_INFO( "ConnectionType is iotGreengrassV2" ) + mConnectivityModule = std::make_shared( bootstrapPtr ); + } + else +#endif + { + std::string privateKey; + std::string certificate; + std::string rootCA; + FWE_LOG_INFO( "ConnectionType is iotCore " + + config["staticConfig"]["mqttConnection"]["connectionType"].asString() ) + // fetch connection parameters from config + if ( config["staticConfig"]["mqttConnection"].isMember( "privateKey" ) ) + { + privateKey = config["staticConfig"]["mqttConnection"]["privateKey"].asString(); + } + else if ( config["staticConfig"]["mqttConnection"].isMember( "privateKeyFilename" ) ) + { + privateKey = + getFileContents( config["staticConfig"]["mqttConnection"]["privateKeyFilename"].asString() ); + } + if ( config["staticConfig"]["mqttConnection"].isMember( "certificate" ) ) + { + certificate = config["staticConfig"]["mqttConnection"]["certificate"].asString(); + } + else if ( config["staticConfig"]["mqttConnection"].isMember( "certificateFilename" ) ) + { + certificate = + getFileContents( config["staticConfig"]["mqttConnection"]["certificateFilename"].asString() ); + } + if ( config["staticConfig"]["mqttConnection"].isMember( "rootCA" ) ) + { + rootCA = config["staticConfig"]["mqttConnection"]["rootCA"].asString(); + } + else if ( config["staticConfig"]["mqttConnection"].isMember( "rootCAFilename" ) ) + { + rootCA = getFileContents( config["staticConfig"]["mqttConnection"]["rootCAFilename"].asString() ); + } + mConnectivityModule = std::make_shared( + privateKey, + certificate, + rootCA, + config["staticConfig"]["mqttConnection"]["endpointUrl"].asString(), + config["staticConfig"]["mqttConnection"]["clientId"].asString(), + bootstrapPtr, + true ); + } // Only CAN data channel needs a payloadManager object for persistency and compression support, // for other components this will be nullptr - mAwsIotChannelSendCanData = mAwsIotModule->createNewChannel( mPayloadManager ); - mAwsIotChannelSendCanData->setTopic( config["staticConfig"]["mqttConnection"]["canDataTopic"].asString() ); + mConnectivityChannelSendVehicleData = mConnectivityModule->createNewChannel( mPayloadManager ); + mConnectivityChannelSendVehicleData->setTopic( + config["staticConfig"]["mqttConnection"]["canDataTopic"].asString() ); - mAwsIotChannelReceiveCollectionSchemeList = mAwsIotModule->createNewChannel( nullptr ); - mAwsIotChannelReceiveCollectionSchemeList->setTopic( + mConnectivityChannelReceiveCollectionSchemeList = mConnectivityModule->createNewChannel( nullptr ); + mConnectivityChannelReceiveCollectionSchemeList->setTopic( config["staticConfig"]["mqttConnection"]["collectionSchemeListTopic"].asString(), true ); - mAwsIotChannelReceiveDecoderManifest = mAwsIotModule->createNewChannel( nullptr ); - mAwsIotChannelReceiveDecoderManifest->setTopic( + mConnectivityChannelReceiveDecoderManifest = mConnectivityModule->createNewChannel( nullptr ); + mConnectivityChannelReceiveDecoderManifest->setTopic( config["staticConfig"]["mqttConnection"]["decoderManifestTopic"].asString(), true ); /* @@ -175,8 +251,8 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) */ if ( config["staticConfig"]["mqttConnection"]["metricsUploadTopic"].asString().length() > 0 ) { - mAwsIotChannelMetricsUpload = mAwsIotModule->createNewChannel( nullptr ); - mAwsIotChannelMetricsUpload->setTopic( + mConnectivityChannelMetricsUpload = mConnectivityModule->createNewChannel( nullptr ); + mConnectivityChannelMetricsUpload->setTopic( config["staticConfig"]["mqttConnection"]["metricsUploadTopic"].asString() ); } /* @@ -185,58 +261,18 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) */ if ( config["staticConfig"]["mqttConnection"]["loggingUploadTopic"].asString().length() > 0 ) { - mAwsIotChannelLogsUpload = mAwsIotModule->createNewChannel( nullptr ); - mAwsIotChannelLogsUpload->setTopic( + mConnectivityChannelLogsUpload = mConnectivityModule->createNewChannel( nullptr ); + mConnectivityChannelLogsUpload->setTopic( config["staticConfig"]["mqttConnection"]["loggingUploadTopic"].asString() ); } // Create an ISender for sending Checkins - mAwsIotChannelSendCheckin = mAwsIotModule->createNewChannel( nullptr ); - mAwsIotChannelSendCheckin->setTopic( config["staticConfig"]["mqttConnection"]["checkinTopic"].asString() ); - - mDataCollectionSender = std::make_shared( - mAwsIotChannelSendCanData, - config["staticConfig"]["publishToCloudParameters"]["maxPublishMessageCount"].asUInt(), - mCANIDTranslator ); - - // Pass on the AWS SDK Bootstrap handle to the IoTModule. - auto bootstrapPtr = AwsBootstrap::getInstance().getClientBootStrap(); + mConnectivityChannelSendCheckin = mConnectivityModule->createNewChannel( nullptr ); + mConnectivityChannelSendCheckin->setTopic( + config["staticConfig"]["mqttConnection"]["checkinTopic"].asString() ); - std::string privateKey; - std::string certificate; - std::string rootCA; - if ( config["staticConfig"]["mqttConnection"].isMember( "privateKey" ) ) - { - privateKey = config["staticConfig"]["mqttConnection"]["privateKey"].asString(); - } - else if ( config["staticConfig"]["mqttConnection"].isMember( "privateKeyFilename" ) ) - { - privateKey = getFileContents( config["staticConfig"]["mqttConnection"]["privateKeyFilename"].asString() ); - } - if ( config["staticConfig"]["mqttConnection"].isMember( "certificate" ) ) - { - certificate = config["staticConfig"]["mqttConnection"]["certificate"].asString(); - } - else if ( config["staticConfig"]["mqttConnection"].isMember( "certificateFilename" ) ) - { - certificate = getFileContents( config["staticConfig"]["mqttConnection"]["certificateFilename"].asString() ); - } - if ( config["staticConfig"]["mqttConnection"].isMember( "rootCA" ) ) - { - rootCA = config["staticConfig"]["mqttConnection"]["rootCA"].asString(); - } - else if ( config["staticConfig"]["mqttConnection"].isMember( "rootCAFilename" ) ) - { - rootCA = getFileContents( config["staticConfig"]["mqttConnection"]["rootCAFilename"].asString() ); - } // For asynchronous connect the call needs to be done after all channels created and setTopic calls - mAwsIotModule->connect( privateKey, - certificate, - rootCA, - config["staticConfig"]["mqttConnection"]["endpointUrl"].asString(), - config["staticConfig"]["mqttConnection"]["clientId"].asString(), - bootstrapPtr, - true ); + mConnectivityModule->connect(); /*************************Connectivity bootstrap end***************************************/ /*************************Remote Profiling bootstrap begin**********************************/ @@ -269,8 +305,8 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) // loggingUploadMaxWaitBeforeUploadMs // profilerPrefix mRemoteProfiler = std::make_unique( - mAwsIotChannelMetricsUpload, - mAwsIotChannelLogsUpload, + mConnectivityChannelMetricsUpload, + mConnectivityChannelLogsUpload, config["staticConfig"]["remoteProfilerDefaultValues"]["metricsUploadIntervalMs"].asUInt(), config["staticConfig"]["remoteProfilerDefaultValues"]["loggingUploadMaxWaitBeforeUploadMs"].asUInt(), logThreshold, @@ -315,24 +351,38 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) FWE_LOG_ERROR( "Failed to init and start the Inspection Engine" ); return false; } - // Make sure the Inspection Engine can notify the Bootstrap thread about ready to be - // published data. - if ( !mCollectionInspectionWorkerThread->subscribeListener( this ) ) + /*************************Inspection Engine bootstrap end***********************************/ + + /*************************DataSender bootstrap begin*********************************/ + mDataSenderManagerWorkerThread = std::make_shared( + mCANIDTranslator, + mConnectivityModule, + mConnectivityChannelSendVehicleData, + mPayloadManager, + config["staticConfig"]["publishToCloudParameters"]["maxPublishMessageCount"].asUInt(), + persistencyUploadRetryIntervalMs ); + if ( ( !mDataSenderManagerWorkerThread->init( mCollectedDataReadyToPublish ) ) || + ( !mDataSenderManagerWorkerThread->start() ) ) { - FWE_LOG_ERROR( "Failed register the Engine Thread to the Inspection Module" ); + FWE_LOG_ERROR( "Failed to init and start the Data Sender" ); return false; } - /*************************Inspection Engine bootstrap end***********************************/ + if ( !mCollectionInspectionWorkerThread->subscribeListener( mDataSenderManagerWorkerThread.get() ) ) + { + FWE_LOG_ERROR( "Failed register the Data Sender Thread to the Inspection Module" ); + return false; + } + /*************************DataSender bootstrap end*********************************/ /*************************CollectionScheme Ingestion bootstrap begin*********************************/ // CollectionScheme Ingestion module executes in the context for the offboardconnectivity thread. Upcoming // messages are expected to come either on the decoder manifest topic or the collectionScheme topic or both // ( eventually ). - mSchemaPtr = std::make_shared( mAwsIotChannelReceiveDecoderManifest, - mAwsIotChannelReceiveCollectionSchemeList, - mAwsIotChannelSendCheckin ); + mSchemaPtr = std::make_shared( mConnectivityChannelReceiveDecoderManifest, + mConnectivityChannelReceiveCollectionSchemeList, + mConnectivityChannelSendCheckin ); /*****************************CollectionScheme Management bootstrap begin*****************************/ @@ -367,6 +417,15 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) return false; } + // Make sure the CollectionScheme Manager can notify the Data Sender about the availability of + // a new set of collection CollectionSchemes. + if ( !mCollectionSchemeManagerPtr->subscribeListener( + static_cast( mDataSenderManagerWorkerThread.get() ) ) ) + { + FWE_LOG_ERROR( "Failed register the Data Sender to the CollectionScheme Manager Module" ); + return false; + } + // Allow CollectionSchemeManagement to send checkins through the Schema Object Callback mCollectionSchemeManagerPtr->setSchemaListenerPtr( mSchemaPtr ); @@ -486,88 +545,6 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) } /****************************CollectionScheme Manager bootstrap end*************************/ -#ifdef FWE_FEATURE_CAMERA - /********************************DDS Module bootstrap start*********************************/ - // First we need to parse the configuration - // and extract all the node(s) settings our DDS module needs. Those are per node( device): - // - The device unique ID. - // - The device Type e.g. CAMERA. - // - The upstream ( Publish ) and downstream( subscribe ) DDS Topics. - // - The Topics DDS QoS. - // - The DDS Transport to be used ( SHM or UDP ). - // - The temporary location where the data artifact received will be stored. - // - Writer and Reader names that will be registered on the DDS Network. - // - The DDS Domain ID that we should register too. - // Other configurations can be added as per the need e.g. UDP Ports/IPs - - DDSDataSourcesConfig ddsNodes; - for ( const auto &ddsNode : config["dds-nodes-configuration"] ) - { - DDSDataSourceConfig nodeConfig; - // Transport, currently only UDP and SHM are supported - if ( ddsNode["dds-transport-protocol"].asString() == "SHM" ) - { - nodeConfig.transportType = DDSTransportType::SHM; - } - else if ( ddsNode["dds-transport-protocol"].asString() == "UDP" ) - { - nodeConfig.transportType = DDSTransportType::UDP; - } - else - { - FWE_LOG_WARN( "Unsupported Transport config provided for a DDS Node, skipping it" ); - continue; - } - - // Device Type, currently only CAMERA is supported - if ( ddsNode["dds-device-type"].asString() == "CAMERA" ) - { - nodeConfig.sourceType = SensorSourceType::CAMERA; - } - else - { - FWE_LOG_WARN( "Unsupported Device type provided for a DDS Node, skipping it" ); - continue; - } - - nodeConfig.sourceID = ddsNode["dds-device-id"].asUInt(); - nodeConfig.domainID = ddsNode["dds-domain-id"].asUInt(); - nodeConfig.readerName = ddsNode["dds-reader-name"].asString(); - nodeConfig.writerName = ddsNode["dds-writer-name"].asString(); - nodeConfig.publishTopicName = ddsNode["upstream-dds-topic-name"].asString(); - nodeConfig.subscribeTopicName = ddsNode["downstream-dds-topic-name"].asString(); - nodeConfig.topicQoS = ddsNode["dds-topics-qos"].asString(); - nodeConfig.temporaryCacheLocation = ddsNode["dds-tmp-cache-location"].asString(); - ddsNodes.emplace_back( nodeConfig ); - } - // Only if there is at least one DDS Node, we should create the DDS Module - if ( !ddsNodes.empty() ) - { - mDataOverDDSModule = std::make_shared(); - // Init the Module - if ( !mDataOverDDSModule->init( ddsNodes ) ) - { - FWE_LOG_ERROR( "Failed to initialize the DDS Module" ); - return false; - } - // Register the DDS Module as a listener to the Inspection Engine and connect it. - if ( ( !mCollectionInspectionWorkerThread->subscribeToEvents( - static_cast( mDataOverDDSModule.get() ) ) ) || - ( !mDataOverDDSModule->connect() ) ) - { - FWE_LOG_ERROR( "Failed to connect the DDS Module" ); - return false; - } - FWE_LOG_INFO( "DDS Module connected" ); - } - else - { - FWE_LOG_INFO( "DDS Module disabled" ); - } - - /********************************DDS Module bootstrap end*********************************/ -#endif // FWE_FEATURE_CAMERA - #ifdef FWE_FEATURE_IWAVE_GPS /********************************IWave GPS Example NMEA reader *********************************/ mIWaveGpsSource = std::make_shared( signalBufferPtr ); @@ -651,6 +628,42 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) /********************************External GPS Example NMEA reader end******************************/ #endif +#ifdef FWE_FEATURE_AAOS_VHAL + /********************************AAOS VHAL Example reader *********************************/ + mAaosVhalSource = std::make_shared( signalBufferPtr ); + bool aaosVhalInitSuccessful = false; + if ( config["staticConfig"].isMember( "aaosVhalExample" ) ) + { + FWE_LOG_TRACE( "Found 'aaosVhalExample' section in config file" ); + aaosVhalInitSuccessful = mAaosVhalSource->init( + mCANIDTranslator.getChannelNumericID( + config["staticConfig"]["aaosVhalExample"][AaosVhalSource::CAN_CHANNEL_NUMBER].asString() ), + stringToU32( config["staticConfig"]["aaosVhalExample"][AaosVhalSource::CAN_RAW_FRAME_ID].asString() ) ); + } + else + { + // If not config available default to this values + aaosVhalInitSuccessful = + mAaosVhalSource->init( mCANIDTranslator.getChannelNumericID( "AAOS-VHAL-CAN" ), 1 ); + } + if ( aaosVhalInitSuccessful ) + { + if ( !mCollectionSchemeManagerPtr->subscribeListener( + static_cast( mAaosVhalSource.get() ) ) ) + { + FWE_LOG_ERROR( "Failed to register the AaosVhalExample to the CollectionScheme Manager" ); + return false; + } + mAaosVhalSource->start(); + } + else + { + FWE_LOG_ERROR( "AaosVhalExample initialization failed" ); + return false; + } + /********************************AAOS VHAL Example reader end******************************/ +#endif + mPrintMetricsCyclicPeriodMs = config["staticConfig"]["internalParameters"]["metricsCyclicPrintIntervalMs"].asUInt(); // default to 0 } @@ -668,6 +681,12 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) bool IoTFleetWiseEngine::disconnect() { +#ifdef FWE_FEATURE_AAOS_VHAL + if ( mAaosVhalSource ) + { + mAaosVhalSource->stop(); + } +#endif #ifdef FWE_FEATURE_EXTERNAL_GPS if ( mExternalGpsSource ) { @@ -680,19 +699,6 @@ IoTFleetWiseEngine::disconnect() mIWaveGpsSource->stop(); } #endif -#ifdef FWE_FEATURE_CAMERA - if ( mDataOverDDSModule ) - { - if ( ( !mCollectionInspectionWorkerThread->unSubscribeFromEvents( - static_cast( mDataOverDDSModule.get() ) ) ) || - ( !mDataOverDDSModule->disconnect() ) ) - { - - FWE_LOG_ERROR( "Could not disconnect DDS Module" ); - return false; - } - } -#endif // FWE_FEATURE_CAMERA if ( mOBDOverCANModule ) { @@ -731,11 +737,18 @@ IoTFleetWiseEngine::disconnect() } } - if ( mAwsIotModule->isAlive() && ( !mAwsIotModule->disconnect() ) ) + if ( mConnectivityModule->isAlive() && ( !mConnectivityModule->disconnect() ) ) { - FWE_LOG_ERROR( "Could not disconnect the off-board connectivity" ); + FWE_LOG_ERROR( "Could not disconnect the offboard connectivity" ); return false; } + + if ( !mDataSenderManagerWorkerThread->stop() ) + { + FWE_LOG_ERROR( "Could not stop the DataSenderManager" ); + return false; + } + FWE_LOG_INFO( "Engine Disconnected" ); TraceModule::get().sectionEnd( TraceSection::FWE_SHUTDOWN ); TraceModule::get().print(); @@ -790,176 +803,34 @@ IoTFleetWiseEngine::isAlive() void IoTFleetWiseEngine::doWork( void *data ) { - IoTFleetWiseEngine *engine = static_cast( data ); - // Time in seconds - double timeTrigger = 0; - bool uploadedPersistedDataOnce = false; TraceModule::get().sectionEnd( TraceSection::FWE_STARTUP ); - engine->mRetrySendingPersistedDataTimer.reset(); - while ( !engine->shouldStop() ) { - // The interrupt arrives because of 2 reasons : - // 1- An event trigger( either cyclic or interrupt ) has arrived, for this, all the buffers of the consumers - // should be read and flushed. - // 2- A consumer has been either reconnected or disconnected on runtime. - // we should then either register or unregister for callbacks from this consumer. - // 3- A new collectionScheme is available - First old data is sent out then the new collectionScheme is - // applied to all consumers of the channel data - engine->mTimer.reset(); - uint32_t elapsedTimeUs = 0; - if ( !uploadedPersistedDataOnce ) - { - // Minimum delay one tenth of FAST_RETRY_UPLOAD_PERSISTED_INTERVAL_MS - uint64_t timeToWaitMs = - IoTFleetWiseEngine::FAST_RETRY_UPLOAD_PERSISTED_INTERVAL_MS - - std::min( static_cast( engine->mRetrySendingPersistedDataTimer.getElapsedMs().count() ), - IoTFleetWiseEngine::FAST_RETRY_UPLOAD_PERSISTED_INTERVAL_MS ); - timeTrigger = static_cast( std::max( - IoTFleetWiseEngine::FAST_RETRY_UPLOAD_PERSISTED_INTERVAL_MS / 10, timeToWaitMs ) ) / - 1000.0; - } - else if ( engine->mPersistencyUploadRetryIntervalMs > 0 ) - { - uint64_t timeToWaitMs = - engine->mPersistencyUploadRetryIntervalMs - - std::min( static_cast( engine->mRetrySendingPersistedDataTimer.getElapsedMs().count() ), - engine->mPersistencyUploadRetryIntervalMs ); - timeTrigger = static_cast( - std::max( IoTFleetWiseEngine::FAST_RETRY_UPLOAD_PERSISTED_INTERVAL_MS, timeToWaitMs ) ) / - 1000.0; - } - + uint64_t minTimeToWaitMs = UINT64_MAX; if ( engine->mPrintMetricsCyclicPeriodMs != 0 ) { uint64_t timeToWaitMs = engine->mPrintMetricsCyclicPeriodMs - std::min( static_cast( engine->mPrintMetricsCyclicTimer.getElapsedMs().count() ), engine->mPrintMetricsCyclicPeriodMs ); - timeTrigger = std::min( timeTrigger, static_cast( timeToWaitMs ) / 1000.0 ); + minTimeToWaitMs = std::min( minTimeToWaitMs, timeToWaitMs ); } - if ( timeTrigger > 0 ) + if ( minTimeToWaitMs < UINT64_MAX ) { - FWE_LOG_TRACE( "Waiting for :" + std::to_string( timeTrigger ) + " seconds. Persistency " + - std::to_string( engine->mPersistencyUploadRetryIntervalMs ) + " configured, " + - std::to_string( engine->mRetrySendingPersistedDataTimer.getElapsedMs().count() ) + - " timer. Cyclic Metrics Print:" + std::to_string( engine->mPrintMetricsCyclicPeriodMs ) + - " configured, " + + FWE_LOG_TRACE( "Waiting for: " + std::to_string( minTimeToWaitMs ) + " ms. Cyclic metrics print:" + + std::to_string( engine->mPrintMetricsCyclicPeriodMs ) + " configured, " + std::to_string( engine->mPrintMetricsCyclicTimer.getElapsedMs().count() ) + " timer." ); - engine->mWait.wait( static_cast( timeTrigger * 1000 ) ); + engine->mWait.wait( static_cast( minTimeToWaitMs ) ); } else { engine->mWait.wait( Platform::Linux::Signal::WaitWithPredicate ); - elapsedTimeUs += static_cast( engine->mTimer.getElapsedMs().count() ); - FWE_LOG_TRACE( "Event Arrived" ); - FWE_LOG_TRACE( "Time Elapsed waiting for the Event : " + std::to_string( elapsedTimeUs ) ); - } - - // Dequeues the collected data queue and sends the data to cloud - auto consumedElements = engine->mCollectedDataReadyToPublish->consume_all( - [&]( const TriggeredCollectionSchemeDataPtr triggeredCollectionSchemeDataPtr ) { - // Only used for trace logging - std::string firstSignalValues = "["; - uint32_t signalPrintCounter = 0; - std::string firstSignalTimestamp; - for ( auto &s : triggeredCollectionSchemeDataPtr->signals ) - { - if ( firstSignalTimestamp.empty() ) - { - firstSignalTimestamp = " first signal timestamp: " + std::to_string( s.receiveTime ); - } - signalPrintCounter++; - if ( signalPrintCounter > MAX_NUMBER_OF_SIGNAL_TO_TRACE_LOG ) - { - firstSignalValues += " ..."; - break; - } - auto signalValue = s.getValue(); - firstSignalValues += std::to_string( s.signalID ) + ":"; - switch ( signalValue.getType() ) - { - case SignalType::UINT8: - firstSignalValues += std::to_string( signalValue.value.uint8Val ) + ","; - break; - case SignalType::INT8: - firstSignalValues += std::to_string( signalValue.value.int8Val ) + ","; - break; - case SignalType::UINT16: - firstSignalValues += std::to_string( signalValue.value.uint16Val ) + ","; - break; - case SignalType::INT16: - firstSignalValues += std::to_string( signalValue.value.int16Val ) + ","; - break; - case SignalType::UINT32: - firstSignalValues += std::to_string( signalValue.value.uint32Val ) + ","; - break; - case SignalType::INT32: - firstSignalValues += std::to_string( signalValue.value.int32Val ) + ","; - break; - case SignalType::UINT64: - firstSignalValues += std::to_string( signalValue.value.uint64Val ) + ","; - break; - case SignalType::INT64: - firstSignalValues += std::to_string( signalValue.value.int64Val ) + ","; - break; - case SignalType::FLOAT: - firstSignalValues += std::to_string( signalValue.value.floatVal ) + ","; - break; - case SignalType::DOUBLE: - firstSignalValues += std::to_string( signalValue.value.doubleVal ) + ","; - break; - case SignalType::BOOLEAN: - firstSignalValues += std::to_string( static_cast( signalValue.value.boolVal ) ) + ","; - break; - default: - firstSignalValues += std::to_string( signalValue.value.doubleVal ) + ","; - break; - } - } - firstSignalValues += "]"; - // Avoid invoking Data Collection Sender if there is nothing to send. - if ( triggeredCollectionSchemeDataPtr->signals.empty() && - triggeredCollectionSchemeDataPtr->canFrames.empty() && - triggeredCollectionSchemeDataPtr->mDTCInfo.mDTCCodes.empty() && - ( !triggeredCollectionSchemeDataPtr->mGeohashInfo.hasItems() ) ) - { - FWE_LOG_INFO( - "The trigger for Campaign: " + triggeredCollectionSchemeDataPtr->metaData.collectionSchemeID + - " activated eventID: " + std::to_string( triggeredCollectionSchemeDataPtr->eventID ) + - " but no data is available to ingest" ); - } - else - { - FWE_LOG_INFO( "FWE data ready to send with eventID " + - std::to_string( triggeredCollectionSchemeDataPtr->eventID ) + " from " + - triggeredCollectionSchemeDataPtr->metaData.collectionSchemeID + - " Signals:" + std::to_string( triggeredCollectionSchemeDataPtr->signals.size() ) + - " " + firstSignalValues + firstSignalTimestamp + " raw CAN frames:" + - std::to_string( triggeredCollectionSchemeDataPtr->canFrames.size() ) + " DTCs:" + - std::to_string( triggeredCollectionSchemeDataPtr->mDTCInfo.mDTCCodes.size() ) + - " Geohash:" + triggeredCollectionSchemeDataPtr->mGeohashInfo.mGeohashString ); - engine->mDataCollectionSender->send( triggeredCollectionSchemeDataPtr ); - } - } ); - TraceModule::get().setVariable( TraceVariable::QUEUE_INSPECTION_TO_SENDER, consumedElements ); - - if ( ( ( engine->mPersistencyUploadRetryIntervalMs > 0 ) && - ( static_cast( engine->mRetrySendingPersistedDataTimer.getElapsedMs().count() ) >= - engine->mPersistencyUploadRetryIntervalMs ) ) || - ( ( !uploadedPersistedDataOnce ) && - ( static_cast( engine->mRetrySendingPersistedDataTimer.getElapsedMs().count() ) >= - IoTFleetWiseEngine::FAST_RETRY_UPLOAD_PERSISTED_INTERVAL_MS ) ) ) - { - engine->mRetrySendingPersistedDataTimer.reset(); - if ( engine->mAwsIotModule->isAlive() && engine->checkAndSendRetrievedData() ) - { - // Check if data was persisted, Retrieve all the data and send - uploadedPersistedDataOnce = true; - } + auto elapsedTimeMs = engine->mTimer.getElapsedMs().count(); + FWE_LOG_TRACE( "Event arrived. Time elapsed waiting for the event: " + std::to_string( elapsedTimeMs ) + + " ms" ); } if ( ( engine->mPrintMetricsCyclicPeriodMs > 0 ) && ( static_cast( engine->mPrintMetricsCyclicTimer.getElapsedMs().count() ) >= @@ -973,61 +844,6 @@ IoTFleetWiseEngine::doWork( void *data ) } } -void -IoTFleetWiseEngine::onDataReadyToPublish() -{ - mWait.notify(); -} - -bool -IoTFleetWiseEngine::checkAndSendRetrievedData() -{ - std::vector payloads; - - // Retrieve the data from persistency library - ErrorCode status = mPayloadManager->retrieveData( payloads ); - - if ( status == ErrorCode::SUCCESS ) - { - ConnectivityError res = ConnectivityError::Success; - FWE_LOG_TRACE( "Number of Payloads to transmit : " + std::to_string( payloads.size() ) ); - - for ( const auto &payload : payloads ) - { - // transmit the retrieved payload - res = mDataCollectionSender->transmit( payload ); - if ( res != ConnectivityError::Success ) - { - // Error occurred in the transmission - FWE_LOG_ERROR( "Payload transmission failed, will be retried on the next bootup" ); - break; - } - else - { - FWE_LOG_TRACE( "Payload has been successfully sent to the backend" ); - } - } - if ( res == ConnectivityError::Success ) - { - // All the stored data has been transmitted, erase the file contents - mPersistDecoderManifestCollectionSchemesAndData->erase( DataType::EDGE_TO_CLOUD_PAYLOAD ); - FWE_LOG_INFO( "All " + std::to_string( payloads.size() ) + " Payloads successfully sent to the backend" ); - return true; - } - return false; - } - else if ( status == ErrorCode::EMPTY ) - { - FWE_LOG_TRACE( "No Payloads to Retrieve" ); - return true; - } - else - { - FWE_LOG_ERROR( "Payload Retrieval Failed" ); - return false; - } -} - std::vector IoTFleetWiseEngine::getExternalOBDPIDsToRequest() { @@ -1081,21 +897,39 @@ IoTFleetWiseEngine::setExternalGpsLocation( double latitude, double longitude ) } #endif +#ifdef FWE_FEATURE_AAOS_VHAL +std::vector> +IoTFleetWiseEngine::getVehiclePropertyInfo() +{ + std::vector> propertyInfo; + if ( mAaosVhalSource != nullptr ) + { + propertyInfo = mAaosVhalSource->getVehiclePropertyInfo(); + } + return propertyInfo; +} +void +IoTFleetWiseEngine::setVehicleProperty( uint32_t signalId, double value ) +{ + if ( mAaosVhalSource == nullptr ) + { + return; + } + mAaosVhalSource->setVehicleProperty( signalId, value ); +} +#endif + std::string IoTFleetWiseEngine::getStatusSummary() { - if ( mAwsIotModule == nullptr || mCollectionSchemeManagerPtr == nullptr || mAwsIotChannelSendCanData == nullptr || - mOBDOverCANModule == nullptr -#ifdef FWE_FEATURE_EXTERNAL_GPS - || mExternalGpsSource == nullptr -#endif - ) + if ( mConnectivityModule == nullptr || mCollectionSchemeManagerPtr == nullptr || + mConnectivityChannelSendVehicleData == nullptr || mOBDOverCANModule == nullptr ) { return ""; } std::string status; - status += - std::string( "MQTT connection: " ) + ( mAwsIotModule->isAlive() ? "CONNECTED" : "NOT CONNECTED" ) + "\n\n"; + status += std::string( "MQTT connection: " ) + ( mConnectivityModule->isAlive() ? "CONNECTED" : "NOT CONNECTED" ) + + "\n\n"; status += "Campaign ARNs:\n"; auto collectionSchemeArns = mCollectionSchemeManagerPtr->getCollectionSchemeArns(); @@ -1112,7 +946,7 @@ IoTFleetWiseEngine::getStatusSummary() } status += "\n"; - status += "Payloads sent: " + std::to_string( mAwsIotChannelSendCanData->getPayloadCountSent() ) + "\n\n"; + status += "Payloads sent: " + std::to_string( mConnectivityChannelSendVehicleData->getPayloadCountSent() ) + "\n\n"; return status; } diff --git a/src/executionmanagement/src/android_shared_library.cpp b/src/executionmanagement/src/android_shared_library.cpp index b3d82843..333fdb7d 100644 --- a/src/executionmanagement/src/android_shared_library.cpp +++ b/src/executionmanagement/src/android_shared_library.cpp @@ -139,7 +139,7 @@ Java_com_aws_iotfleetwise_Fwe_run( JNIEnv *env, // Connect the Engine if ( mEngine->connect( config ) && mEngine->start() ) { - LOGI( std::string( " AWS IoT FleetWise Edge Service Started successfully " ) ); + LOGI( std::string( "Started successfully" ) ); } else { @@ -154,12 +154,12 @@ Java_com_aws_iotfleetwise_Fwe_run( JNIEnv *env, } if ( mEngine->stop() && mEngine->disconnect() ) { - LOGI( std::string( " AWS IoT FleetWise Edge Service Stopped successfully " ) ); + LOGI( std::string( "Stopped successfully" ) ); mEngine = nullptr; return EXIT_SUCCESS; } - LOGE( std::string( " AWS IoT FleetWise Edge Service Stopped with errors " ) ); + LOGE( std::string( "Stopped with errors" ) ); mEngine = nullptr; return EXIT_FAILURE; } @@ -199,6 +199,50 @@ Java_com_aws_iotfleetwise_Fwe_setLocation( JNIEnv *env, jobject me, jdouble lati } #endif +#ifdef FWE_FEATURE_AAOS_VHAL +extern "C" JNIEXPORT jobjectArray JNICALL +Java_com_aws_iotfleetwise_Fwe_getVehiclePropertyInfo( JNIEnv *env, jobject me ) +{ + static_cast( me ); + jclass cls = env->FindClass( "[I" ); + jintArray iniVal = env->NewIntArray( 4 ); + jobjectArray outer; + if ( mEngine == nullptr ) + { + outer = env->NewObjectArray( 0, cls, iniVal ); + } + else + { + auto propertyInfo = mEngine->getVehiclePropertyInfo(); + outer = env->NewObjectArray( static_cast( propertyInfo.size() ), cls, iniVal ); + for ( size_t i = 0; i < propertyInfo.size(); i++ ) + { + jintArray inner = env->NewIntArray( 4 ); + for ( size_t j = 0; j < 4; j++ ) + { + jint val = static_cast( propertyInfo[i][j] ); + env->SetIntArrayRegion( inner, static_cast( j ), 1, &val ); + } + env->SetObjectArrayElement( outer, static_cast( i ), inner ); + env->DeleteLocalRef( inner ); + } + } + return outer; +} + +extern "C" JNIEXPORT void JNICALL +Java_com_aws_iotfleetwise_Fwe_setVehicleProperty( JNIEnv *env, jobject me, jint signalId, jdouble value ) +{ + static_cast( env ); + static_cast( me ); + if ( mEngine == nullptr ) + { + return; + } + mEngine->setVehicleProperty( static_cast( signalId ), value ); +} +#endif + extern "C" JNIEXPORT jintArray JNICALL Java_com_aws_iotfleetwise_Fwe_getObdPidsToRequest( JNIEnv *env, jobject me ) { diff --git a/src/executionmanagement/src/main.cpp b/src/executionmanagement/src/main.cpp index 9729f6f5..6eae0af7 100644 --- a/src/executionmanagement/src/main.cpp +++ b/src/executionmanagement/src/main.cpp @@ -62,11 +62,11 @@ signalToExitCode( int signalNumber ) switch ( signalNumber ) { case SIGUSR1: - std::cout << "Fatal error, stopping AWS IoT FleetWise Edge Service " << std::endl; + std::cout << "Fatal error, stopping" << std::endl; return -1; case SIGINT: case SIGTERM: - std::cout << "Stopping AWS IoT FleetWise Edge Service " << std::endl; + std::cout << "Stopping" << std::endl; return 0; default: std::cout << "Received unexpected signal " << signalNumber << std::endl; @@ -82,7 +82,7 @@ main( int argc, char *argv[] ) printVersion(); if ( argc < 2 ) { - std::cout << "error: no config file provided" << std::endl; + std::cout << "Error: no config file provided" << std::endl; return EXIT_FAILURE; } @@ -93,7 +93,7 @@ main( int argc, char *argv[] ) Json::Value config; if ( !IoTFleetWiseConfig::read( configFilename, config ) ) { - std::cout << "AWS IoT FleetWise Edge Service failed to read config file: " + configFilename << std::endl; + std::cout << "Failed to read config file: " + configFilename << std::endl; return EXIT_FAILURE; } // Set system wide log level @@ -103,7 +103,7 @@ main( int argc, char *argv[] ) // Connect the Engine if ( engine.connect( config ) && engine.start() ) { - std::cout << "AWS IoT FleetWise Edge Service Started successfully" << std::endl; + std::cout << "Started successfully" << std::endl; } else { @@ -117,11 +117,11 @@ main( int argc, char *argv[] ) int exitCode = signalToExitCode( mSignal ); if ( engine.stop() && engine.disconnect() ) { - std::cout << "AWS IoT FleetWise Edge Service Stopped successfully" << std::endl; + std::cout << "Stopped successfully" << std::endl; return exitCode; } - std::cout << "AWS IoT FleetWise Edge Service Stopped with errors" << std::endl; + std::cout << "Stopped with errors" << std::endl; return EXIT_FAILURE; } catch ( const std::exception &e ) diff --git a/src/executionmanagement/test/IoTFleetWiseEngineTest.cpp b/src/executionmanagement/test/IoTFleetWiseEngineTest.cpp index c72f0d67..e633ca87 100644 --- a/src/executionmanagement/test/IoTFleetWiseEngineTest.cpp +++ b/src/executionmanagement/test/IoTFleetWiseEngineTest.cpp @@ -88,8 +88,8 @@ TEST_F( IoTFleetWiseEngineTest, CheckPublishDataQueue ) // Push to the publish data queue std::shared_ptr collectedDataPtr = std::make_shared(); - collectedDataPtr->metaData.collectionSchemeID = "123"; - collectedDataPtr->metaData.decoderID = "456"; + collectedDataPtr->metadata.collectionSchemeID = "123"; + collectedDataPtr->metadata.decoderID = "456"; collectedDataPtr->triggerTime = 800; { CollectedSignal collectedSignalMsg1( 120 /*signalId*/, 800 /*receiveTime*/, 77.88 /*value*/ ); @@ -128,26 +128,3 @@ TEST_F( IoTFleetWiseEngineTest, InitAndFailToStartCorruptConfig ) // Connect should fail as the Config file has a non complete Bus definition ASSERT_FALSE( engine.connect( config ) ); } - -TEST_F( IoTFleetWiseEngineTest, TestDataRetrieval ) -{ - Json::Value config; - ASSERT_TRUE( IoTFleetWiseConfig::read( "em-example-config.json", config ) ); - IoTFleetWiseEngine engine; - ASSERT_TRUE( engine.connect( config ) ); - // Write some data to the file - std::string testString = "!24$iklmnopabcdefjh!24$3@qrstuvwxyz"; - - ASSERT_EQ( engine.mPersistDecoderManifestCollectionSchemesAndData->write( - reinterpret_cast( testString.c_str() ), - testString.size(), - DataType::EDGE_TO_CLOUD_PAYLOAD ), - ErrorCode::SUCCESS ); - - ASSERT_TRUE( engine.start() ); - ASSERT_TRUE( engine.isAlive() ); - ASSERT_EQ( engine.mPersistDecoderManifestCollectionSchemesAndData->erase( DataType::EDGE_TO_CLOUD_PAYLOAD ), - ErrorCode::SUCCESS ); - ASSERT_TRUE( engine.disconnect() ); - ASSERT_TRUE( engine.stop() ); -} diff --git a/src/executionmanagement/test/em-example-config-inline-creds.json b/src/executionmanagement/test/em-example-config-inline-creds.json index 96ec0559..08436014 100644 --- a/src/executionmanagement/test/em-example-config-inline-creds.json +++ b/src/executionmanagement/test/em-example-config-inline-creds.json @@ -64,19 +64,5 @@ "longitudeStartBit": "32", "latitudeStartBit": "0" } - }, - "dds-nodes-configuration": [ - { - "dds-domain-id": 0, - "upstream-dds-topic-name": "testUpstreamTopic", - "downstream-dds-topic-name": "testDownstreamTopic", - "dds-topics-qos": "TOPIC_QOS_DEFAULT", - "dds-transport-protocol": "SHM", - "dds-device-id": 1, - "dds-device-type": "CAMERA", - "dds-tmp-cache-location": "/tmp/", - "dds-reader-name": "FWE-Reader", - "dds-writer-name": "FWE-Writer" - } - ] + } } diff --git a/src/executionmanagement/test/em-example-config.json b/src/executionmanagement/test/em-example-config.json index 78fbe336..de443ec6 100644 --- a/src/executionmanagement/test/em-example-config.json +++ b/src/executionmanagement/test/em-example-config.json @@ -73,19 +73,5 @@ "loggingUploadMaxWaitBeforeUploadMs": 60000, "profilerPrefix": "TestVehicle1" } - }, - "dds-nodes-configuration": [ - { - "dds-domain-id": 0, - "upstream-dds-topic-name": "testUpstreamTopic", - "downstream-dds-topic-name": "testDownstreamTopic", - "dds-topics-qos": "TOPIC_QOS_DEFAULT", - "dds-transport-protocol": "SHM", - "dds-device-id": 1, - "dds-device-type": "CAMERA", - "dds-tmp-cache-location": "/tmp/", - "dds-reader-name": "FWE-Reader", - "dds-writer-name": "FWE-Writer" - } - ] + } } diff --git a/src/offboardconnectivity/api/include/IConnectionTypes.h b/src/offboardconnectivity/api/include/IConnectionTypes.h index 5b158f59..96684281 100644 --- a/src/offboardconnectivity/api/include/IConnectionTypes.h +++ b/src/offboardconnectivity/api/include/IConnectionTypes.h @@ -18,11 +18,12 @@ namespace OffboardConnectivity */ enum class ConnectivityError { - Success = 0, /**< everything OK, still no guarantee that data was transmitted correctly */ - NoConnection, /**< currently no connection, the Connectivity module will try to reestablish it automatically */ - QuotaReached, /**< quota reached for example outgoing queue full so please try again after few milliseconds */ - NotConfigured, /**< the object used was not configured correctly */ - WrongInputData, /**< Invalid input data was provided */ + Success = 0, /**< everything OK, still no guarantee that data was transmitted correctly */ + NoConnection, /**< currently no connection, the Connectivity module will try to reestablish it automatically */ + QuotaReached, /**< quota reached for example outgoing queue full so please try again after few milliseconds */ + NotConfigured, /**< the object used was not configured correctly */ + WrongInputData, /**< invalid input data was provided */ + TypeNotSupported, /**< requested upload type is not supported by the sender */ }; } // namespace OffboardConnectivity diff --git a/src/offboardconnectivity/api/include/IConnectivityChannel.h b/src/offboardconnectivity/api/include/IConnectivityChannel.h new file mode 100644 index 00000000..37d15560 --- /dev/null +++ b/src/offboardconnectivity/api/include/IConnectivityChannel.h @@ -0,0 +1,44 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +// Includes +#include "IReceiver.h" +#include "ISender.h" +#include "PayloadManager.h" +#include +#include +#include +#include +#include + +namespace Aws +{ +namespace IoTFleetWise +{ + +namespace OffboardConnectivity +{ + +class IConnectivityChannel : public Aws::IoTFleetWise::OffboardConnectivity::ISender, + public Aws::IoTFleetWise::OffboardConnectivity::IReceiver +{ +public: + ~IConnectivityChannel() override = default; + + /** + * @brief the topic must be set always before using any functionality of this class + * @param topicNameRef MQTT topic that will be used for sending or receiving data + * if subscribe was called + * @param subscribeAsynchronously if true the channel will be subscribed to the topic asynchronously so that the + * channel can receive data + * + */ + virtual void setTopic( const std::string &topicNameRef, bool subscribeAsynchronously = false ) = 0; + + virtual unsigned getPayloadCountSent() const = 0; +}; +} // namespace OffboardConnectivity +} // namespace IoTFleetWise +} // namespace Aws diff --git a/src/offboardconnectivity/api/include/IConnectivityModule.h b/src/offboardconnectivity/api/include/IConnectivityModule.h new file mode 100644 index 00000000..4a431d3f --- /dev/null +++ b/src/offboardconnectivity/api/include/IConnectivityModule.h @@ -0,0 +1,39 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "IConnectivityChannel.h" +#include "PayloadManager.h" +#include +#include +#include +#include + +namespace Aws +{ +namespace IoTFleetWise +{ + +namespace OffboardConnectivity +{ +using Aws::IoTFleetWise::OffboardConnectivityAwsIot::PayloadManager; + +class IConnectivityModule +{ +public: + virtual ~IConnectivityModule() = default; + + virtual bool isAlive() const = 0; + + virtual std::shared_ptr createNewChannel( + const std::shared_ptr &payloadManager ) = 0; + + virtual bool disconnect() = 0; + + virtual bool connect() = 0; +}; + +} // namespace OffboardConnectivity +} // namespace IoTFleetWise +} // namespace Aws diff --git a/src/offboardconnectivity/api/include/ISender.h b/src/offboardconnectivity/api/include/ISender.h index b379b600..ad0f9083 100644 --- a/src/offboardconnectivity/api/include/ISender.h +++ b/src/offboardconnectivity/api/include/ISender.h @@ -21,6 +21,8 @@ struct CollectionSchemeParams bool persist{ false }; // specifies if data needs to be persisted in case of connection loss bool compression{ false }; // specifies if data needs to be compressed for cloud uint32_t priority{ 0 }; // collectionScheme priority specified by the cloud + uint64_t triggerTime{ 0 }; // timestamp of event ocurred + uint32_t eventID{ 0 }; // event id }; /** @@ -70,6 +72,23 @@ class ISender const std::uint8_t *buf, size_t size, struct CollectionSchemeParams collectionSchemeParams = CollectionSchemeParams() ) = 0; + + /** + * @brief called to send data from file to the cloud + * + * The function will return after async upload was successfully initiated or error occurred. + * + * @param filePath path to the file to upload + * @param size size of the payload + * @param collectionSchemeParams object containing collectionScheme related metadata for data persistency and + * transmission + * + * @return SUCCESS if connection is established. + */ + virtual ConnectivityError sendFile( + const std::string &filePath, + size_t size, + struct CollectionSchemeParams collectionSchemeParams = CollectionSchemeParams() ) = 0; }; } // namespace OffboardConnectivity } // namespace IoTFleetWise diff --git a/src/offboardconnectivity/implementation/aws/bootstrap/include/AwsSDKMemoryManager.h b/src/offboardconnectivity/implementation/aws/bootstrap/include/AwsSDKMemoryManager.h index 74050994..b68f20f0 100644 --- a/src/offboardconnectivity/implementation/aws/bootstrap/include/AwsSDKMemoryManager.h +++ b/src/offboardconnectivity/implementation/aws/bootstrap/include/AwsSDKMemoryManager.h @@ -33,13 +33,28 @@ class AwsSDKMemoryManager : public Aws::Utils::Memory::MemorySystemInterface void *AllocateMemory( std::size_t blockSize, std::size_t alignment, const char *allocationTag = nullptr ) override; void FreeMemory( void *memoryPtr ) override; + /** + * @brief Set the Limit of maximal memory usage + * + * @param size + * @return true if setting succeeds + * @return false if setting fails + */ + bool setLimit( std::size_t size ); + + /** + * @brief Get the Limit object + * + * @return std::size_t + */ + std::size_t getLimit() const; /** * @brief Reserve a chunk of memory for usage later * - * @return std::size_t Memory size currently in use plus reserved + * @return true if successfully reserved, false if the allocation exceeds the limit */ - std::size_t reserveMemory( std::size_t bytes ); + bool reserveMemory( std::size_t bytes ); /** * @brief Release the memory reservation. @@ -59,6 +74,10 @@ class AwsSDKMemoryManager : public Aws::Utils::Memory::MemorySystemInterface * */ std::atomic mMemoryUsedAndReserved{ 0 }; + + static constexpr std::size_t MAXIMUM_AWS_SDK_HEAP_MEMORY_BYTES = 10000000; + + size_t mMaximumAwsSDKMemorySize = MAXIMUM_AWS_SDK_HEAP_MEMORY_BYTES; }; } // namespace OffboardConnectivityAwsIot } // namespace IoTFleetWise diff --git a/src/offboardconnectivity/implementation/aws/bootstrap/src/AwsSDKMemoryManager.cpp b/src/offboardconnectivity/implementation/aws/bootstrap/src/AwsSDKMemoryManager.cpp index 7012dd73..74d18b47 100644 --- a/src/offboardconnectivity/implementation/aws/bootstrap/src/AwsSDKMemoryManager.cpp +++ b/src/offboardconnectivity/implementation/aws/bootstrap/src/AwsSDKMemoryManager.cpp @@ -94,11 +94,32 @@ AwsSDKMemoryManager::FreeMemory( void *memoryPtr ) } std::size_t +AwsSDKMemoryManager::getLimit() const +{ + return mMaximumAwsSDKMemorySize; +} + +bool +AwsSDKMemoryManager::setLimit( size_t size ) +{ + if ( size == 0U ) + { + return false; + } + mMaximumAwsSDKMemorySize = size; + return true; +} + +bool AwsSDKMemoryManager::reserveMemory( std::size_t bytes ) { + if ( ( mMemoryUsedAndReserved + bytes + ALIGN_OFFSET ) > mMaximumAwsSDKMemorySize ) + { + return false; + } mMemoryUsedAndReserved += ( bytes + ALIGN_OFFSET ); TraceModule::get().setVariable( TraceVariable::MQTT_HEAP_USAGE, mMemoryUsedAndReserved ); - return mMemoryUsedAndReserved; + return true; } std::size_t diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/CMakeLists.txt b/src/offboardconnectivity/implementation/aws/iotcpp/CMakeLists.txt index 1d0c5fd1..5f873682 100644 --- a/src/offboardconnectivity/implementation/aws/iotcpp/CMakeLists.txt +++ b/src/offboardconnectivity/implementation/aws/iotcpp/CMakeLists.txt @@ -4,10 +4,19 @@ set(libraryTargetName iotfleetwise.offboardconnectivityawsiot) # The alias name is what other targets will use as a dependency set(libraryAliasName IoTFleetWise::OffboardConnectivityAwsIot) +if(FWE_FEATURE_GREENGRASSV2) + set(EXTRA_SOURCE_FILES + ${EXTRA_SOURCE_FILES} + src/AwsGGConnectivityModule.cpp + src/AwsGGChannel.cpp) + find_package(GreengrassIpc-cpp REQUIRED) + set(GG_IPC_LIB_NAME AWS::GreengrassIpc-cpp) +endif() set(librarySrc src/AwsIotChannel.cpp src/AwsIotConnectivityModule.cpp + ${EXTRA_SOURCE_FILES} src/RetryThread.cpp src/PayloadManager.cpp src/RemoteProfiler.cpp) @@ -20,6 +29,8 @@ add_library( find_path(JSONCPP_INCLUDE_DIR "json/json.h" PATH_SUFFIXES "jsoncpp") find_library(JSONCPP_LIBRARY NAMES jsoncpp) +find_package(aws-crt-cpp REQUIRED) + set(OLD_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS}) set(BUILD_SHARED_LIBS ${FWE_AWS_SDK_SHARED_LIBS}) @@ -36,6 +47,8 @@ target_link_libraries( IoTFleetWise::OffboardConnectivityAwsBootstrap IoTFleetWise::OffboardConnectivity IoTFleetWise::Platform::Linux + AWS::aws-crt-cpp + ${GG_IPC_LIB_NAME} ${AWSSDK_LINK_LIBRARIES} ${SNAPPY_LIBRARIES} ${JSONCPP_LIBRARY} @@ -74,6 +87,8 @@ if(${BUILD_TESTING}) IoTFleetWise::OffboardConnectivityAwsBootstrap IoTFleetWise::OffboardConnectivity IoTFleetWise::TestingSupport + AWS::aws-crt-cpp + ${GG_IPC_LIB_NAME} ${GMOCK_MAIN_LIBRARY} ${GMOCK_LIB} ${SNAPPY_LIBRARIES} diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsGGChannel.h b/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsGGChannel.h new file mode 100644 index 00000000..3fe8cee9 --- /dev/null +++ b/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsGGChannel.h @@ -0,0 +1,175 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +// Includes +#include "IConnectivityChannel.h" +#include "IConnectivityModule.h" +#include "PayloadManager.h" +#include +#include +#include +#include +#include +#include + +namespace Aws +{ +namespace IoTFleetWise +{ +/** + * @brief Namespace depending on Aws Iot SDK headers + */ +namespace OffboardConnectivityAwsIot +{ + +using namespace Aws::Greengrass; +using namespace Aws::IoTFleetWise::Platform::Linux; + +using Aws::IoTFleetWise::OffboardConnectivity::CollectionSchemeParams; +using Aws::IoTFleetWise::OffboardConnectivity::ConnectivityError; + +using SubscribeCallback = std::function; + +class SubscribeStreamHandler : public SubscribeToIoTCoreStreamHandler +{ +public: + SubscribeStreamHandler( SubscribeCallback callback ) + : mCallback( callback ) + { + } + +private: + // coverity[autosar_cpp14_a0_1_3_violation] false positive - function overrides sdk's virtual function. + void + OnStreamEvent( IoTCoreMessage *response ) override + { + auto message = response->GetMessage(); + + if ( message.has_value() && message.value().GetPayload().has_value() ) + { + auto payloadBytes = message.value().GetPayload().value(); + std::string payloadString( payloadBytes.begin(), payloadBytes.end() ); + mCallback( payloadBytes.data(), payloadBytes.size() ); + } + }; + SubscribeCallback mCallback; +}; + +/** + * @brief a channel that can be used as IReceiver or ISender or both + * + * If the Channel should be used for receiving data subscribe must be called. + * setTopic must be called always. There can be multiple AwsGGChannels + * from one AwsGGConnectivityModule. The channel of the connectivityModule passed in the + * constructor must be established before anything meaningful can be done with this class + * @see AwsGGConnectivityModule + */ +class AwsGGChannel : public IConnectivityChannel +{ +public: + AwsGGChannel( IConnectivityModule *connectivityModule, + std::shared_ptr payloadManager, + std::shared_ptr &ggConnection ); + ~AwsGGChannel() override; + + AwsGGChannel( const AwsGGChannel & ) = delete; + AwsGGChannel &operator=( const AwsGGChannel & ) = delete; + AwsGGChannel( AwsGGChannel && ) = delete; + AwsGGChannel &operator=( AwsGGChannel && ) = delete; + + /** + * @brief the topic must be set always before using any functionality of this class + * @param topicNameRef MQTT topic that will be used for sending or receiving data + * if subscribe was called + * @param subscribeAsynchronously if true the channel will be subscribed to the topic asynchronously so that the + * channel can receive data + * + */ + void setTopic( const std::string &topicNameRef, bool subscribeAsynchronously = false ) override; + + /** + * @brief Subscribe to the MQTT topic from setTopic. Necessary if data is received on the topic + * + * This function blocks until subscribe succeeded or failed and should be done in the setup form + * the bootstrap thread. The connection of the connectivityModule passed in the constructor + * must be established otherwise subscribe will fail. No retries are done to try to subscribe + * this needs to be done in the bootstrap during the setup. + * @return Success if subscribe finished correctly + */ + ConnectivityError subscribe(); + + /** + * @brief After unsubscribe no data will be received over the channel + * @return True for success + */ + bool unsubscribe(); + + bool isAlive() override; + + size_t getMaxSendSize() const override; + + ConnectivityError sendBuffer( + const std::uint8_t *buf, + size_t size, + struct CollectionSchemeParams collectionSchemeParams = CollectionSchemeParams() ) override; + + ConnectivityError sendFile( + const std::string &filePath, + size_t size, + struct CollectionSchemeParams collectionSchemeParams = CollectionSchemeParams() ) override; + + void + invalidateConnection() + { + std::lock_guard connectivityLock( mConnectivityMutex ); + std::lock_guard connectivityLambdaLock( mConnectivityLambdaMutex ); + mConnectivityModule = nullptr; + } + + bool + shouldSubscribeAsynchronously() const + { + return mSubscribeAsynchronously; + } + + /** + * @brief Returns the number of payloads successfully passed to the AWS IoT SDK + * @return Number of payloads + */ + unsigned + getPayloadCountSent() const override + { + return mPayloadCountSent; + } + +private: + bool isAliveNotThreadSafe(); + bool + isTopicValid() + { + return !mTopicName.empty(); + }; + /** See "Message size" : "The payload for every publish request can be no larger + * than 128 KB. AWS IoT Core rejects publish and connect requests larger than this size." + * https://docs.aws.amazon.com/general/latest/gr/iot-core.html#limits_iot + */ + static const size_t AWS_IOT_MAX_MESSAGE_SIZE = 131072; // = 128 KiB + IConnectivityModule *mConnectivityModule; + + std::mutex mConnectivityMutex; + std::mutex mConnectivityLambdaMutex; + std::shared_ptr mPayloadManager; + std::shared_ptr &mConnection; + std::atomic mSubscribed; + bool mSubscribeAsynchronously; + std::atomic mPayloadCountSent{}; + + std::string mTopicName; + std::shared_ptr mSubscribeStreamHandler; + std::shared_ptr mSubscribeOperation; +}; +} // namespace OffboardConnectivityAwsIot +} // namespace IoTFleetWise +} // namespace Aws diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsGGConnectivityModule.h b/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsGGConnectivityModule.h new file mode 100644 index 00000000..2f176687 --- /dev/null +++ b/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsGGConnectivityModule.h @@ -0,0 +1,96 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +// Includes +#include "AwsGGChannel.h" +#include "Listener.h" +#include "LoggingModule.h" +#include "TraceModule.h" +#include +#include +#include +#include +#include + +namespace Aws +{ +namespace IoTFleetWise +{ +namespace OffboardConnectivityAwsIot +{ +using namespace Aws::IoTFleetWise::Platform; +using namespace Aws::IoTFleetWise::Platform::Linux; +using namespace Aws::Greengrass; + +class IpcLifecycleHandler : public ConnectionLifecycleHandler +{ + + // coverity[autosar_cpp14_a0_1_3_violation] false positive - function overrides sdk's virtual function. + void + OnConnectCallback() override + { + FWE_LOG_INFO( "Connected to Greengrass Core." ); + } + + // coverity[autosar_cpp14_a0_1_3_violation] false positive - function overrides sdk's virtual function. + void + OnDisconnectCallback( RpcError status ) override + { + if ( !status ) + { + FWE_LOG_ERROR( "Disconnected from Greengrass Core with error: " + std::to_string( status ) ); + } + } + + // coverity[autosar_cpp14_a0_1_3_violation] false positive - function overrides sdk's virtual function. + bool + OnErrorCallback( RpcError status ) override + { + FWE_LOG_ERROR( "Processing messages from the Greengrass Core resulted in error:" + std::to_string( status ) ); + return true; + } +}; + +/** + * @brief bootstrap of the Aws GG connectivity module. Only one object of this should normally exist + * + */ +class AwsGGConnectivityModule : public IConnectivityModule +{ +public: + AwsGGConnectivityModule( Aws::Crt::Io::ClientBootstrap *clientBootstrap ); + ~AwsGGConnectivityModule() override; + + bool connect() override; + + bool disconnect() override; + + bool + isAlive() const override + { + return mConnected; + }; + + /** + * @brief create a new channel sharing the connection of this module + * This call needs to be done before calling connect for all asynchronous subscribe channel + * @param payloadManager the payload manager used by the new channel, + * @return a pointer to the newly created channel. A reference to the newly created channel is also hold inside this + * module. + */ + std::shared_ptr createNewChannel( + const std::shared_ptr &payloadManager ) override; + +private: + std::atomic mConnected; + std::atomic mConnectionEstablished; + std::vector> mChannels; + std::unique_ptr mLifecycleHandler; + std::shared_ptr mConnection; + Aws::Crt::Io::ClientBootstrap *mClientBootstrap; +}; +} // namespace OffboardConnectivityAwsIot +} // namespace IoTFleetWise +} // namespace Aws diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsIotChannel.h b/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsIotChannel.h index f588ca3e..5bcc1b4f 100644 --- a/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsIotChannel.h +++ b/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsIotChannel.h @@ -4,12 +4,15 @@ #pragma once // Includes +#include "IConnectivityChannel.h" +#include "IConnectivityModule.h" #include "IReceiver.h" #include "ISender.h" #include "PayloadManager.h" #include #include #include +#include #include #include #include @@ -23,31 +26,7 @@ namespace IoTFleetWise */ namespace OffboardConnectivityAwsIot { - -class IConnectivityModule -{ -public: - virtual std::shared_ptr getConnection() const = 0; - - /** - * @brief Increases atomically the memory usage - * @param bytes number of bytes to reserve - * @return number of bytes after the increase. - */ - virtual std::size_t reserveMemoryUsage( std::size_t bytes ) = 0; - - /** - * @brief Decreases atomically the memory usage - * @param bytes number of bytes to release - * @return number of bytes after the decrease. - */ - virtual std::size_t releaseMemoryUsage( std::size_t bytes ) = 0; - - virtual bool isAlive() const = 0; - - virtual ~IConnectivityModule() = default; -}; - +using namespace Aws::IoTFleetWise::Platform::Linux; using Aws::IoTFleetWise::OffboardConnectivity::CollectionSchemeParams; using Aws::IoTFleetWise::OffboardConnectivity::ConnectivityError; @@ -62,17 +41,13 @@ using Aws::IoTFleetWise::OffboardConnectivity::ConnectivityError; */ // coverity[cert_dcl60_cpp_violation] false positive - class only defined once // coverity[autosar_cpp14_m3_2_2_violation] false positive - class only defined once -class AwsIotChannel : public Aws::IoTFleetWise::OffboardConnectivity::ISender, - public Aws::IoTFleetWise::OffboardConnectivity::IReceiver +// coverity[misra_cpp_2008_rule_3_2_2_violation] false positive - class only defined once +class AwsIotChannel : public IConnectivityChannel { public: - static constexpr std::size_t MAXIMUM_IOT_SDK_HEAP_MEMORY_BYTES = - 10000000; /**< After the SDK allocated more than the here defined 10MB we will stop pushing data to the SDK to - avoid increasing heap consumption */ - AwsIotChannel( IConnectivityModule *connectivityModule, std::shared_ptr payloadManager, - std::size_t maximumIotSDKHeapMemoryBytes = MAXIMUM_IOT_SDK_HEAP_MEMORY_BYTES ); + std::shared_ptr &mqttConnection ); ~AwsIotChannel() override; AwsIotChannel( const AwsIotChannel & ) = delete; @@ -88,7 +63,7 @@ class AwsIotChannel : public Aws::IoTFleetWise::OffboardConnectivity::ISender, * channel can receive data * */ - void setTopic( const std::string &topicNameRef, bool subscribeAsynchronously = false ); + void setTopic( const std::string &topicNameRef, bool subscribeAsynchronously = false ) override; /** * @brief Subscribe to the MQTT topic from setTopic. Necessary if data is received on the topic @@ -96,13 +71,14 @@ class AwsIotChannel : public Aws::IoTFleetWise::OffboardConnectivity::ISender, * This function blocks until subscribe succeeded or failed and should be done in the setup form * the bootstrap thread. The connection of the connectivityModule passed in the constructor * must be established otherwise subscribe will fail. No retries are done to try to subscribe - * this needs to be done in the boostrap during the setup. + * this needs to be done in the bootstrap during the setup. * @return Success if subscribe finished correctly */ ConnectivityError subscribe(); /** * @brief After unsubscribe no data will be received over the channel + * @return True for success */ bool unsubscribe(); @@ -115,11 +91,10 @@ class AwsIotChannel : public Aws::IoTFleetWise::OffboardConnectivity::ISender, size_t size, struct CollectionSchemeParams collectionSchemeParams = CollectionSchemeParams() ) override; - bool - isTopicValid() - { - return !mTopicName.empty(); - } + ConnectivityError sendFile( + const std::string &filePath, + size_t size, + struct CollectionSchemeParams collectionSchemeParams = CollectionSchemeParams() ) override; void invalidateConnection() @@ -127,38 +102,42 @@ class AwsIotChannel : public Aws::IoTFleetWise::OffboardConnectivity::ISender, std::lock_guard connectivityLock( mConnectivityMutex ); std::lock_guard connectivityLambdaLock( mConnectivityLambdaMutex ); mConnectivityModule = nullptr; - } + }; bool shouldSubscribeAsynchronously() const { return mSubscribeAsynchronously; - } + }; /** * @brief Returns the number of payloads successfully passed to the AWS IoT SDK * @return Number of payloads */ unsigned - getPayloadCountSent() const + getPayloadCountSent() const override { return mPayloadCountSent; } private: bool isAliveNotThreadSafe(); + bool + isTopicValid() + { + return !mTopicName.empty(); + }; /** See "Message size" : "The payload for every publish request can be no larger * than 128 KB. AWS IoT Core rejects publish and connect requests larger than this size." * https://docs.aws.amazon.com/general/latest/gr/iot-core.html#limits_iot */ static const size_t AWS_IOT_MAX_MESSAGE_SIZE = 131072; // = 128 KiB - std::size_t mMaximumIotSDKHeapMemoryBytes; /**< If the iot device sdk heap memory usage from all channels exceeds - this threshold this channel stops publishing data*/ IConnectivityModule *mConnectivityModule; + std::shared_ptr mPayloadManager; + std::shared_ptr &mConnection; std::mutex mConnectivityMutex; std::mutex mConnectivityLambdaMutex; - std::shared_ptr mPayloadManager; std::string mTopicName; std::atomic mSubscribed; std::atomic mPayloadCountSent{}; diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsIotConnectivityModule.h b/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsIotConnectivityModule.h index 35ace682..ef83ec69 100644 --- a/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsIotConnectivityModule.h +++ b/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsIotConnectivityModule.h @@ -3,19 +3,15 @@ #pragma once -// Includes #include "AwsIotChannel.h" #include "Listener.h" #include "RetryThread.h" #include +#include #include #include #include -#include - -#include - namespace Aws { namespace IoTFleetWise @@ -29,13 +25,39 @@ using namespace Aws::IoTFleetWise::Platform::Linux; * */ // coverity[cert_dcl60_cpp_violation] false positive - class only defined once // coverity[autosar_cpp14_m3_2_2_violation] false positive - class only defined once +// coverity[misra_cpp_2008_rule_3_2_2_violation] false positive - class only defined once class AwsIotConnectivityModule : public IRetryable, public IConnectivityModule { public: constexpr static uint32_t RETRY_FIRST_CONNECTION_START_BACKOFF_MS = 1000; // start retry after one second constexpr static uint32_t RETRY_FIRST_CONNECTION_MAX_BACKOFF_MS = 256000; // retry at least every 256 seconds - AwsIotConnectivityModule(); + /** + * @brief Construct a new Aws Iot Connectivity Module object + * + * @param privateKey The private key .pem file provided during setup of the AWS + * Iot Thing. + * @param certificate The certificate .crt.txt file provided during setup + * of the AWS Iot Thing. + * @param rootCA The Root CA for the certificate + * @param endpointUrl the endpoint URL normally in the format like + * "[YOUR-THING]-ats.iot.us-west-2.amazonaws.com" + * @param clientId the id that is used to identify this connection instance + * @param clientBootstrap pointer to AWS client bootstrap. Note AwsBootstrap is responsible for the client + * bootstrap lifecycle + * @param asynchronous + * If asynchronous is false this function blocks until it succeeded or failed. Also if asynchronous is false + * no retries are done to try to reconnected if initial connection could not be established. + * If asynchronous is true this function returns without being connected and starts a background + * thread that retries until it was successfully connected or aborts for some reason. + */ + AwsIotConnectivityModule( std::string privateKey, + std::string certificate, + std::string rootCA, + std::string endpointUrl, + std::string clientId, + Aws::Crt::Io::ClientBootstrap *clientBootstrap, + bool asynchronous = false ); ~AwsIotConnectivityModule() override; AwsIotConnectivityModule( const AwsIotConnectivityModule & ) = delete; @@ -48,34 +70,13 @@ class AwsIotConnectivityModule : public IRetryable, public IConnectivityModule * * A thing created over the Aws CLI under IoT Core normally has a security and a collectionScheme * attached. The endpoint of the thing must be passed as parameter. - * If asynchronous is false this function blocks until it succeeded or failed. Also if asynchronous is false - * no retries are done to try to reconnected if initial connection could not be established. - * If asynchronous is true this function returns without being connected and starts a background - * thread that retries until it was successfully connected or aborts for some reason. * This function should be called only once on the object - * @param privateKey The private key .pem file provided during setup of the AWS - * Iot Thing. - * @param certificate The certificate .crt.txt file provided during setup - * of the AWS Iot Thing. - * @param rootCA The Root CA for the certificate - * @param endpointUrl the endpoint URL normally in the format like - * "[YOUR-THING]-ats.iot.us-west-2.amazonaws.com" - * @param clientId the id that is used to identify this connection instance - * @param clientBootstrap pointer to AWS client bootstrap. Note AwsBootstrap is responsible for the client - * bootstrap lifecycle - * @param asynchronous if true launch a background thread. * @return True if connecting was successful in the synchronous case or if asynchronous true if the establish * connection retry thread was successfully started */ - bool connect( const std::string &privateKey, - const std::string &certificate, - const std::string &rootCA, - const std::string &endpointUrl, - const std::string &clientId, - Aws::Crt::Io::ClientBootstrap *clientBootstrap, - bool asynchronous = false ); + bool connect() override; - bool disconnect(); + bool disconnect() override; bool isAlive() const override @@ -83,26 +84,6 @@ class AwsIotConnectivityModule : public IRetryable, public IConnectivityModule return mConnected; }; - std::shared_ptr - getConnection() const override - { - return mConnection; - } - - /** - * @brief Increases atomically the memory usage - * @param bytes number of bytes to reserve - * @return number of bytes after the increase. - */ - std::size_t reserveMemoryUsage( std::size_t bytes ) override; - - /** - * @brief Decreases atomically the memory usage - * @param bytes number of bytes to release - * @return number of bytes after the decrease. - */ - std::size_t releaseMemoryUsage( std::size_t bytes ) override; - RetryStatus attempt() override; void onFinished( RetryStatus code ) override; @@ -111,29 +92,30 @@ class AwsIotConnectivityModule : public IRetryable, public IConnectivityModule * @brief create a new channel sharing the connection of this module * This call needs to be done before calling connect for all asynchronous subscribe channel * @param payloadManager the payload manager used by the new channel, - * @param maximumIotSDKHeapMemoryBytes the iot sdk heap threshold in bytes after which this channel will stop * sending data * * @return a pointer to the newly created channel. A reference to the newly created channel is also hold inside this * module. */ - std::shared_ptr createNewChannel( - const std::shared_ptr &payloadManager, - std::size_t maximumIotSDKHeapMemoryBytes = AwsIotChannel::MAXIMUM_IOT_SDK_HEAP_MEMORY_BYTES ); + std::shared_ptr createNewChannel( + const std::shared_ptr &payloadManager ) override; + + std::shared_ptr mConnection; private: - bool createMqttConnection( Aws::Crt::Io::ClientBootstrap *clientBootstrap ); + bool createMqttConnection(); void setupCallbacks(); static void renameEventLoopTask(); bool resetConnection(); - Aws::Crt::ByteCursor mCertificate{ 0, nullptr }; - Aws::Crt::String mEndpointUrl; - Aws::Crt::ByteCursor mPrivateKey{ 0, nullptr }; - Aws::Crt::ByteCursor mRootCA{ 0, nullptr }; - Aws::Crt::String mClientId; - std::shared_ptr mConnection; + std::string mPrivateKey; + std::string mCertificate; + std::string mRootCA; + std::string mEndpointUrl; + std::string mClientId; std::unique_ptr mMqttClient; + Aws::Crt::Io::ClientBootstrap *mClientBootstrap; + bool mAsynchronous; RetryThread mRetryThread; std::promise mConnectionCompletedPromise; diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/include/PayloadManager.h b/src/offboardconnectivity/implementation/aws/iotcpp/include/PayloadManager.h index 3b98732a..55890e80 100644 --- a/src/offboardconnectivity/implementation/aws/iotcpp/include/PayloadManager.h +++ b/src/offboardconnectivity/implementation/aws/iotcpp/include/PayloadManager.h @@ -24,15 +24,6 @@ using namespace Aws::IoTFleetWise::OffboardConnectivity; using Aws::IoTFleetWise::OffboardConnectivity::CollectionSchemeParams; using namespace Aws::IoTFleetWise::Platform::Linux::PersistencyManagement; -#pragma pack( push, 1 ) -struct PayloadHeader -{ - bool compressionRequired{ false }; - size_t size{ 0 }; -}; - -#pragma pack( pop ) - /** * @brief Class that handles offline data storage/retrieval and data compression before transmission */ @@ -42,46 +33,52 @@ class PayloadManager PayloadManager( std::shared_ptr persistencyPtr ); /** - * @brief Prepare the payload data to be written to storage. Adds a header with metadata consisting - * of compression flag and size of the payload. + * @brief Prepares and writes the payload data to storage. Constructs and writes payload metadata JSON object. * * @param buf buffer containing payload * @param size number of accessible bytes in buf * @param collectionSchemeParams object containing collectionScheme related metadata for data persistency and * transmission * - * @return true if data was persisted, else false + * @return true if data was persisted and metadata was added, else false */ bool storeData( const std::uint8_t *buf, size_t size, const struct CollectionSchemeParams &collectionSchemeParams ); /** - * @brief Parses the retrieved data from the storage. Separates metadata from the actual payload. - * - * @param data vector to store parsed payloads + * @brief Constructs and writes payload metadata JSON object. * - * @return SUCCESS if true, EMPTY if no data to retrieve, FILESYSTEM_ERROR if other errors + * @param filename file to construct metadata for + * @param size size of the payload + * @param collectionSchemeParams object containing collectionScheme related metadata for data persistency and + * transmission */ - ErrorCode retrieveData( std::vector &data ); + void storeMetadata( const std::string filename, + size_t size, + const struct CollectionSchemeParams &collectionSchemeParams ); -private: - std::shared_ptr mPersistencyPtr; + /** + * @brief Retrieves metadata for all persisted files from the JSON file and removes extracted metadata from the JSON + * file. + * + * @param files JSON object for all persisted files + * + * @return SUCCESS if metadata was successfully retrieved, FILESYSTEM_ERROR for other errors + */ + ErrorCode retrievePayloadMetadata( Json::Value &files ); /** - * @brief Prepare the payload data to be written to storage. Adds a header with metadata consisting - * of compression flag and size of the payload. + * @brief Retrieves persisted payload from the file and deletes the file. * - * @param buf buffer to store encoded payload - * @param data proto data to be stored - * @param size size of the buffer being passed - * @param collectionSchemeParams object containing collectionScheme related metadata for data persistency and - * transmission + * @param buf buffer containing payload + * @param size number of accessible bytes in buf + * @param filename filename to retrieve payload * - * @return true if prep was successful, false if error occurred + * @return SUCCESS if metadata was successfully retrieved, FILESYSTEM_ERROR for other errors */ - static bool preparePayload( uint8_t *const buf, - size_t size, - const std::string &data, - const CollectionSchemeParams &collectionSchemeParams ); + ErrorCode retrievePayload( uint8_t *buf, size_t size, const std::string &filename ); + +private: + std::shared_ptr mPersistencyPtr; }; } // namespace OffboardConnectivityAwsIot } // namespace IoTFleetWise diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsGGChannel.cpp b/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsGGChannel.cpp new file mode 100644 index 00000000..d7f23040 --- /dev/null +++ b/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsGGChannel.cpp @@ -0,0 +1,405 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#include "AwsGGChannel.h" +#include "AwsSDKMemoryManager.h" +#include "IConnectivityModule.h" +#include "LoggingModule.h" +#include +#include + +using namespace Aws::IoTFleetWise::OffboardConnectivityAwsIot; +using namespace Aws::IoTFleetWise::OffboardConnectivity; +using namespace Aws::Crt; +using namespace Aws::Greengrass; + +AwsGGChannel::AwsGGChannel( IConnectivityModule *connectivityModule, + std::shared_ptr payloadManager, + std::shared_ptr &ggConnection ) + : mConnectivityModule( connectivityModule ) + , mPayloadManager( std::move( payloadManager ) ) + , mConnection( ggConnection ) + , mSubscribed( false ) + , mSubscribeAsynchronously( false ) +{ +} + +bool +AwsGGChannel::isAlive() +{ + std::lock_guard connectivityLock( mConnectivityMutex ); + return isAliveNotThreadSafe(); +} + +bool +AwsGGChannel::isAliveNotThreadSafe() +{ + if ( mConnectivityModule == nullptr ) + { + return false; + } + return mConnectivityModule->isAlive(); +} + +void +AwsGGChannel::setTopic( const std::string &topicNameRef, bool subscribeAsynchronously ) +{ + if ( topicNameRef.empty() ) + { + FWE_LOG_ERROR( "Empty ingestion topic name provided" ); + } + mSubscribeAsynchronously = subscribeAsynchronously; + mTopicName = topicNameRef; +} + +ConnectivityError +AwsGGChannel::subscribe() +{ + std::lock_guard connectivityLock( mConnectivityMutex ); + if ( !isTopicValid() ) + { + FWE_LOG_ERROR( "Empty ingestion topic name provided" ); + return ConnectivityError::NotConfigured; + } + if ( !isAliveNotThreadSafe() ) + { + FWE_LOG_ERROR( "MQTT Connection not established, failed to subscribe" ); + return ConnectivityError::NoConnection; + } + + mSubscribeStreamHandler = std::make_shared( [&]( uint8_t *data, size_t size ) { + notifyListeners( &IReceiverCallback::onDataReceived, data, size ); + } ); + + if ( mConnection == nullptr ) + { + FWE_LOG_ERROR( "mConnection is null, not initialised" ) + return ConnectivityError::NoConnection; + } + mSubscribeOperation = mConnection->NewSubscribeToIoTCore( mSubscribeStreamHandler ); + SubscribeToIoTCoreRequest subscribeRequest; + subscribeRequest.SetQos( QOS_AT_LEAST_ONCE ); + subscribeRequest.SetTopicName( mTopicName.c_str() != nullptr ? mTopicName.c_str() : "" ); + + FWE_LOG_TRACE( "Attempting to subscribe to " + mTopicName + " topic" ); + auto requestStatus = mSubscribeOperation->Activate( subscribeRequest ).get(); + if ( !requestStatus ) + { + FWE_LOG_ERROR( "Failed to send subscription request to " + mTopicName + " topic" ); + return ConnectivityError::NoConnection; + } + + auto subscribeResultFuture = mSubscribeOperation->GetResult(); + + // To avoid throwing exceptions, wait on the result for a specified timeout: + if ( subscribeResultFuture.wait_for( std::chrono::seconds( 10 ) ) == std::future_status::timeout ) + { + FWE_LOG_ERROR( "Timed out while waiting for response from Greengrass Core" ); + return ConnectivityError::NoConnection; + } + + auto subscribeResult = subscribeResultFuture.get(); + if ( subscribeResult ) + { + FWE_LOG_TRACE( "Successfully subscribed to " + mTopicName + " topic" ); + mPayloadCountSent++; + } + else + { + auto errorType = subscribeResult.GetResultType(); + if ( errorType == OPERATION_ERROR ) + { + OperationError *error = subscribeResult.GetOperationError(); + /* + * This pointer can be casted to any error type like so: + * if(error->GetModelName() == UnauthorizedError::MODEL_NAME) + * UnauthorizedError *unauthorizedError = static_cast(error); + */ + if ( error->GetMessage().has_value() ) + { + auto errString = error->GetMessage().value().c_str(); + FWE_LOG_ERROR( "Greengrass Core responded with an error: " + + ( errString != nullptr ? std::string( errString ) : std::string( "Unknown error" ) ) ); + } + } + else + { + auto errString = subscribeResult.GetRpcError().StatusToString(); + FWE_LOG_ERROR( "Attempting to receive the response from the server failed with error code: " + + std::string( errString.c_str() != nullptr ? errString.c_str() : "Unknown error" ) ); + } + return ConnectivityError::NoConnection; + } + + return ConnectivityError::Success; +} + +size_t +AwsGGChannel::getMaxSendSize() const +{ + return AWS_IOT_MAX_MESSAGE_SIZE; +} + +ConnectivityError +AwsGGChannel::sendBuffer( const std::uint8_t *buf, size_t size, struct CollectionSchemeParams collectionSchemeParams ) +{ + std::lock_guard connectivityLock( mConnectivityMutex ); + if ( !isTopicValid() ) + { + FWE_LOG_WARN( "Invalid topic provided" ); + return ConnectivityError::NotConfigured; + } + + if ( ( buf == nullptr ) || ( size == 0 ) ) + { + FWE_LOG_WARN( "No valid data provided" ); + return ConnectivityError::WrongInputData; + } + + if ( size > getMaxSendSize() ) + { + FWE_LOG_WARN( "Payload provided is too long" ); + return ConnectivityError::WrongInputData; + } + + if ( !isAliveNotThreadSafe() ) + { + FWE_LOG_WARN( "No alive IPC Connection." ); + if ( mPayloadManager != nullptr ) + { + if ( collectionSchemeParams.persist ) + { + mPayloadManager->storeData( buf, size, collectionSchemeParams ); + } + else + { + FWE_LOG_TRACE( "CollectionScheme does not activate persistency on disk" ); + } + } + return ConnectivityError::NoConnection; + } + if ( !( AwsSDKMemoryManager::getInstance().reserveMemory( size ) ) ) + { + FWE_LOG_ERROR( "Not sending out the message with size " + std::to_string( size ) + + " because IoT device SDK allocated the maximum defined memory. Payload will be stored" ); + + if ( collectionSchemeParams.persist ) + { + mPayloadManager->storeData( buf, size, collectionSchemeParams ); + } + else + { + FWE_LOG_TRACE( "CollectionScheme does not activate persistency on disk" ); + } + return ConnectivityError::QuotaReached; + } + + if ( mConnection == nullptr ) + { + FWE_LOG_ERROR( "mConnection is null, not initialised" ) + return ConnectivityError::NoConnection; + } + auto publishOperation = mConnection->NewPublishToIoTCore(); + PublishToIoTCoreRequest publishRequest; + publishRequest.SetTopicName( mTopicName.c_str() != nullptr ? mTopicName.c_str() : "" ); + Vector payload( buf, buf + size ); + publishRequest.SetPayload( payload ); + publishRequest.SetQos( QOS_AT_LEAST_ONCE ); + + FWE_LOG_TRACE( "Attempting to publish to " + mTopicName + " topic" ); + auto requestStatus = publishOperation->Activate( publishRequest ).get(); + if ( !requestStatus ) + { + auto errString = requestStatus.StatusToString(); + FWE_LOG_ERROR( "Failed to publish to " + mTopicName + " topic with error " + + std::string( errString.c_str() != nullptr ? errString.c_str() : "Unknown error" ) ); + + return ConnectivityError::NoConnection; + } + + auto publishResultFuture = publishOperation->GetResult(); + + // To avoid throwing exceptions, wait on the result for a specified timeout: + if ( publishResultFuture.wait_for( std::chrono::seconds( 10 ) ) == std::future_status::timeout ) + { + FWE_LOG_ERROR( "Timed out while waiting for response from Greengrass Core" ); + return ConnectivityError::NoConnection; + } + + auto publishResult = publishResultFuture.get(); + if ( !publishResult ) + { + FWE_LOG_ERROR( "Failed to publish to " + mTopicName + " topic" ); + auto errorType = publishResult.GetResultType(); + if ( errorType == OPERATION_ERROR ) + { + OperationError *error = publishResult.GetOperationError(); + /* + * This pointer can be casted to any error type like so: + * if(error->GetModelName() == UnauthorizedError::MODEL_NAME) + * UnauthorizedError *unauthorizedError = static_cast(error); + */ + if ( error->GetMessage().has_value() ) + { + auto errString = error->GetMessage().value().c_str(); + FWE_LOG_ERROR( "Greengrass Core responded with an error: " + + ( errString != nullptr ? std::string( errString ) : std::string( "Unknown error" ) ) ); + } + } + else + { + auto errString = publishResult.GetRpcError().StatusToString(); + FWE_LOG_ERROR( "Attempting to receive the response from the server failed with error code " + + std::string( errString.c_str() != nullptr ? errString.c_str() : "Unknown error" ) ); + } + return ConnectivityError::NoConnection; + } + + return ConnectivityError::Success; +} + +ConnectivityError +AwsGGChannel::sendFile( const std::string &filePath, size_t size, struct CollectionSchemeParams collectionSchemeParams ) +{ + std::lock_guard connectivityLock( mConnectivityMutex ); + if ( !isTopicValid() ) + { + FWE_LOG_WARN( "Invalid topic provided" ); + return ConnectivityError::NotConfigured; + } + + if ( mPayloadManager == nullptr ) + { + FWE_LOG_WARN( "No payload manager provided" ); + return ConnectivityError::NotConfigured; + } + + if ( filePath.empty() ) + { + FWE_LOG_WARN( "No valid file path provided" ); + return ConnectivityError::WrongInputData; + } + + if ( size > getMaxSendSize() ) + { + FWE_LOG_WARN( "Payload provided is too long" ); + return ConnectivityError::WrongInputData; + } + + if ( !isAliveNotThreadSafe() ) + { + if ( collectionSchemeParams.persist ) + { + // Only store metadata, file is already written on the disk + mPayloadManager->storeMetadata( filePath, size, collectionSchemeParams ); + } + else + { + FWE_LOG_TRACE( "CollectionScheme does not activate persistency on disk" ); + } + return ConnectivityError::NoConnection; + } + + if ( !AwsSDKMemoryManager::getInstance().reserveMemory( size ) ) + { + FWE_LOG_ERROR( "Not sending out the message with size " + std::to_string( size ) + + " because IoT device SDK allocated the maximum defined memory. Currently allocated " ); + { + if ( collectionSchemeParams.persist ) + { + // Only store metadata, file is already written on the disk + mPayloadManager->storeMetadata( filePath, size, collectionSchemeParams ); + } + else + { + FWE_LOG_TRACE( "CollectionScheme does not activate persistency on disk" ); + } + } + return ConnectivityError::QuotaReached; + } + + Vector payload( size ); + if ( mPayloadManager->retrievePayload( payload.data(), payload.size(), filePath ) != ErrorCode::SUCCESS ) + { + return ConnectivityError::WrongInputData; + } + + if ( mConnection == nullptr ) + { + FWE_LOG_ERROR( "mConnection is null, not initialised" ) + return ConnectivityError::NoConnection; + } + auto publishOperation = mConnection->NewPublishToIoTCore(); + PublishToIoTCoreRequest publishRequest; + publishRequest.SetTopicName( mTopicName.c_str() != nullptr ? mTopicName.c_str() : "" ); + publishRequest.SetPayload( payload ); + publishRequest.SetQos( QOS_AT_LEAST_ONCE ); + + FWE_LOG_TRACE( "Attempting to publish to " + mTopicName + " topic" ); + auto requestStatus = publishOperation->Activate( publishRequest ).get(); + if ( !requestStatus ) + { + FWE_LOG_ERROR( "Failed to publish to " + mTopicName + " topic with error " + + std::string( requestStatus.StatusToString().c_str() ) ); + return ConnectivityError::NoConnection; + } + + auto publishResultFuture = publishOperation->GetResult(); + + // To avoid throwing exceptions, wait on the result for a specified timeout: + if ( publishResultFuture.wait_for( std::chrono::seconds( 10 ) ) == std::future_status::timeout ) + { + FWE_LOG_ERROR( "Timed out while waiting for response from Greengrass Core" ); + return ConnectivityError::NoConnection; + } + + auto publishResult = publishResultFuture.get(); + if ( !publishResult ) + { + FWE_LOG_ERROR( "Failed to publish to " + mTopicName + " topic" ); + auto errorType = publishResult.GetResultType(); + if ( errorType == OPERATION_ERROR ) + { + OperationError *error = publishResult.GetOperationError(); + /* + * This pointer can be casted to any error type like so: + * if(error->GetModelName() == UnauthorizedError::MODEL_NAME) + * UnauthorizedError *unauthorizedError = static_cast(error); + */ + if ( error->GetMessage().has_value() ) + { + + auto errString = error->GetMessage().value().c_str(); + FWE_LOG_ERROR( "Greengrass Core responded with an error: " + + ( errString != nullptr ? std::string( errString ) : std::string( "Unknown error" ) ) ); + } + } + else + { + auto errString = publishResult.GetRpcError().StatusToString(); + FWE_LOG_ERROR( "Attempting to receive the response from the server failed with error code " + + std::string( errString.c_str() != nullptr ? errString.c_str() : "Unknown error" ) ); + } + return ConnectivityError::NoConnection; + } + + return ConnectivityError::Success; +} + +bool +AwsGGChannel::unsubscribe() +{ + std::lock_guard connectivityLock( mConnectivityMutex ); + if ( mSubscribed && isAliveNotThreadSafe() ) + { + mSubscribeOperation.get()->Close().wait(); + mSubscribed = false; + return true; + } + return false; +} + +AwsGGChannel::~AwsGGChannel() +{ + unsubscribe(); +} diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsGGConnectivityModule.cpp b/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsGGConnectivityModule.cpp new file mode 100644 index 00000000..c86da397 --- /dev/null +++ b/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsGGConnectivityModule.cpp @@ -0,0 +1,83 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#include "AwsGGConnectivityModule.h" +#include "AwsGGChannel.h" +#include "LoggingModule.h" +#include "TraceModule.h" + +#include +#include + +#include +#include +#include +#include + +#include "Thread.h" + +using namespace Aws::Crt; +using namespace Aws::Greengrass; +using namespace Aws::IoTFleetWise::OffboardConnectivityAwsIot; + +AwsGGConnectivityModule::AwsGGConnectivityModule( Aws::Crt::Io::ClientBootstrap *clientBootstrap ) + : mConnected( false ) + , mConnectionEstablished( false ) + , mClientBootstrap( clientBootstrap ) +{ +} + +/* + * As first step to enable backend communication this code is mainly oriented on the basic_pub_sub + * example from the Aws Iot C++ SDK + */ +bool +AwsGGConnectivityModule::connect() +{ + + mConnected = false; + + mLifecycleHandler = std::make_unique(); + mConnection = std::make_shared( *( mClientBootstrap ) ); + auto connectionStatus = mConnection->Connect( *( mLifecycleHandler.get() ) ).get(); + if ( !connectionStatus ) + { + FWE_LOG_ERROR( "Failed to establish connection with error " + std::to_string( connectionStatus ) ); + return false; + } + + mConnected = true; + for ( auto channel : mChannels ) + { + if ( channel->shouldSubscribeAsynchronously() ) + { + channel->subscribe(); + } + } + FWE_LOG_INFO( "Successfully connected" ); + return true; +} + +std::shared_ptr +AwsGGConnectivityModule::createNewChannel( const std::shared_ptr &payloadManager ) +{ + auto channel = std::make_shared( this, payloadManager, mConnection ); + mChannels.emplace_back( channel ); + return channel; +} + +bool +AwsGGConnectivityModule::disconnect() +{ + for ( auto channel : mChannels ) + { + channel->unsubscribe(); + channel->invalidateConnection(); + } + return true; +} + +AwsGGConnectivityModule::~AwsGGConnectivityModule() +{ + AwsGGConnectivityModule::disconnect(); +} diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsIotChannel.cpp b/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsIotChannel.cpp index 16d64583..52448194 100644 --- a/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsIotChannel.cpp +++ b/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsIotChannel.cpp @@ -2,7 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 #include "AwsIotChannel.h" -#include "AwsIotConnectivityModule.h" +#include "AwsSDKMemoryManager.h" +#include "IConnectivityModule.h" #include "LoggingModule.h" #include "TraceModule.h" #include @@ -13,10 +14,10 @@ using namespace Aws::Crt; AwsIotChannel::AwsIotChannel( IConnectivityModule *connectivityModule, std::shared_ptr payloadManager, - std::size_t maximumIotSDKHeapMemoryBytes ) - : mMaximumIotSDKHeapMemoryBytes( maximumIotSDKHeapMemoryBytes ) - , mConnectivityModule( connectivityModule ) + std::shared_ptr &mqttConnection ) + : mConnectivityModule( connectivityModule ) , mPayloadManager( std::move( payloadManager ) ) + , mConnection( mqttConnection ) , mSubscribed( false ) , mSubscribeAsynchronously( false ) { @@ -64,7 +65,6 @@ AwsIotChannel::subscribe() FWE_LOG_ERROR( "MQTT Connection not established, failed to subscribe" ); return ConnectivityError::NoConnection; } - auto connection = mConnectivityModule->getConnection(); /* * This is invoked upon the reception of a message on a subscribed topic. */ @@ -122,7 +122,7 @@ AwsIotChannel::subscribe() }; FWE_LOG_TRACE( "Subscribing..." ); - connection->Subscribe( mTopicName.c_str(), Mqtt::QOS::AWS_MQTT_QOS_AT_LEAST_ONCE, onMessage, onSubAck ); + mConnection->Subscribe( mTopicName.c_str(), Mqtt::QOS::AWS_MQTT_QOS_AT_LEAST_ONCE, onMessage, onSubAck ); // Blocked call until subscribe finished this call should quickly either fail or succeed but // depends on the network quality the Bootstrap needs to retry subscribing if failed. @@ -168,64 +168,146 @@ AwsIotChannel::sendBuffer( const std::uint8_t *buf, size_t size, struct Collecti { if ( mPayloadManager != nullptr ) { - bool isDataPersisted = mPayloadManager->storeData( buf, size, collectionSchemeParams ); - - if ( isDataPersisted ) + if ( collectionSchemeParams.persist ) { - FWE_LOG_TRACE( "Payload has persisted successfully on disk" ); + mPayloadManager->storeData( buf, size, collectionSchemeParams ); } else { - FWE_LOG_WARN( "Payload has not been persisted" ); + FWE_LOG_TRACE( "CollectionScheme does not activate persistency on disk" ); } } return ConnectivityError::NoConnection; } - uint64_t currentMemoryUsage = mConnectivityModule->reserveMemoryUsage( size ); - if ( ( mMaximumIotSDKHeapMemoryBytes != 0 ) && ( currentMemoryUsage > mMaximumIotSDKHeapMemoryBytes ) ) + if ( !AwsSDKMemoryManager::getInstance().reserveMemory( size ) ) { - mConnectivityModule->releaseMemoryUsage( size ); FWE_LOG_ERROR( "Not sending out the message with size " + std::to_string( size ) + - " because IoT device SDK allocated the maximum defined memory. Currently allocated " + - std::to_string( currentMemoryUsage ) ); - if ( mPayloadManager != nullptr ) + " because IoT device SDK allocated the maximum defined memory. Payload will be stored" ); + + if ( collectionSchemeParams.persist ) + { + mPayloadManager->storeData( buf, size, collectionSchemeParams ); + } + else { - bool isDataPersisted = mPayloadManager->storeData( buf, size, collectionSchemeParams ); + FWE_LOG_TRACE( "CollectionScheme does not activate persistency on disk" ); + } + return ConnectivityError::QuotaReached; + } + + auto payload = ByteBufNewCopy( DefaultAllocator(), (const uint8_t *)buf, size ); - if ( isDataPersisted ) + auto onPublishComplete = + [payload, size, this]( Mqtt::MqttConnection &mqttConnection, uint16_t packetId, int errorCode ) mutable { + /* This call means that the data was handed over to some lower level in the stack but not + that the data is actually sent on the bus or removed from RAM*/ + (void)mqttConnection; + aws_byte_buf_clean_up( &payload ); { - FWE_LOG_TRACE( "Data was persisted successfully" ); + std::lock_guard connectivityLambdaLock( mConnectivityLambdaMutex ); + AwsSDKMemoryManager::getInstance().releaseReservedMemory( size ); + } + if ( ( packetId != 0U ) && ( errorCode == 0 ) ) + { + FWE_LOG_TRACE( "Operation on packetId " + std::to_string( packetId ) + " Succeeded" ); + mPayloadCountSent++; } else { - FWE_LOG_WARN( "Data was not persisted and is lost" ); + auto errSting = aws_error_debug_str( errorCode ); + std::string errLog = errSting != nullptr ? std::string( errSting ) : std::string( "Unknown error" ); + FWE_LOG_ERROR( std::string( "Operation failed with error" ) + errLog ); + } + }; + mConnection->Publish( mTopicName.c_str(), Mqtt::QOS::AWS_MQTT_QOS_AT_MOST_ONCE, false, payload, onPublishComplete ); + return ConnectivityError::Success; +} + +ConnectivityError +AwsIotChannel::sendFile( const std::string &filePath, + size_t size, + struct CollectionSchemeParams collectionSchemeParams ) +{ + std::lock_guard connectivityLock( mConnectivityMutex ); + if ( !isTopicValid() ) + { + FWE_LOG_WARN( "Invalid topic provided" ); + return ConnectivityError::NotConfigured; + } + + if ( mPayloadManager == nullptr ) + { + FWE_LOG_WARN( "No payload manager provided" ); + return ConnectivityError::NotConfigured; + } + + if ( filePath.empty() ) + { + FWE_LOG_WARN( "No valid file path provided" ); + return ConnectivityError::WrongInputData; + } + + if ( size > getMaxSendSize() ) + { + FWE_LOG_WARN( "Payload provided is too long" ); + return ConnectivityError::WrongInputData; + } + + if ( !isAliveNotThreadSafe() ) + { + + if ( collectionSchemeParams.persist ) + { + // Only store metadata, file is already written on the disk + mPayloadManager->storeMetadata( filePath, size, collectionSchemeParams ); + } + else + { + FWE_LOG_TRACE( "CollectionScheme does not activate persistency on disk" ); + } + return ConnectivityError::NoConnection; + } + + if ( !AwsSDKMemoryManager::getInstance().reserveMemory( size ) ) + { + FWE_LOG_ERROR( "Not sending out the message with size " + std::to_string( size ) + + " because IoT device SDK allocated the maximum defined memory. Currently allocated " ); + { + if ( collectionSchemeParams.persist ) + { + // Only store metadata, file is already written on the disk + mPayloadManager->storeMetadata( filePath, size, collectionSchemeParams ); + } + else + { + FWE_LOG_TRACE( "CollectionScheme does not activate persistency on disk" ); } } return ConnectivityError::QuotaReached; } - auto connection = mConnectivityModule->getConnection(); + std::vector payload( size ); + if ( mPayloadManager->retrievePayload( payload.data(), payload.size(), filePath ) != ErrorCode::SUCCESS ) + { + return ConnectivityError::WrongInputData; + } - auto payload = ByteBufNewCopy( DefaultAllocator(), (const uint8_t *)buf, size ); + auto payloadBuffer = ByteBufNewCopy( DefaultAllocator(), (const uint8_t *)payload.data(), payload.size() ); auto onPublishComplete = - [payload, size, this]( Mqtt::MqttConnection &mqttConnection, uint16_t packetId, int errorCode ) mutable { + [payloadBuffer, size, this]( Mqtt::MqttConnection &mqttConnection, uint16_t packetId, int errorCode ) mutable { /* This call means that the data was handed over to some lower level in the stack but not - that the data is actually sent on the bus or removed from RAM*/ + that the data is actually sent on the bus or removed from RAM */ (void)mqttConnection; - aws_byte_buf_clean_up( &payload ); + aws_byte_buf_clean_up( &payloadBuffer ); { std::lock_guard connectivityLambdaLock( mConnectivityLambdaMutex ); - if ( mConnectivityModule != nullptr ) - { - mConnectivityModule->releaseMemoryUsage( size ); - } + AwsSDKMemoryManager::getInstance().releaseReservedMemory( size ); } if ( ( packetId != 0U ) && ( errorCode == 0 ) ) { FWE_LOG_TRACE( "Operation on packetId " + std::to_string( packetId ) + " Succeeded" ); - mPayloadCountSent++; } else { @@ -234,7 +316,8 @@ AwsIotChannel::sendBuffer( const std::uint8_t *buf, size_t size, struct Collecti FWE_LOG_ERROR( std::string( "Operation failed with error" ) + errLog ); } }; - connection->Publish( mTopicName.c_str(), Mqtt::QOS::AWS_MQTT_QOS_AT_MOST_ONCE, false, payload, onPublishComplete ); + mConnection->Publish( + mTopicName.c_str(), Mqtt::QOS::AWS_MQTT_QOS_AT_MOST_ONCE, false, payloadBuffer, onPublishComplete ); return ConnectivityError::Success; } @@ -244,19 +327,17 @@ AwsIotChannel::unsubscribe() std::lock_guard connectivityLock( mConnectivityMutex ); if ( mSubscribed && isAliveNotThreadSafe() ) { - auto connection = mConnectivityModule->getConnection(); - std::promise unsubscribeFinishedPromise; FWE_LOG_TRACE( "Unsubscribing..." ); - connection->Unsubscribe( mTopicName.c_str(), - [&]( Mqtt::MqttConnection &mqttConnection, uint16_t packetId, int errorCode ) { - (void)mqttConnection; - (void)packetId; - (void)errorCode; - FWE_LOG_TRACE( "Unsubscribed" ); - mSubscribed = false; - unsubscribeFinishedPromise.set_value(); - } ); + mConnection->Unsubscribe( mTopicName.c_str(), + [&]( Mqtt::MqttConnection &mqttConnection, uint16_t packetId, int errorCode ) { + (void)mqttConnection; + (void)packetId; + (void)errorCode; + FWE_LOG_TRACE( "Unsubscribed" ); + mSubscribed = false; + unsubscribeFinishedPromise.set_value(); + } ); // Blocked call until subscribe finished this call should quickly either fail or succeed but // depends on the network quality the Bootstrap needs to retry subscribing if failed. unsubscribeFinishedPromise.get_future().wait(); diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsIotConnectivityModule.cpp b/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsIotConnectivityModule.cpp index d9c26da8..b0802993 100644 --- a/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsIotConnectivityModule.cpp +++ b/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsIotConnectivityModule.cpp @@ -3,7 +3,6 @@ #include "AwsIotConnectivityModule.h" #include "AwsBootstrap.h" -#include "AwsSDKMemoryManager.h" #include "LoggingModule.h" #include "TraceModule.h" @@ -14,7 +13,6 @@ #include #include "Thread.h" -#include using namespace Aws::IoTFleetWise::OffboardConnectivityAwsIot; using namespace Aws::Crt; @@ -29,47 +27,36 @@ constexpr uint16_t MQTT_CONNECT_KEEP_ALIVE_SECONDS = 60; // If the PING request does not return within this interval, the stack will create a new one. constexpr uint32_t MQTT_PING_TIMOUT_MS = 3000; -AwsIotConnectivityModule::AwsIotConnectivityModule() - : mRetryThread( *this, RETRY_FIRST_CONNECTION_START_BACKOFF_MS, RETRY_FIRST_CONNECTION_MAX_BACKOFF_MS ) +AwsIotConnectivityModule::AwsIotConnectivityModule( std::string privateKey, + std::string certificate, + std::string rootCA, + std::string endpointUrl, + std::string clientId, + Aws::Crt::Io::ClientBootstrap *clientBootstrap, + bool asynchronous ) + : mPrivateKey( std::move( privateKey ) ) + , mCertificate( std::move( certificate ) ) + , mRootCA( std::move( rootCA ) ) + , mEndpointUrl( std::move( endpointUrl ) ) + , mClientId( std::move( clientId ) ) + , mClientBootstrap( clientBootstrap ) + , mAsynchronous( asynchronous ) + , mRetryThread( *this, RETRY_FIRST_CONNECTION_START_BACKOFF_MS, RETRY_FIRST_CONNECTION_MAX_BACKOFF_MS ) , mConnected( false ) , mConnectionEstablished( false ) { } -std::size_t -AwsIotConnectivityModule::reserveMemoryUsage( std::size_t bytes ) -{ - return AwsSDKMemoryManager::getInstance().reserveMemory( bytes ); -} - -std::size_t -AwsIotConnectivityModule::releaseMemoryUsage( std::size_t bytes ) -{ - return AwsSDKMemoryManager::getInstance().releaseReservedMemory( bytes ); -} - /* * As first step to enable backend communication this code is mainly oriented on the basic_pub_sub * example from the Aws Iot C++ SDK */ bool -AwsIotConnectivityModule::connect( const std::string &privateKey, - const std::string &certificate, - const std::string &rootCA, - const std::string &endpointUrl, - const std::string &clientId, - Aws::Crt::Io::ClientBootstrap *clientBootstrap, - bool asynchronous ) +AwsIotConnectivityModule::connect() { - mClientId = clientId.c_str() != nullptr ? clientId.c_str() : ""; - mConnected = false; - mCertificate = Crt::ByteCursorFromCString( certificate.c_str() ); - mEndpointUrl = endpointUrl.c_str() != nullptr ? endpointUrl.c_str() : ""; - mPrivateKey = Crt::ByteCursorFromCString( privateKey.c_str() ); - mRootCA = Crt::ByteCursorFromCString( rootCA.c_str() ); - if ( !createMqttConnection( clientBootstrap ) ) + if ( !createMqttConnection() ) { return false; } @@ -78,7 +65,7 @@ AwsIotConnectivityModule::connect( const std::string &privateKey, // Connection callbacks setupCallbacks(); - if ( asynchronous ) + if ( mAsynchronous ) { return mRetryThread.start(); } @@ -93,11 +80,10 @@ AwsIotConnectivityModule::connect( const std::string &privateKey, } } -std::shared_ptr -AwsIotConnectivityModule::createNewChannel( const std::shared_ptr &payloadManager, - std::size_t maximumIotSDKHeapMemoryBytes ) +std::shared_ptr +AwsIotConnectivityModule::createNewChannel( const std::shared_ptr &payloadManager ) { - auto channel = std::make_shared( this, payloadManager, maximumIotSDKHeapMemoryBytes ); + auto channel = std::make_shared( this, payloadManager, mConnection ); mChannels.emplace_back( channel ); return channel; } @@ -232,21 +218,21 @@ AwsIotConnectivityModule::renameEventLoopTask() } bool -AwsIotConnectivityModule::createMqttConnection( Aws::Crt::Io::ClientBootstrap *clientBootstrap ) +AwsIotConnectivityModule::createMqttConnection() { - if ( ( mCertificate.len == 0 ) || ( mPrivateKey.len == 0 ) || mEndpointUrl.empty() || mClientId.empty() ) + if ( ( mCertificate.empty() ) || ( mPrivateKey.empty() ) || mEndpointUrl.empty() || mClientId.empty() ) { FWE_LOG_ERROR( "Please provide X.509 Certificate, private Key, endpoint and client-Id" ); return false; } - if ( clientBootstrap == nullptr ) + if ( mClientBootstrap == nullptr ) { FWE_LOG_ERROR( "ClientBootstrap failed with error" ); return false; } - else if ( !( *clientBootstrap ) ) + else if ( !( *mClientBootstrap ) ) { - auto errString = ErrorDebugString( clientBootstrap->LastError() ); + auto errString = ErrorDebugString( mClientBootstrap->LastError() ); FWE_LOG_ERROR( "ClientBootstrap failed with error" ); FWE_LOG_ERROR( errString != nullptr ? std::string( errString ) : std::string( "Unknown error" ) ); return false; @@ -254,12 +240,13 @@ AwsIotConnectivityModule::createMqttConnection( Aws::Crt::Io::ClientBootstrap *c Aws::Iot::MqttClientConnectionConfigBuilder builder; - builder = Aws::Iot::MqttClientConnectionConfigBuilder( mCertificate, mPrivateKey ); - if ( mRootCA.len > 0 ) + builder = Aws::Iot::MqttClientConnectionConfigBuilder( Crt::ByteCursorFromCString( mCertificate.c_str() ), + Crt::ByteCursorFromCString( mPrivateKey.c_str() ) ); + if ( !mRootCA.empty() ) { - builder.WithCertificateAuthority( mRootCA ); + builder.WithCertificateAuthority( Crt::ByteCursorFromCString( mRootCA.c_str() ) ); } - builder.WithEndpoint( mEndpointUrl ); + builder.WithEndpoint( ( !mEndpointUrl.empty() ? mEndpointUrl.c_str() : "" ) ); TraceModule::get().sectionBegin( TraceSection::BUILD_MQTT ); auto clientConfig = builder.Build(); @@ -276,7 +263,7 @@ AwsIotConnectivityModule::createMqttConnection( Aws::Crt::Io::ClientBootstrap *c * Documentation of MqttClient says that the parameter objects needs to live only for the * function call so all the needed objects like bootstrap are on the stack. */ - mMqttClient = std::make_unique( *clientBootstrap ); + mMqttClient = std::make_unique( *mClientBootstrap ); if ( !*mMqttClient ) { auto errString = ErrorDebugString( mMqttClient->LastError() ); @@ -345,5 +332,5 @@ AwsIotConnectivityModule::onFinished( RetryStatus code ) AwsIotConnectivityModule::~AwsIotConnectivityModule() { - disconnect(); + AwsIotConnectivityModule::disconnect(); } diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/src/PayloadManager.cpp b/src/offboardconnectivity/implementation/aws/iotcpp/src/PayloadManager.cpp index 8d1bb37a..7b89f526 100644 --- a/src/offboardconnectivity/implementation/aws/iotcpp/src/PayloadManager.cpp +++ b/src/offboardconnectivity/implementation/aws/iotcpp/src/PayloadManager.cpp @@ -17,175 +17,100 @@ PayloadManager::PayloadManager( std::shared_ptr persistencyPtr } bool -PayloadManager::preparePayload( uint8_t *const buf, - size_t size, - const std::string &data, - const CollectionSchemeParams &collectionSchemeParams ) +PayloadManager::storeData( const std::uint8_t *buf, + size_t size, + const struct CollectionSchemeParams &collectionSchemeParams ) { - if ( buf == nullptr ) + if ( mPersistencyPtr == nullptr ) + { + FWE_LOG_ERROR( "No CacheAndPersist module provided" ); + return false; + } + + if ( ( buf == nullptr ) || ( size == 0 ) ) { + FWE_LOG_ERROR( "Payload buffer is empty" ); TraceModule::get().incrementVariable( TraceVariable::PM_MEMORY_NULL ); - FWE_LOG_ERROR( "Payload provided is empty" ); return false; } - // Prefix the data with header - PayloadHeader payloadHdr = {}; - size_t hdrSize = sizeof( PayloadHeader ); + std::string filename; + { + filename = std::to_string( collectionSchemeParams.eventID ) + "-" + + std::to_string( collectionSchemeParams.triggerTime ) + ".bin"; + } - if ( size < ( data.size() + hdrSize ) ) + ErrorCode writeStatus = mPersistencyPtr->write( buf, size, DataType::EDGE_TO_CLOUD_PAYLOAD, filename ); + if ( writeStatus != ErrorCode::SUCCESS ) { - TraceModule::get().incrementVariable( TraceVariable::PM_MEMORY_INSUFFICIENT ); - FWE_LOG_ERROR( "Payload Buffer size not sufficient" ); + FWE_LOG_ERROR( "Failed to persist collected data on disk" ); + TraceModule::get().incrementVariable( TraceVariable::PM_STORE_ERROR ); + if ( writeStatus == ErrorCode::MEMORY_FULL ) + { + TraceModule::get().incrementVariable( TraceVariable::PM_MEMORY_INSUFFICIENT ); + } return false; } - // Add a payload header before writing to the file - payloadHdr.size = data.size(); - payloadHdr.compressionRequired = collectionSchemeParams.compression; - - memcpy( &buf[0], &payloadHdr, hdrSize ); - memcpy( reinterpret_cast( &buf[hdrSize] ), data.data(), payloadHdr.size ); + FWE_LOG_TRACE( "Payload of size : " + std::to_string( size ) + " Bytes has been successfully persisted in file " + + filename ); + storeMetadata( filename, size, collectionSchemeParams ); return true; } -bool -PayloadManager::storeData( const std::uint8_t *buf, - size_t size, - const struct CollectionSchemeParams &collectionSchemeParams ) +void +PayloadManager::storeMetadata( const std::string filename, + size_t size, + const struct CollectionSchemeParams &collectionSchemeParams ) { - bool isDataPersisted = false; - if ( collectionSchemeParams.persist ) - { - FWE_LOG_TRACE( "The schema activates data persistency" ); - std::string payload; - if ( buf != nullptr ) - { - payload.assign( reinterpret_cast( buf ), size ); - } - std::string compressedData; - // if compression was not specified in the collectionScheme, DCSender did not compress - // compress it anyway for storage - if ( !collectionSchemeParams.compression ) - { - FWE_LOG_TRACE( "CollectionScheme does not activate compression, but will apply compression for local " - "persistency anyway" ); - if ( snappy::Compress( payload.data(), payload.size(), &compressedData ) == 0U ) - { - TraceModule::get().incrementVariable( TraceVariable::PM_COMPRESS_ERROR ); - FWE_LOG_ERROR( "Error occurred when compressing the payload. The payload is likely corrupted." ); - return isDataPersisted; - } - } - else - { - // the payload was already compressed - compressedData = payload; - } - - size_t totalWriteSize = compressedData.size() + sizeof( PayloadHeader ); - // Allocate a bigger buffer to prefix payload header to the payload - std::vector writeBuffer( totalWriteSize ); - // Add metadata to the payload before storage - if ( !preparePayload( writeBuffer.data(), totalWriteSize, compressedData, collectionSchemeParams ) ) - { - FWE_LOG_ERROR( "Error occurred during payload preparation" ); - return isDataPersisted; - } - - ErrorCode status = - mPersistencyPtr->write( writeBuffer.data(), totalWriteSize, DataType::EDGE_TO_CLOUD_PAYLOAD ); + Json::Value metadata; + metadata["filename"] = filename; + metadata["payloadSize"] = static_cast( size ); + metadata["compressionRequired"] = collectionSchemeParams.compression; + mPersistencyPtr->addMetadata( metadata ); + FWE_LOG_TRACE( "Metadata for file " + filename + " has been successfully added" ); +} - if ( status == ErrorCode::SUCCESS ) - { - // set the ErrorCode::SUCCESSful storage flag to true - isDataPersisted = true; - FWE_LOG_TRACE( "Payload of size : " + std::to_string( totalWriteSize ) + " Bytes (header: " + - std::to_string( sizeof( PayloadHeader ) ) + ") has been ErrorCode::SUCCESSfully persisted" ); - } - else - { - TraceModule::get().incrementVariable( TraceVariable::PM_STORE_ERROR ); - FWE_LOG_ERROR( "Failed to persist data on disk" ); - } - } - else +ErrorCode +PayloadManager::retrievePayloadMetadata( Json::Value &files ) +{ + if ( mPersistencyPtr == nullptr ) { - FWE_LOG_TRACE( "CollectionScheme does not activate persistency on disk" ); + FWE_LOG_ERROR( "No CacheAndPersist module provided" ); + return ErrorCode::INVALID_DATA; } - return isDataPersisted; + + files = mPersistencyPtr->getMetadata(); + mPersistencyPtr->clearMetadata(); + FWE_LOG_TRACE( "Successfully retrieved metadata" ); + return ErrorCode::SUCCESS; } ErrorCode -PayloadManager::retrieveData( std::vector &data ) +PayloadManager::retrievePayload( uint8_t *buf, size_t size, const std::string &filename ) { - size_t readSize = mPersistencyPtr->getSize( DataType::EDGE_TO_CLOUD_PAYLOAD ); - - if ( readSize == 0 ) + if ( mPersistencyPtr == nullptr ) { - return ErrorCode::EMPTY; + FWE_LOG_ERROR( "No CacheAndPersist module provided" ); + return ErrorCode::INVALID_DATA; } - // Parsed data will be stored here - std::vector readBuffer( readSize ); - ErrorCode status = mPersistencyPtr->read( readBuffer.data(), readSize, DataType::EDGE_TO_CLOUD_PAYLOAD ); + if ( ( buf == nullptr ) || ( size == 0 ) ) + { + FWE_LOG_ERROR( "Buffer is empty" ); + return ErrorCode::INVALID_DATA; + } + ErrorCode status = mPersistencyPtr->read( buf, size, DataType::EDGE_TO_CLOUD_PAYLOAD, filename ); + // Delete file from disk + mPersistencyPtr->erase( DataType::EDGE_TO_CLOUD_PAYLOAD, filename ); if ( status != ErrorCode::SUCCESS ) { + FWE_LOG_ERROR( "Failed to read persisted data from file " + filename ); return status; } - else - { - size_t size = 0U; - std::string dataString; - - // Read from the beginning of the buffer - size_t pos = 0; - - while ( pos < readSize ) - { - size_t j = 0; - PayloadHeader payloadHdr{}; - memcpy( &payloadHdr, &( readBuffer[pos] ), sizeof( PayloadHeader ) ); - pos += sizeof( PayloadHeader ); - - // capture the size of the payload - size = payloadHdr.size; - - // Clear the data string before parsing new payload - dataString.clear(); - for ( j = 0; ( j < size ) && ( ( pos + j ) < readSize ); ++j ) - { - dataString += static_cast( readBuffer[pos + j] ); - } - - std::string payloadData; - // Since we always compress for storage, - // uncompress if the collectionScheme did not require compression - if ( !payloadHdr.compressionRequired ) - { - FWE_LOG_TRACE( "CollectionScheme does not require compression, uncompress " + - std::to_string( dataString.size() ) + - " bytes before transmitting the " - "persisted data." ); - if ( !snappy::Uncompress( dataString.data(), dataString.size(), &payloadData ) ) - { - FWE_LOG_ERROR( - "Error occurred while un-compressing the payload from disk. The payload is likely corrupted." ); - return ErrorCode::INVALID_DATA; - } - } - else - { - payloadData = dataString; - } - - data.emplace_back( payloadData ); - pos += j; - } - } - FWE_LOG_INFO( "Payload of Size: " + std::to_string( readSize ) + " Bytes has been loaded from disk" ); - + FWE_LOG_TRACE( "Successfully retrieved persisted data of size " + std::to_string( size ) + " Bytes from file " + + filename ); return ErrorCode::SUCCESS; } diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/test/src/AwsIotConnectivityModuleTest.cpp b/src/offboardconnectivity/implementation/aws/iotcpp/test/src/AwsIotConnectivityModuleTest.cpp index 1d76f658..d716b350 100644 --- a/src/offboardconnectivity/implementation/aws/iotcpp/test/src/AwsIotConnectivityModuleTest.cpp +++ b/src/offboardconnectivity/implementation/aws/iotcpp/test/src/AwsIotConnectivityModuleTest.cpp @@ -111,14 +111,15 @@ class AwsIotConnectivityModuleTest : public ::testing::Test /** @brief Test attempting to disconnect when connection has already failed */ TEST_F( AwsIotConnectivityModuleTest, disconnectAfterFailedConnect ) { - std::shared_ptr m = std::make_shared(); - ASSERT_FALSE( m->connect( "", "", "", "", "", bootstrap ) ); + std::shared_ptr m = + std::make_shared( "", "", "", "", "", bootstrap ); + ASSERT_FALSE( m->connect() ); // disconnect must only disconnect when connection is available so this should not seg fault m->disconnect(); } /** @brief Test successful connection */ -TEST_F( AwsIotConnectivityModuleTest, connectSuccessfull ) +TEST_F( AwsIotConnectivityModuleTest, connectSuccessfully ) { std::string endpoint( "endpoint" ); auto con = setupValidConnection(); @@ -126,9 +127,10 @@ TEST_F( AwsIotConnectivityModuleTest, connectSuccessfull ) std::string requestedEndpoint; EXPECT_CALL( confBuilder, WithEndpoint( _ ) ).Times( 1 ).WillOnce( SaveArg<0>( &requestedEndpoint ) ); - std::shared_ptr m = std::make_shared(); + std::shared_ptr m = + std::make_shared( "key", "cert", "rootca", endpoint, "clientIdTest", bootstrap ); - ASSERT_TRUE( m->connect( "key", "cert", "rootca", endpoint, "clientIdTest", bootstrap ) ); + ASSERT_TRUE( m->connect() ); con->OnDisconnect( *con ); @@ -143,8 +145,9 @@ TEST_F( AwsIotConnectivityModuleTest, connectFailsOnClientBootstrapCreation ) { auto con = setupValidConnection(); - std::shared_ptr m = std::make_shared(); - ASSERT_FALSE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", nullptr ) ); + std::shared_ptr m = + std::make_shared( "key", "cert", "rootca", "endpoint", "clientIdTest", nullptr ); + ASSERT_FALSE( m->connect() ); } /** @brief Test trying to connect, where creation of the client fails */ @@ -153,8 +156,9 @@ TEST_F( AwsIotConnectivityModuleTest, connectFailsOnClientCreation ) auto con = setupValidConnection(); EXPECT_CALL( clientMock, operatorBool() ).Times( AtLeast( 1 ) ).WillRepeatedly( Return( false ) ); - std::shared_ptr m = std::make_shared(); - ASSERT_FALSE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ) ); + std::shared_ptr m = + std::make_shared( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ); + ASSERT_FALSE( m->connect() ); } /** @brief Test opening a connection, then interrupting it and resuming it */ @@ -162,8 +166,9 @@ TEST_F( AwsIotConnectivityModuleTest, connectionInterrupted ) { auto con = setupValidConnection(); - std::shared_ptr m = std::make_shared(); - ASSERT_TRUE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ) ); + std::shared_ptr m = + std::make_shared( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ); + ASSERT_TRUE( m->connect() ); con->OnConnectionInterrupted( *con, 10 ); con->OnConnectionResumed( *con, ReturnCode::AWS_MQTT_CONNECT_ACCEPTED, true ); @@ -197,13 +202,14 @@ TEST_F( AwsIotConnectivityModuleTest, connectFailsServerUnavailableWithDelay ) con->OnDisconnect( *con ); } ); - std::shared_ptr m = std::make_shared(); + std::shared_ptr m = + std::make_shared( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ); EXPECT_CALL( *con, Connect( _, _, _, _ ) ).Times( 1 ).WillOnce( Return( true ) ); // We want to see exactly one call to disconnect EXPECT_CALL( *con, Disconnect() ).Times( 1 ).WillRepeatedly( Return( true ) ); - ASSERT_FALSE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ) ); + ASSERT_FALSE( m->connect() ); std::this_thread::sleep_for( std::chrono::milliseconds( 20 ) ); killAllThread = true; completeThread.join(); @@ -213,8 +219,9 @@ TEST_F( AwsIotConnectivityModuleTest, connectFailsServerUnavailableWithDelay ) /** @brief Test subscribing without a configured topic, expect an error */ TEST_F( AwsIotConnectivityModuleTest, subscribeWithoutTopic ) { - std::shared_ptr m = std::make_shared(); - AwsIotChannel c( m.get(), nullptr ); + std::shared_ptr m = + std::make_shared( "", "", "", "", "", nullptr ); + AwsIotChannel c( m.get(), nullptr, m->mConnection ); ASSERT_EQ( c.subscribe(), ConnectivityError::NotConfigured ); c.invalidateConnection(); } @@ -222,8 +229,9 @@ TEST_F( AwsIotConnectivityModuleTest, subscribeWithoutTopic ) /** @brief Test subscribing without being connected, expect an error */ TEST_F( AwsIotConnectivityModuleTest, subscribeWithoutBeingConnected ) { - std::shared_ptr m = std::make_shared(); - AwsIotChannel c( m.get(), nullptr ); + std::shared_ptr m = + std::make_shared( "", "", "", "", "", nullptr ); + AwsIotChannel c( m.get(), nullptr, m->mConnection ); c.setTopic( "topic" ); ASSERT_EQ( c.subscribe(), ConnectivityError::NoConnection ); c.invalidateConnection(); @@ -234,10 +242,12 @@ TEST_F( AwsIotConnectivityModuleTest, subscribeSuccessfully ) { auto con = setupValidConnection(); - std::shared_ptr m = std::make_shared(); - AwsIotChannel c( m.get(), nullptr ); + std::shared_ptr m = + std::make_shared( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ); + + ASSERT_TRUE( m->connect() ); + AwsIotChannel c( m.get(), nullptr, m->mConnection ); - ASSERT_TRUE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ) ); c.setTopic( "topic" ); EXPECT_CALL( *con, Subscribe( _, _, _, _ ) ) .Times( 1 ) @@ -266,8 +276,9 @@ TEST_F( AwsIotConnectivityModuleTest, subscribeSuccessfully ) TEST_F( AwsIotConnectivityModuleTest, sendWithoutTopic ) { auto con = setupValidConnection(); - std::shared_ptr m = std::make_shared(); - AwsIotChannel c( m.get(), nullptr ); + std::shared_ptr m = + std::make_shared( "", "", "", "", "", nullptr ); + AwsIotChannel c( m.get(), nullptr, m->mConnection ); std::uint8_t input[] = { 0xca, 0xfe }; ASSERT_EQ( c.sendBuffer( input, sizeof( input ) ), ConnectivityError::NotConfigured ); c.invalidateConnection(); @@ -276,8 +287,9 @@ TEST_F( AwsIotConnectivityModuleTest, sendWithoutTopic ) /** @brief Test sending without a connection, expect an error */ TEST_F( AwsIotConnectivityModuleTest, sendWithoutConnection ) { - std::shared_ptr m = std::make_shared(); - AwsIotChannel c( m.get(), nullptr ); + std::shared_ptr m = + std::make_shared( "", "", "", "", "", nullptr ); + AwsIotChannel c( m.get(), nullptr, m->mConnection ); std::uint8_t input[] = { 0xca, 0xfe }; c.setTopic( "topic" ); ASSERT_EQ( c.sendBuffer( input, sizeof( input ) ), ConnectivityError::NoConnection ); @@ -288,9 +300,10 @@ TEST_F( AwsIotConnectivityModuleTest, sendWithoutConnection ) TEST_F( AwsIotConnectivityModuleTest, sendWrongInput ) { auto con = setupValidConnection(); - std::shared_ptr m = std::make_shared(); - AwsIotChannel c( m.get(), nullptr ); - ASSERT_TRUE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ) ); + std::shared_ptr m = + std::make_shared( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ); + AwsIotChannel c( m.get(), nullptr, m->mConnection ); + ASSERT_TRUE( m->connect() ); c.setTopic( "topic" ); ASSERT_EQ( c.sendBuffer( nullptr, 10 ), ConnectivityError::WrongInputData ); con->OnDisconnect( *con ); @@ -301,9 +314,11 @@ TEST_F( AwsIotConnectivityModuleTest, sendWrongInput ) TEST_F( AwsIotConnectivityModuleTest, sendTooBig ) { auto con = setupValidConnection(); - std::shared_ptr m = std::make_shared(); - AwsIotChannel c( m.get(), nullptr ); - ASSERT_TRUE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ) ); + std::shared_ptr m = + std::make_shared( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ); + AwsIotChannel c( m.get(), nullptr, m->mConnection ); + + ASSERT_TRUE( m->connect() ); c.setTopic( "topic" ); std::vector a; a.resize( c.getMaxSendSize() + 1U ); @@ -318,9 +333,11 @@ TEST_F( AwsIotConnectivityModuleTest, sendTooBig ) TEST_F( AwsIotConnectivityModuleTest, sendMultiple ) { auto con = setupValidConnection(); - std::shared_ptr m = std::make_shared(); - AwsIotChannel c( m.get(), nullptr ); - ASSERT_TRUE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ) ); + std::shared_ptr m = + std::make_shared( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ); + ASSERT_TRUE( m->connect() ); + AwsIotChannel c( m.get(), nullptr, m->mConnection ); + std::uint8_t input[] = { 0xca, 0xfe }; c.setTopic( "topic" ); std::list completeHandlers; @@ -365,8 +382,9 @@ TEST_F( AwsIotConnectivityModuleTest, sdkRAMExceeded ) { auto con = setupValidConnection(); - std::shared_ptr m = std::make_shared(); - ASSERT_TRUE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ) ); + std::shared_ptr m = + std::make_shared( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ); + ASSERT_TRUE( m->connect() ); auto &memMgr = AwsSDKMemoryManager::getInstance(); void *alloc1 = memMgr.AllocateMemory( 600000000, alignof( std::size_t ) ); @@ -377,13 +395,13 @@ TEST_F( AwsIotConnectivityModuleTest, sdkRAMExceeded ) ASSERT_NE( alloc2, nullptr ); memMgr.FreeMemory( alloc2 ); - AwsIotChannel c( m.get(), nullptr ); + AwsIotChannel c( m.get(), nullptr, m->mConnection ); c.setTopic( "topic" ); std::array input = { 0xCA, 0xFE }; const auto required = input.size() * sizeof( std::uint8_t ); { void *alloc3 = - memMgr.AllocateMemory( 50 * AwsIotChannel::MAXIMUM_IOT_SDK_HEAP_MEMORY_BYTES, alignof( std::size_t ) ); + memMgr.AllocateMemory( 50 * AwsSDKMemoryManager::getInstance().getLimit(), alignof( std::size_t ) ); ASSERT_NE( alloc3, nullptr ); ASSERT_EQ( c.sendBuffer( input.data(), input.size() * sizeof( std::uint8_t ) ), @@ -394,16 +412,16 @@ TEST_F( AwsIotConnectivityModuleTest, sdkRAMExceeded ) constexpr auto offset = alignof( std::max_align_t ); // check that we will be out of memory even if we allocate less than the max because of the allocator's offset // in the below alloc we are leaving space for the input - auto alloc4 = memMgr.AllocateMemory( AwsIotChannel::MAXIMUM_IOT_SDK_HEAP_MEMORY_BYTES - ( offset + required ), + auto alloc4 = memMgr.AllocateMemory( AwsSDKMemoryManager::getInstance().getLimit() - ( offset + required ), alignof( std::size_t ) ); ASSERT_NE( alloc4, nullptr ); ASSERT_EQ( c.sendBuffer( input.data(), sizeof( input ) ), ConnectivityError::QuotaReached ); memMgr.FreeMemory( alloc4 ); // check that allocation and hence send succeed when there is just enough memory - // here we subtract the offset twice - once for MAXIMUM_IOT_SDK_HEAP_MEMORY_BYTES and once for the input + // here we subtract the offset twice - once for MAXIMUM_AWS_SDK_HEAP_MEMORY_BYTES and once for the input auto alloc5 = memMgr.AllocateMemory( - AwsIotChannel::MAXIMUM_IOT_SDK_HEAP_MEMORY_BYTES - ( ( 2 * offset ) + required ), alignof( std::size_t ) ); + AwsSDKMemoryManager::getInstance().getLimit() - ( ( 2 * offset ) + required ), alignof( std::size_t ) ); ASSERT_NE( alloc5, nullptr ); std::list completeHandlers; @@ -431,16 +449,249 @@ TEST_F( AwsIotConnectivityModuleTest, sdkRAMExceeded ) c.invalidateConnection(); } +/** @brief Test sending file over MQTT without topic */ +TEST_F( AwsIotConnectivityModuleTest, sendFileOverMQTTNoTopic ) +{ + std::shared_ptr m = + std::make_shared( "", "", "", "", "", nullptr ); + AwsIotChannel c( m.get(), nullptr, m->mConnection ); + std::string filename{ "testFile.json" }; + ASSERT_EQ( c.sendFile( filename, 0 ), ConnectivityError::NotConfigured ); + c.invalidateConnection(); +} + +/** @brief Test sending file over MQTT, payload manager not defined */ +TEST_F( AwsIotConnectivityModuleTest, sendFileOverMQTTNoPayloadManager ) +{ + std::shared_ptr m = + std::make_shared( "", "", "", "", "", nullptr ); + AwsIotChannel c( m.get(), nullptr, m->mConnection ); + std::string filename{ "testFile.json" }; + c.setTopic( "topic" ); + ASSERT_EQ( c.sendFile( filename, 0 ), ConnectivityError::NotConfigured ); + c.invalidateConnection(); +} + +/** @brief Test sending file over MQTT, filename not defined */ +TEST_F( AwsIotConnectivityModuleTest, sendFileOverMQTTNoFilename ) +{ + std::shared_ptr m = + std::make_shared( "", "", "", "", "", nullptr ); + char buffer[PATH_MAX]; + if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) + { + const std::shared_ptr persistencyPtr = + std::make_shared( std::string( buffer ) + "/Persistency", 131072 ); + persistencyPtr->init(); + const std::shared_ptr payloadManager = std::make_shared( persistencyPtr ); + AwsIotChannel c( m.get(), payloadManager, m->mConnection ); + std::string filename; + c.setTopic( "topic" ); + ASSERT_EQ( c.sendFile( filename, 0 ), ConnectivityError::WrongInputData ); + c.invalidateConnection(); + } +} + +/** @brief Test sending file over MQTT, file size too big */ +TEST_F( AwsIotConnectivityModuleTest, sendFileOverMQTTBigFile ) +{ + std::shared_ptr m = + std::make_shared( "", "", "", "", "", nullptr ); + char buffer[PATH_MAX]; + if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) + { + const std::shared_ptr persistencyPtr = + std::make_shared( std::string( buffer ) + "/Persistency", 131072 ); + persistencyPtr->init(); + const std::shared_ptr payloadManager = std::make_shared( persistencyPtr ); + AwsIotChannel c( m.get(), payloadManager, m->mConnection ); + std::string filename = "testFile.json"; + c.setTopic( "topic" ); + ASSERT_EQ( c.sendFile( filename, 150000 ), ConnectivityError::WrongInputData ); + c.invalidateConnection(); + } +} + +TEST_F( AwsIotConnectivityModuleTest, sendFileOverMQTTNoFile ) +{ + char buffer[PATH_MAX]; + if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) + { + auto con = setupValidConnection(); + std::shared_ptr m = std::make_shared( + "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ); + + const std::shared_ptr persistencyPtr = + std::make_shared( std::string( buffer ) + "/Persistency", 131072 ); + persistencyPtr->init(); + const std::shared_ptr payloadManager = std::make_shared( persistencyPtr ); + + AwsIotChannel c( m.get(), payloadManager, m->mConnection ); + + ASSERT_TRUE( m->connect() ); + c.setTopic( "topic" ); + + std::string filename = "testFile.json"; + ASSERT_EQ( c.sendFile( filename, 100 ), ConnectivityError::WrongInputData ); + + con->OnDisconnect( *con ); + c.invalidateConnection(); + } +} + +TEST_F( AwsIotConnectivityModuleTest, sendFileOverMQTTSdkRAMExceeded ) +{ + char buffer[PATH_MAX]; + if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) + { + auto con = setupValidConnection(); + + std::shared_ptr m = std::make_shared( + "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ); + ASSERT_TRUE( m->connect() ); + + auto &memMgr = AwsSDKMemoryManager::getInstance(); + void *alloc1 = memMgr.AllocateMemory( 600000000, alignof( std::size_t ) ); + ASSERT_NE( alloc1, nullptr ); + memMgr.FreeMemory( alloc1 ); + + void *alloc2 = memMgr.AllocateMemory( 600000010, alignof( std::size_t ) ); + ASSERT_NE( alloc2, nullptr ); + memMgr.FreeMemory( alloc2 ); + + const std::shared_ptr persistencyPtr = + std::make_shared( std::string( buffer ) + "/Persistency", 131072 ); + persistencyPtr->init(); + + const std::shared_ptr payloadManager = std::make_shared( persistencyPtr ); + AwsIotChannel c( m.get(), payloadManager, m->mConnection ); + + c.setTopic( "topic" ); + // Fake file content + std::array input = { 0xCA, 0xFE }; + const auto required = input.size() * sizeof( std::uint8_t ); + std::string filename = "testFile.bin"; + { + void *alloc3 = + memMgr.AllocateMemory( 50 * AwsSDKMemoryManager::getInstance().getLimit(), alignof( std::size_t ) ); + ASSERT_NE( alloc3, nullptr ); + CollectionSchemeParams collectionSchemeParams; + collectionSchemeParams.persist = true; + collectionSchemeParams.compression = false; + ASSERT_EQ( c.sendFile( filename, input.size() * sizeof( std::uint8_t ), collectionSchemeParams ), + ConnectivityError::QuotaReached ); + memMgr.FreeMemory( alloc3 ); + } + { + constexpr auto offset = alignof( std::max_align_t ); + // check that we will be out of memory even if we allocate less than the max because of the allocator's + // offset in the below alloc we are leaving space for the input + auto alloc4 = memMgr.AllocateMemory( AwsSDKMemoryManager::getInstance().getLimit() - ( offset + required ), + alignof( std::size_t ) ); + ASSERT_NE( alloc4, nullptr ); + ASSERT_EQ( c.sendFile( filename, sizeof( input ) ), ConnectivityError::QuotaReached ); + memMgr.FreeMemory( alloc4 ); + } + + con->OnDisconnect( *con ); + c.invalidateConnection(); + } +} + +TEST_F( AwsIotConnectivityModuleTest, sendFileOverMQTT ) +{ + char buffer[PATH_MAX]; + if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) + { + int ret = std::system( "mkdir ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + + auto con = setupValidConnection(); + std::shared_ptr m = std::make_shared( + "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ); + + const std::shared_ptr persistencyPtr = + std::make_shared( std::string( buffer ) + "/Persistency", 131072 ); + persistencyPtr->init(); + + std::string testData = "abcdefjh!24$iklmnop!24$3@qaabcdefjh!24$iklmnop!24$3@qaabcdefjh!24$iklmnop!24$3@qabbbb"; + const uint8_t *stringData = reinterpret_cast( testData.data() ); + + std::string filename = "testFile.bin"; + persistencyPtr->write( stringData, testData.size(), DataType::EDGE_TO_CLOUD_PAYLOAD, filename ); + + const std::shared_ptr payloadManager = std::make_shared( persistencyPtr ); + + AwsIotChannel c( m.get(), payloadManager, m->mConnection ); + ASSERT_TRUE( m->connect() ); + c.setTopic( "topic" ); + + std::list completeHandlers; + EXPECT_CALL( *con, Publish( _, _, _, _, _ ) ) + .Times( 2 ) + .WillRepeatedly( Invoke( + [&completeHandlers]( const char *, + aws_mqtt_qos, + bool, + const struct aws_byte_buf &, + MqttConnection::OnOperationCompleteHandler &&onOpComplete ) noexcept -> bool { + completeHandlers.push_back( std::move( onOpComplete ) ); + return true; + } ) ); + + ASSERT_EQ( c.sendFile( filename, testData.size() ), ConnectivityError::Success ); + + // Confirm 1st (success as packetId is 1---v): + completeHandlers.front().operator()( *con, 1, 0 ); + completeHandlers.pop_front(); + + // Test callback return false + persistencyPtr->write( stringData, testData.size(), DataType::EDGE_TO_CLOUD_PAYLOAD, filename ); + ASSERT_EQ( c.sendFile( filename, testData.size() ), ConnectivityError::Success ); + + completeHandlers.front().operator()( *con, 0, 0 ); + completeHandlers.pop_front(); + + con->OnDisconnect( *con ); + c.invalidateConnection(); + } +} + +/** @brief Test sending file over MQTT, no connection */ +TEST_F( AwsIotConnectivityModuleTest, sendFileOverMQTTNoConnection ) +{ + std::shared_ptr m = + std::make_shared( "", "", "", "", "", nullptr ); + char buffer[PATH_MAX]; + if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) + { + const std::shared_ptr persistencyPtr = + std::make_shared( std::string( buffer ) + "/Persistency", 131072 ); + persistencyPtr->init(); + const std::shared_ptr payloadManager = std::make_shared( persistencyPtr ); + AwsIotChannel c( m.get(), payloadManager, m->mConnection ); + std::string filename = "testFile.json"; + c.setTopic( "topic" ); + ASSERT_EQ( c.sendFile( filename, 100 ), ConnectivityError::NoConnection ); + CollectionSchemeParams collectionSchemeParams; + collectionSchemeParams.persist = true; + collectionSchemeParams.compression = false; + ASSERT_EQ( c.sendFile( filename, 100, collectionSchemeParams ), ConnectivityError::NoConnection ); + c.invalidateConnection(); + } +} + /** @brief Test the separate thread with exponential backoff that tries to connect until connection succeeds */ TEST_F( AwsIotConnectivityModuleTest, asyncConnect ) { auto con = setupValidConnection(); - std::shared_ptr m = std::make_shared(); + std::shared_ptr m = std::make_shared( + "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap, true ); EXPECT_CALL( *con, Connect( _, _, _, _ ) ).Times( 1 ).WillOnce( Return( true ) ); - ASSERT_TRUE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap, true ) ); + ASSERT_TRUE( m->connect() ); std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); // first attempt should come immediately diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/test/src/PayloadManagerTest.cpp b/src/offboardconnectivity/implementation/aws/iotcpp/test/src/PayloadManagerTest.cpp index 43933eb1..2b4dcdcb 100644 --- a/src/offboardconnectivity/implementation/aws/iotcpp/test/src/PayloadManagerTest.cpp +++ b/src/offboardconnectivity/implementation/aws/iotcpp/test/src/PayloadManagerTest.cpp @@ -19,95 +19,142 @@ TEST( PayloadManagerTest, TestNoConnectionDataPersistency ) char buffer[PATH_MAX]; if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) { + int ret = std::system( "mkdir ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + const std::shared_ptr persistencyPtr = - std::make_shared( std::string( buffer ), 131072 ); + std::make_shared( std::string( buffer ) + "/Persistency", 131072 ); + persistencyPtr->erase( DataType::PAYLOAD_METADATA ); persistencyPtr->init(); - persistencyPtr->erase( DataType::EDGE_TO_CLOUD_PAYLOAD ); PayloadManager testSend( persistencyPtr ); - std::string testData = "abcdefjh!24$iklmnop!24$3@qaabcdefjh!24$iklmnop!24$3@qaabcdefjh!24$iklmnop!24$3@qabbbb"; - testData += + std::string testData1 = "abcdefjh!24$iklmnop!24$3@qaabcdefjh!24$iklmnop!24$3@qaabcdefjh!24$iklmnop!24$3@qabbbb"; + testData1 += '\0'; // make sure compression can handle null characters and does not stop after the first null character - testData += "testproto"; - size_t size = testData.size(); - const uint8_t *stringData = reinterpret_cast( testData.data() ); + testData1 += "testproto"; - CollectionSchemeParams collectionSchemeParams; - collectionSchemeParams.persist = true; - collectionSchemeParams.compression = false; - collectionSchemeParams.priority = 0; + const uint8_t *stringData1 = reinterpret_cast( testData1.data() ); + + CollectionSchemeParams collectionSchemeParams1; + collectionSchemeParams1.compression = false; + collectionSchemeParams1.eventID = 123456; + collectionSchemeParams1.triggerTime = 123456; + + CollectionSchemeParams collectionSchemeParams2; + collectionSchemeParams2.compression = false; + collectionSchemeParams2.eventID = 678910; + collectionSchemeParams2.triggerTime = 678910; + + ASSERT_EQ( testSend.storeData( stringData1, testData1.size(), collectionSchemeParams1 ), true ); - ASSERT_EQ( testSend.storeData( stringData, size, collectionSchemeParams ), true ); + std::string filename; - std::vector payloads; - testSend.retrieveData( payloads ); - ASSERT_EQ( payloads.size(), 1 ); + Json::Value files; + ASSERT_EQ( testSend.retrievePayloadMetadata( files ), ErrorCode::SUCCESS ); + ASSERT_EQ( files[0]["filename"], + filename + std::to_string( collectionSchemeParams1.eventID ) + "-" + + std::to_string( collectionSchemeParams1.triggerTime ) + ".bin" ); + ASSERT_EQ( files[0]["payloadSize"].asUInt(), testData1.size() ); + ASSERT_EQ( files[0]["compressionRequired"], false ); - ASSERT_TRUE( 0 == std::memcmp( payloads[0].c_str(), testData.c_str(), testData.size() ) ); - persistencyPtr->erase( DataType::EDGE_TO_CLOUD_PAYLOAD ); + filename = files[0]["filename"].asString(); + std::vector payload1( files[0]["payloadSize"].asUInt() ); + ASSERT_EQ( testSend.retrievePayload( payload1.data(), files[0]["payloadSize"].asUInt(), filename ), + ErrorCode::SUCCESS ); + ASSERT_EQ( testData1.size(), payload1.size() ); + ASSERT_TRUE( std::equal( testData1.begin(), testData1.begin() + testData1.size(), payload1.begin() ) ); + + persistencyPtr->erase( DataType::PAYLOAD_METADATA ); + + ret = std::system( "rm -rf ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); } } -TEST( PayloadManagerTest, TestNoConnectionNoDataPersistency ) +TEST( PayloadManagerTest, TestNoCacheAndPersistModule ) { char buffer[PATH_MAX]; if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) { - const std::shared_ptr persistencyPtr = - std::make_shared( std::string( buffer ), 131072 ); - persistencyPtr->init(); - persistencyPtr->erase( DataType::EDGE_TO_CLOUD_PAYLOAD ); - PayloadManager testSend( persistencyPtr ); + PayloadManager testSend( nullptr ); + + std::string testData = "abcdefjh!24$iklmnop!24$3@qaabcdefjh!24$iklmnop!24$3@qaabcdefjh!24$iklmnop!24$3@qabbbb"; + + const uint8_t *stringData = reinterpret_cast( testData.data() ); - std::string testData = "abcdefjh!24$^^77@p!24$3@"; - size_t size = testData.size(); - uint8_t *buf = new uint8_t[size]; - memcpy( buf, &testData, size ); CollectionSchemeParams collectionSchemeParams; - collectionSchemeParams.persist = false; collectionSchemeParams.compression = false; - collectionSchemeParams.priority = 0; - - ASSERT_FALSE( testSend.storeData( buf, size, collectionSchemeParams ) ); - persistencyPtr->erase( DataType::EDGE_TO_CLOUD_PAYLOAD ); - delete[] buf; + collectionSchemeParams.eventID = 123456; + collectionSchemeParams.triggerTime = 123456; + + std::string filename; + ASSERT_EQ( testSend.storeData( stringData, testData.size(), collectionSchemeParams ), false ); + ASSERT_EQ( testSend.retrievePayload( nullptr, 0, filename ), ErrorCode::INVALID_DATA ); + Json::Value files; + ASSERT_EQ( testSend.retrievePayloadMetadata( files ), ErrorCode::INVALID_DATA ); } } -TEST( PayloadManagerTest, TestCompression ) +TEST( PayloadManagerTest, TestEmptyBuffer ) { char buffer[PATH_MAX]; if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) { + int ret = std::system( "mkdir ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + const std::shared_ptr persistencyPtr = - std::make_shared( std::string( buffer ), 131072 ); + std::make_shared( std::string( buffer ) + "/Persistency", 131072 ); + persistencyPtr->erase( DataType::PAYLOAD_METADATA ); persistencyPtr->init(); - persistencyPtr->erase( DataType::EDGE_TO_CLOUD_PAYLOAD ); PayloadManager testSend( persistencyPtr ); - std::string testData = "abcdefjh!24$iklmnop!24$3@qrstuvwxyz"; + CollectionSchemeParams collectionSchemeParams1; + collectionSchemeParams1.compression = false; + collectionSchemeParams1.eventID = 123456; + collectionSchemeParams1.triggerTime = 123456; - CollectionSchemeParams collectionSchemeParams; - collectionSchemeParams.persist = true; - collectionSchemeParams.compression = true; - collectionSchemeParams.priority = 0; + std::string filename; + ASSERT_EQ( testSend.storeData( nullptr, 0, collectionSchemeParams1 ), false ); + ASSERT_EQ( testSend.retrievePayload( nullptr, 0, filename ), ErrorCode::INVALID_DATA ); + + ret = std::system( "rm -rf ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + } +} + +TEST( PayloadManagerTest, TestFailToAddMetadata ) +{ + char buffer[PATH_MAX]; + if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) + { + int ret = std::system( "mkdir ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + + const std::shared_ptr persistencyPtr = + std::make_shared( std::string( buffer ) + "/Persistency", 120 ); + persistencyPtr->init(); + PayloadManager testSend( persistencyPtr ); - std::string payloadData; - ASSERT_TRUE( snappy::Compress( testData.data(), testData.size(), &payloadData ) ); + std::string testData1 = "abcdefjh!24$iklmnop!24$3@qaabcdefjh!24$iklmnop!24$3@qaabcdefjh!24$iklmnop!24$3@qabbbb"; + const uint8_t *stringData1 = reinterpret_cast( testData1.data() ); - ASSERT_EQ( testSend.storeData( reinterpret_cast( payloadData.c_str() ), - payloadData.size(), - collectionSchemeParams ), - true ); + CollectionSchemeParams collectionSchemeParams1; + collectionSchemeParams1.compression = false; + collectionSchemeParams1.eventID = 123456; + collectionSchemeParams1.triggerTime = 123456; + ASSERT_EQ( testSend.storeData( stringData1, testData1.size(), collectionSchemeParams1 ), false ); - std::vector payloads; - testSend.retrieveData( payloads ); - ASSERT_EQ( payloads.size(), 1 ); - ASSERT_STRNE( payloads[0].c_str(), testData.c_str() ); + std::string filename = std::to_string( collectionSchemeParams1.eventID ) + "-" + + std::to_string( collectionSchemeParams1.triggerTime ) + ".bin"; - ASSERT_TRUE( snappy::Uncompress( payloads[0].c_str(), payloads[0].size(), &payloadData ) ); - ASSERT_STREQ( payloadData.c_str(), testData.c_str() ); + Json::Value files; + std::vector payload1( testData1.size() ); + ASSERT_EQ( testSend.retrievePayloadMetadata( files ), ErrorCode::SUCCESS ); + ASSERT_EQ( testSend.retrievePayload( payload1.data(), payload1.size(), filename ), ErrorCode::EMPTY ); + ASSERT_EQ( persistencyPtr->getSize( DataType::EDGE_TO_CLOUD_PAYLOAD, filename ), 0 ); - persistencyPtr->erase( DataType::EDGE_TO_CLOUD_PAYLOAD ); + ret = std::system( "rm -rf ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); } } diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/test/src/RemoteProfilerTest.cpp b/src/offboardconnectivity/implementation/aws/iotcpp/test/src/RemoteProfilerTest.cpp index b35620e7..17a5d918 100644 --- a/src/offboardconnectivity/implementation/aws/iotcpp/test/src/RemoteProfilerTest.cpp +++ b/src/offboardconnectivity/implementation/aws/iotcpp/test/src/RemoteProfilerTest.cpp @@ -44,6 +44,17 @@ class MockSender : public ISender } return mCallback( buf, size ); } + + ConnectivityError + sendFile( const std::string &filePath, + size_t size, + struct CollectionSchemeParams collectionSchemeParams = CollectionSchemeParams() ) override + { + static_cast( filePath ); + static_cast( size ); + static_cast( collectionSchemeParams ); + return ConnectivityError::TypeNotSupported; + } }; void diff --git a/src/platform/linux/CMakeLists.txt b/src/platform/linux/CMakeLists.txt index 20cec941..46e4c1ed 100644 --- a/src/platform/linux/CMakeLists.txt +++ b/src/platform/linux/CMakeLists.txt @@ -24,6 +24,10 @@ add_library( persistencymanagement/src/CacheAndPersist.cpp ) +find_path(JSONCPP_INCLUDE_DIR "json/json.h" PATH_SUFFIXES "jsoncpp") +find_library(JSONCPP_LIBRARY NAMES jsoncpp) +find_package(Boost 1.65.1 REQUIRED COMPONENTS filesystem) + # These are public includes so we can expose the headers to other consumers target_include_directories( ${libraryTargetName} @@ -33,14 +37,15 @@ target_include_directories( timemanagement/include resourcemanagement/include persistencymanagement/include - # Linux Kernel less than 5.10 require this header. - can + ${JSONCPP_INCLUDE_DIR} ) target_link_libraries( ${libraryTargetName} # From the Platform, this is what is used: logmanagement timemanagement IoTFleetWise::Platform::Utility + Boost::filesystem + ${JSONCPP_LIBRARY} ) # This allows the preprocessor to enable the code in the libraries diff --git a/src/platform/linux/persistencymanagement/include/CacheAndPersist.h b/src/platform/linux/persistencymanagement/include/CacheAndPersist.h index e919a1fd..b69ec89a 100644 --- a/src/platform/linux/persistencymanagement/include/CacheAndPersist.h +++ b/src/platform/linux/persistencymanagement/include/CacheAndPersist.h @@ -4,9 +4,10 @@ #pragma once // Includes -#include "ICacheAndPersist.h" #include +#include #include +#include #include #include @@ -17,11 +18,6 @@ constexpr size_t MAX_DATA_RD_SIZE = 131072; // file size assigned for an invalid data type constexpr size_t INVALID_FILE_SIZE = (size_t)-1; -// Define File names for the components using the lib -#define DECODER_MANIFEST_FILE "/DecoderManifest.bin" -#define COLLECTION_SCHEME_LIST_FILE "/CollectionSchemeList.bin" -#define COLLECTED_DATA_FILE "/CollectedData.bin" - namespace Aws { namespace IoTFleetWise @@ -32,6 +28,26 @@ namespace Linux { namespace PersistencyManagement { + +enum class ErrorCode +{ + SUCCESS = 0, + MEMORY_FULL, + EMPTY, + FILESYSTEM_ERROR, + INVALID_DATATYPE, + INVALID_DATA +}; + +enum class DataType +{ + EDGE_TO_CLOUD_PAYLOAD = 0, + PAYLOAD_METADATA, + COLLECTION_SCHEME_LIST, + DECODER_MANIFEST, + DEFAULT_DATA_TYPE +}; + /** * @brief Class that implements the persistency interface. Handles storage/retrieval for non-volatile memory(NVM). * @@ -40,10 +56,14 @@ namespace PersistencyManagement * Multiple components using this library e.g. CollectionScheme Manager, Payload Manager are operating on its separate * files hence are thread safe. */ -class CacheAndPersist : public ICacheAndPersist +// coverity[cert_dcl60_cpp_violation] false positive - class only defined once +// coverity[autosar_cpp14_m3_2_2_violation] false positive - class only defined once +// coverity[misra_cpp_2008_rule_3_2_2_violation] false positive - class only defined once +class CacheAndPersist { public: + CacheAndPersist() = default; /** * @brief Constructor * @param partitionPath Partition allocated for the NV storage (from config file) @@ -52,51 +72,109 @@ class CacheAndPersist : public ICacheAndPersist CacheAndPersist( const std::string &partitionPath, size_t maxPartitionSize ); /** - * @brief Writes to the non volatile memory(NVM). + * @brief Destructor - writes metadata from memory to the JSON file and cleans up the directory. + */ + virtual ~CacheAndPersist(); + + CacheAndPersist( const CacheAndPersist & ) = delete; + CacheAndPersist &operator=( const CacheAndPersist & ) = delete; + CacheAndPersist( CacheAndPersist && ) = delete; + CacheAndPersist &operator=( CacheAndPersist && ) = delete; + + /** + * @brief Writes to the non volatile memory(NVM) from buffer based on datatype and filename. * * @param bufPtr buffer location that contains the data to be written * @param size size of the data to be written * @param dataType specifies if the data is an edge to cloud payload, collectionScheme list, etc. + * @param filename specifies file for the data to be written to, only valid for edge to cloud payload * * @return ErrorCode SUCCESS if the write is successful, * MEMORY_FULL if the partition size is reached, - * INVALID_DATA if the buffer ptr is NULL + * INVALID_DATA if the buffer ptr is NULL, + * INVALID_DATATYPE if filename is empty, * FILESYSTEM_ERROR in case of any file I/O errors. */ - ErrorCode write( const uint8_t *bufPtr, size_t size, DataType dataType ) override; + virtual ErrorCode write( const uint8_t *bufPtr, + size_t size, + DataType dataType, + const std::string &filename = std::string() ); + + /** + * @brief Adds new file metadata to existing JSON object. + * + * @param metadata JSON object of the file metadata to persist + */ + void addMetadata( Json::Value &metadata ); /** - * @brief Gets the total size of data in the persistence library. - * Includes data written to the file as well as any data in flight i.e. in cache. + * @brief Gets the size of data based on the datatype. * @param dataType specifies if the data is an edge to cloud payload, collectionScheme list, etc. + * @param filename filename to get size for * - * @return total size of all the persisted data. + * @return size of the persisted data of specified type. */ - size_t getSize( DataType dataType ) override; + virtual size_t getSize( DataType dataType, const std::string &filename = std::string() ); /** - * @brief Reads the persisted data in a pre-allocated buffer. + * @brief Gets the size of metadata object transformed into string stored in memory. + * + * @return size of the metadata. + */ + size_t getMetadataSize(); + + /** + * @brief Reads the persisted data of specified datatype in a pre-allocated buffer. * * @param readBufPtr pointer to a buffer location where data should be read * @param size size to be read * @param dataType specifies if the data is an edge to cloud payload, collectionScheme list, etc. + * @param filename specifies file for the data to read, only valid for edge to cloud payload * * @return ErrorCode SUCCESS if the read is successful, - * EMPTY if there was no data to read - * INVALID_DATA if the buffer ptr is NULL + * EMPTY if there was no data to read, + * MEMORY_FULL if provided size is bigger than max size used by persistency library, + * INVALID_DATATYPE if provided datatype has no associated file, + * INVALID_DATA if the buffer ptr is NULL or the actual size differs from the provided, * FILESYSTEM_ERROR in case of any file I/O errors. */ - ErrorCode read( uint8_t *const readBufPtr, size_t size, DataType dataType ) override; + virtual ErrorCode read( uint8_t *const readBufPtr, + size_t size, + DataType dataType, + const std::string &filename = std::string() ); /** - * @brief Deletes all the persisted data for the specified data type. + * @brief Deletes persisted data for the specified data type and filename. * @param dataType specifies if the data is an edge to cloud payload, collectionScheme list, etc. + * @param filename specifies file for the data to delete, only valid for edge to cloud payload + * + * @return ErrorCode SUCCESS if the delete is successful or file does not exist, + * INVALID_DATATYPE if provided datatype does not have associated filename, + * FILESYSTEM_ERROR in case of any file I/O errors. + */ + ErrorCode erase( DataType dataType, const std::string &filename = std::string() ); + + /** + * @brief Deletes persisted metadata. * * @return ErrorCode SUCCESS if the delete is successful, - * EMPTY if there was no data to erase + * EMPTY if there was no data to erase, * FILESYSTEM_ERROR in case of any file I/O errors. */ - ErrorCode erase( DataType dataType ) override; + void clearMetadata(); + + /** + * @brief Returns a text representation of the error for better readable logging + * @param err the error code to convert to string + * @return never a nullptr, "UNKNOWN" in case of unknown string representation + * + */ + static const char *getErrorString( ErrorCode err ); + + /** + * @brief Returns metadata JSON object stored in memory + */ + Json::Value getMetadata(); /** * @brief Initializes the library by checking if the files exist and creating if necessary @@ -106,18 +184,135 @@ class CacheAndPersist : public ICacheAndPersist bool init(); private: + // Define File names for the components using the lib + static constexpr const char *DECODER_MANIFEST_FILE = "DecoderManifest.bin"; + static constexpr const char *COLLECTION_SCHEME_LIST_FILE = "CollectionSchemeList.bin"; + static constexpr const char *PAYLOAD_METADATA_FILE = "PayloadMetadata.json"; + // Folder to isolate persistency workspace + static constexpr const char *PERSISTENCY_WORKSPACE = "FWE_Persistency/"; + // Folder for payload files + static constexpr const char *COLLECTED_DATA_FOLDER = "CollectedData/"; + // Deprecated files to clean + static constexpr const char *DEPRECATED_COLLECTED_DATA_FILE = "CollectedData.bin"; + + static constexpr const char *METADATA_SCHEME_VERSION = "1.0.0"; + // Estimate size of metadata: expected average for Proto payload is 100 + static constexpr size_t ESTIMATED_METADATA_SIZE_PER_FILE = 400; + + std::string mPersistencyPath; + std::string mPersistencyWorkspace; std::string mDecoderManifestFile; std::string mCollectionSchemeListFile; - std::string mCollectedDataFile; - size_t mMaxPersistencePartitionSize; + std::string mPayloadMetadataFile; + std::string mCollectedDataPath; + std::uintmax_t mMaxPersistencePartitionSize; + + Json::Value mPersistedMetadata; + + /** + * @brief Writes JSON object from memory to the JSON file. + * + * @param metadata JSON object with new metadata to persist + * + * @return true if write operation succeeded + */ + bool writeMetadata( Json::Value &metadata ); + + /** + * @brief Returns filename for the specific datatype + * + * @param dataType specifies if the data is an edge to cloud payload, collectionScheme list, etc. + * + * @return filename defined for the datatype + */ + std::string getFileName( DataType dataType ); + + /** + * @brief Gets the size of all persisted data, incl. decoder manifest, collection scheme, metadata, and all + * payloads. + * + * @return size of all persisted data. + */ + std::uintmax_t getTotalSize(); + + /** + * @brief Writes to the non volatile memory(NVM) to the given path from buffer. + * + * @param bufPtr buffer location that contains the data to be written + * @param size size of the data to be written + * @param path filename to write data + * + * @return ErrorCode SUCCESS if the write is successful, + * MEMORY_FULL if the partition size is reached, + * INVALID_DATA if the buffer ptr is NULL, + * INVALID_DATATYPE if filename is empty + * FILESYSTEM_ERROR in case of any file I/O errors. + */ + ErrorCode write( const uint8_t *bufPtr, size_t size, std::string &path ); + + /** + * @brief Reads the persisted data from specified path in a pre-allocated buffer. + * + * @param readBufPtr pointer to a buffer location where data should be read + * @param size size to be read + * @param path file to read data from + * + * @return ErrorCode SUCCESS if the read is successful, + * EMPTY if there was no data to read, + * MEMORY_FULL if provided size is bigger than max size used by persistency library, + * INVALID_DATA if the buffer ptr is NULL or the actual size differs from the provided, + * INVALID_DATATYPE if filename is empty, + * FILESYSTEM_ERROR in case of any file I/O errors. + */ + ErrorCode read( uint8_t *const readBufPtr, size_t size, std::string &path ) const; + + /** + * @brief Reads the persisted metadata from JSON file in a JSON object. + * + * @param metadata JSON object to store persisted metadata + * + * @return ErrorCode SUCCESS if the read is successful, + * EMPTY if there was no data to read, + * INVALID_DATA if the buffer ptr is NULL, + * FILESYSTEM_ERROR in case of any file I/O errors. + */ + ErrorCode readMetadata( Json::Value &metadata ); + + /** + * @brief Deletes specified file + * @param path path to the file to delete. + * + * @return ErrorCode SUCCESS if the delete is successful or file does not exist, + * INVALID_DATATYPE if filename is empty, + * FILESYSTEM_ERROR in case of any file I/O errors. + */ + static ErrorCode erase( std::string &path ); + + /** + * @brief Gets the size of data store in the file + * @param path path to the file where the data is stored + * + * @return size of the persisted data in the file. + */ + static size_t getSize( const std::string &path ); + + /** + * @brief Deletes all files from the persistency folder that do not have associated metadata or are not reserved for + * collection schemes and decoder manifest. + * + * @return ErrorCode SUCCESS if cleanup was completed, + * FILESYSTEM_ERROR in case of any file I/O errors or if directory does not exist. + */ + ErrorCode cleanupPersistedData(); /** - * @brief checks if the specified file exists, if not creates a new one + * @brief This function is called when component is initialised. It deletes all files, including deprecated, that + * were written by FWE to the root persistency folder. * - * @param fileName Absolute file path along with the filename to be created - * @return SUCCESS if the file is created, FILESYSTEM_ERROR if not. + * @return ErrorCode SUCCESS if cleanup was completed, + * FILESYSTEM_ERROR in case of any file I/O errors or if directory does not exist. */ - static ErrorCode createFile( const std::string &fileName ); + ErrorCode cleanupDeprecatedFiles(); }; } // namespace PersistencyManagement } // namespace Linux diff --git a/src/platform/linux/persistencymanagement/include/ICacheAndPersist.h b/src/platform/linux/persistencymanagement/include/ICacheAndPersist.h deleted file mode 100644 index 2c2ef6c4..00000000 --- a/src/platform/linux/persistencymanagement/include/ICacheAndPersist.h +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -// Includes - -#include -#include - -namespace Aws -{ -namespace IoTFleetWise -{ -namespace Platform -{ -namespace Linux -{ -namespace PersistencyManagement -{ - -enum class ErrorCode -{ - SUCCESS = 0, - MEMORY_FULL, - EMPTY, - FILESYSTEM_ERROR, - INVALID_DATATYPE, - INVALID_DATA -}; - -enum class DataType -{ - EDGE_TO_CLOUD_PAYLOAD = 0, - COLLECTION_SCHEME_LIST, - DECODER_MANIFEST, - DEFAULT_DATA_TYPE -}; - -/** - * @brief Interface for a class that handles storage/retrieval for non-volatile memory(NVM). - */ -class ICacheAndPersist -{ - -public: - /** - * @brief destructor - */ - virtual ~ICacheAndPersist() = default; - - /** - * @brief Writes to the non volatile memory(NVM). - * - * @param bufPtr buffer location that contains the data to be written - * @param size size of the data to be written - * @param dataType specifies if the data is an edge to cloud payload, collectionScheme list, etc. - - * @return ErrorCode SUCCESS if the write is successful, - * MEMORY_FULL if the max partition size is reached, - * FILESYSTEM_ERROR in case of any file I/O errors. - */ - virtual ErrorCode write( const uint8_t *bufPtr, size_t size, DataType dataType ) = 0; - - /** - * @brief Gets the total size of the specified data in the persistence library. - * @param dataType specifies if the data is an edge to cloud payload, collectionScheme list, etc. - * - * @return total size of all the persisted data. - */ - virtual size_t getSize( DataType dataType ) = 0; - - /** - * @brief Reads the persisted data in a pre-allocated buffer. - * - * @param readBufPtr pointer to a buffer location where data should be read - * @param size size to be read - * @param dataType specifies if the data is an edge to cloud payload, collectionScheme list, etc. - * - * @return ErrorCode SUCCESS if the read is successful, - * EMPTY if there was no data to read - * FILESYSTEM_ERROR in case of any file I/O errors. - */ - virtual ErrorCode read( uint8_t *const readBufPtr, size_t size, DataType dataType ) = 0; - - /** - * @brief Deletes all the persisted data for the specified data type. - * @param dataType specifies if the data is an edge to cloud payload, collectionScheme list, etc. - * - * @return ErrorCode SUCCESS if the delete is successful, - * EMPTY if there was no data to erase - * FILESYSTEM_ERROR in case of any file I/O errors. - */ - virtual ErrorCode erase( DataType dataType ) = 0; - - /** - * @brief Returns a text representation of the error for better readable logging - * @param err the error code to convert to string - * @return never a nullptr, "UNKNOWN" in case of unknown string representation - * - */ - static const char *getErrorString( ErrorCode err ); -}; -} // namespace PersistencyManagement -} // namespace Linux -} // namespace Platform -} // namespace IoTFleetWise -} // namespace Aws diff --git a/src/platform/linux/persistencymanagement/src/CacheAndPersist.cpp b/src/platform/linux/persistencymanagement/src/CacheAndPersist.cpp index 848168cf..ed87be89 100644 --- a/src/platform/linux/persistencymanagement/src/CacheAndPersist.cpp +++ b/src/platform/linux/persistencymanagement/src/CacheAndPersist.cpp @@ -4,18 +4,20 @@ // Includes #include "CacheAndPersist.h" #include "LoggingModule.h" +#include #include #include #include -#include -#include using namespace Aws::IoTFleetWise::Platform::Linux::PersistencyManagement; CacheAndPersist::CacheAndPersist( const std::string &partitionPath, size_t maxPartitionSize ) - : mDecoderManifestFile{ partitionPath + DECODER_MANIFEST_FILE } - , mCollectionSchemeListFile{ partitionPath + COLLECTION_SCHEME_LIST_FILE } - , mCollectedDataFile{ partitionPath + COLLECTED_DATA_FILE } + : mPersistencyPath{ partitionPath } + , mPersistencyWorkspace{ partitionPath + PERSISTENCY_WORKSPACE } + , mDecoderManifestFile{ mPersistencyWorkspace + DECODER_MANIFEST_FILE } + , mCollectionSchemeListFile{ mPersistencyWorkspace + COLLECTION_SCHEME_LIST_FILE } + , mPayloadMetadataFile{ mPersistencyWorkspace + PAYLOAD_METADATA_FILE } + , mCollectedDataPath{ mPersistencyWorkspace + COLLECTED_DATA_FOLDER } , mMaxPersistencePartitionSize{ maxPartitionSize } { } @@ -23,288 +25,534 @@ CacheAndPersist::CacheAndPersist( const std::string &partitionPath, size_t maxPa bool CacheAndPersist::init() { - if ( createFile( mDecoderManifestFile ) != ErrorCode::SUCCESS ) + cleanupDeprecatedFiles(); + // coverity[misra_cpp_2008_rule_14_8_2_violation] - boost filesystem path header defines both template and and + // non-template function + if ( ( !boost::filesystem::exists( mPayloadMetadataFile ) ) || + ( readMetadata( mPersistedMetadata ) != ErrorCode::SUCCESS ) ) { - FWE_LOG_ERROR( "Failed to create decoder manifest file" ); - return false; + mPersistedMetadata["version"] = METADATA_SCHEME_VERSION; + mPersistedMetadata["files"] = Json::arrayValue; } - - if ( createFile( mCollectionSchemeListFile ) != ErrorCode::SUCCESS ) + else if ( mPersistedMetadata["version"] != METADATA_SCHEME_VERSION ) { - FWE_LOG_ERROR( "Failed to create collectionScheme list file" ); - return false; + FWE_LOG_ERROR( "Metadata scheme version is not supported. Ignoring persisted files." ) + mPersistedMetadata["version"] = METADATA_SCHEME_VERSION; + mPersistedMetadata["files"] = Json::arrayValue; + static_cast( erase( mPayloadMetadataFile ) ); + } + else + { + FWE_LOG_TRACE( "Successfully read persisted metadata" ); } - if ( createFile( mCollectedDataFile ) != ErrorCode::SUCCESS ) + // Create directory for persisted data if it doesn't exist + // coverity[misra_cpp_2008_rule_14_8_2_violation] - boost filesystem path header defines both template and and + // non-template function + if ( !boost::filesystem::exists( mPersistencyWorkspace ) ) { - FWE_LOG_ERROR( "Failed to create collected data file" ); + try + { + // coverity[misra_cpp_2008_rule_14_8_2_violation] - boost filesystem path header defines both template and + // and non-template function + boost::filesystem::create_directory( mPersistencyWorkspace ); + } + catch ( const boost::filesystem::filesystem_error &err ) + { + FWE_LOG_ERROR( "Failed to create directory for persistency workspace: " + std::string( err.what() ) ); + return false; + } + } - return false; + // Create directory for persisted data if it doesn't exist + // coverity[misra_cpp_2008_rule_14_8_2_violation] - boost filesystem path header defines both template and and + // non-template function + if ( !boost::filesystem::exists( mCollectedDataPath ) ) + { + try + { + // coverity[misra_cpp_2008_rule_14_8_2_violation] - boost filesystem path header defines both template and + // and non-template function + boost::filesystem::create_directory( mCollectedDataPath ); + } + catch ( const boost::filesystem::filesystem_error &err ) + { + FWE_LOG_ERROR( "Failed to create directory for collected data: " + std::string( err.what() ) ); + return false; + } } + // Clean directory from files without metadata at startup + cleanupPersistedData(); + FWE_LOG_INFO( "Persistency library successfully initialised" ); return true; } ErrorCode -CacheAndPersist::createFile( const std::string &fileName ) +CacheAndPersist::write( const uint8_t *bufPtr, size_t size, DataType dataType, const std::string &filename ) { - ErrorCode status = ErrorCode::SUCCESS; - - // Check if the file exists - std::ifstream existingFile( fileName.c_str(), std::ios_base::binary ); - - if ( !existingFile.is_open() ) + std::string path = getFileName( dataType ); + if ( dataType == DataType::EDGE_TO_CLOUD_PAYLOAD ) { - // File does not exist, create a new one - std::ofstream newFile( fileName.c_str(), std::ios_base::binary | std::ios_base::app ); - if ( !newFile.is_open() ) + if ( filename.empty() ) { - status = ErrorCode::FILESYSTEM_ERROR; + FWE_LOG_ERROR( "Failed to persist data: filename for the payload is empty " ); + return ErrorCode::INVALID_DATATYPE; } else { - newFile.close(); - status = ErrorCode::SUCCESS; + path += filename; } } - else - { - existingFile.close(); - status = ErrorCode::SUCCESS; - } - - return status; + return write( bufPtr, size, path ); } ErrorCode -CacheAndPersist::write( const uint8_t *bufPtr, size_t size, DataType dataType ) +CacheAndPersist::write( const uint8_t *bufPtr, size_t size, std::string &path ) { - ErrorCode status = ErrorCode::SUCCESS; - std::string fileName; - std::ofstream file; - if ( bufPtr == nullptr ) { + FWE_LOG_ERROR( "Failed to persist data: buffer is empty" ); return ErrorCode::INVALID_DATA; } - size_t collectionSchemeListSize = getSize( DataType::COLLECTION_SCHEME_LIST ); - size_t decoderManifestSize = getSize( DataType::DECODER_MANIFEST ); - size_t edgeToCloudPayloadSize = getSize( DataType::EDGE_TO_CLOUD_PAYLOAD ); - if ( collectionSchemeListSize + decoderManifestSize + edgeToCloudPayloadSize + size >= - mMaxPersistencePartitionSize ) + + if ( path.empty() ) + { + FWE_LOG_ERROR( "Failed to persist data: path is empty" ); + return ErrorCode::INVALID_DATATYPE; + } + + if ( getTotalSize() + size >= mMaxPersistencePartitionSize ) { + FWE_LOG_ERROR( "Failed to persist data: memory limit achieved" ); return ErrorCode::MEMORY_FULL; } - switch ( dataType ) + std::ofstream file( path.c_str(), std::ios_base::out | std::ios_base::binary ); + file.write( reinterpret_cast( bufPtr ), static_cast( size ) ); + if ( !file.good() ) { - case DataType::COLLECTION_SCHEME_LIST: - fileName = mCollectionSchemeListFile; - break; - - case DataType::DECODER_MANIFEST: - fileName = mDecoderManifestFile; - break; + FWE_LOG_ERROR( "Failed to persist data: write to the file failed" ); + return ErrorCode::FILESYSTEM_ERROR; + } + return ErrorCode::SUCCESS; +} - case DataType::EDGE_TO_CLOUD_PAYLOAD: - fileName = mCollectedDataFile; - break; +void +CacheAndPersist::addMetadata( Json::Value &metadata ) +{ + mPersistedMetadata["files"].append( metadata ); +} - default: - status = ErrorCode::INVALID_DATATYPE; - FWE_LOG_ERROR( "Invalid data type specified" ); - return status; +size_t +CacheAndPersist::getSize( DataType dataType, const std::string &filename ) +{ + std::string path = getFileName( dataType ); + if ( dataType == DataType::EDGE_TO_CLOUD_PAYLOAD ) + { + if ( filename.empty() ) + { + FWE_LOG_ERROR( "Could not get filesize: filename for the payload is empty " ); + return INVALID_FILE_SIZE; + } + else + { + path += filename; + } } + return getSize( path ); +} - if ( dataType == DataType::EDGE_TO_CLOUD_PAYLOAD ) +size_t +CacheAndPersist::getSize( const std::string &path ) +{ + if ( path.empty() ) { - // Payload is appended to the existing file - file.open( fileName.c_str(), std::ios_base::binary | std::ios_base::app ); + FWE_LOG_ERROR( "Could not get filesize, path is empty" ); + return INVALID_FILE_SIZE; } - else + // coverity[misra_cpp_2008_rule_14_8_2_violation] - boost filesystem path header defines both template and and + // non-template function + if ( !boost::filesystem::exists( path ) ) { - // CollectionScheme list and Decoder Manifest are overwritten - file.open( fileName.c_str(), std::ios_base::binary ); + FWE_LOG_TRACE( "File " + path + " does not exist" ); + return 0; } - - if ( !file.is_open() ) + // coverity[misra_cpp_2008_rule_14_8_2_violation] - boost filesystem path header defines both template and and + // non-template function + std::uintmax_t fileSize = boost::filesystem::file_size( path ); + // In theory this should never happen as we only read the files created in this system, but it could + // happen if someone copies persisted files from a different architecture (e.g. from arm64 to armhf). + if ( fileSize > SIZE_MAX ) { - status = ErrorCode::FILESYSTEM_ERROR; - FWE_LOG_ERROR( "Could not open file" ); + FWE_LOG_ERROR( "Filesize is larger than SIZE_MAX. File content can't be fully stored in memory." ); + return INVALID_FILE_SIZE; } - else + return static_cast( fileSize ); +} + +size_t +CacheAndPersist::getMetadataSize() +{ + // Estimate metadata size if one more file will be added + return ( mPersistedMetadata["files"].size() + 1 ) * ESTIMATED_METADATA_SIZE_PER_FILE; +} + +ErrorCode +CacheAndPersist::read( uint8_t *const readBufPtr, size_t size, DataType dataType, const std::string &filename ) +{ + std::string path = getFileName( dataType ); + if ( dataType == DataType::EDGE_TO_CLOUD_PAYLOAD ) { - file.write( reinterpret_cast( bufPtr ), static_cast( size ) ); - if ( !file.good() ) + if ( filename.empty() ) { - status = ErrorCode::FILESYSTEM_ERROR; - FWE_LOG_ERROR( "Error writing to the file" ); + FWE_LOG_ERROR( "Failed to read persisted data: filename for the payload is empty " ); + return ErrorCode::INVALID_DATATYPE; + } + else + { + path += filename; } - file.close(); } - return status; + + return read( readBufPtr, size, path ); } -size_t -CacheAndPersist::getSize( DataType dataType ) +ErrorCode +CacheAndPersist::read( uint8_t *const readBufPtr, size_t size, std::string &path ) const { - // get size of the file specified - std::string fileName; - size_t size = 0U; - struct stat res = {}; + if ( readBufPtr == nullptr ) + { + FWE_LOG_ERROR( "Failed to read persisted data: buffer is empty " ); + return ErrorCode::INVALID_DATA; + } - switch ( dataType ) + if ( size >= mMaxPersistencePartitionSize ) { - case DataType::COLLECTION_SCHEME_LIST: - fileName = mCollectionSchemeListFile; - break; + FWE_LOG_ERROR( "Failed to read persisted data: size is bigger than memory limit " ); + return ErrorCode::MEMORY_FULL; + } - case DataType::DECODER_MANIFEST: - fileName = mDecoderManifestFile; - break; + if ( path.empty() ) + { + FWE_LOG_ERROR( "Failed to read persisted data: path is empty " ); + return ErrorCode::INVALID_DATATYPE; + } + size_t filesize = getSize( path ); + if ( ( filesize == 0 ) || ( filesize == INVALID_FILE_SIZE ) ) + { + FWE_LOG_ERROR( "Failed to read persisted data: file " + path + " is empty " ); + return ErrorCode::EMPTY; + } - case DataType::EDGE_TO_CLOUD_PAYLOAD: - fileName = mCollectedDataFile; - break; + if ( size != filesize ) + { + FWE_LOG_ERROR( "Failed to read persisted data: requested size " + std::to_string( size ) + + " Bytes and actual size " + std::to_string( filesize ) + " Bytes differ" ); + return ErrorCode::INVALID_DATA; + } - default: - FWE_LOG_ERROR( "Invalid data type specified" ); - return INVALID_FILE_SIZE; + std::ifstream file( path.c_str(), std::ios_base::binary | std::ios_base::in ); + file.clear(); + file.read( reinterpret_cast( readBufPtr ), static_cast( size ) ); + if ( file.fail() ) + { + FWE_LOG_ERROR( "Error reading file" ); + return ErrorCode::FILESYSTEM_ERROR; } - // Get the file size - if ( stat( fileName.c_str(), &res ) == 0 ) + return ErrorCode::SUCCESS; +} + +ErrorCode +CacheAndPersist::readMetadata( Json::Value &metadata ) +{ + try + { + std::ifstream jsonStream( mPayloadMetadataFile ); + jsonStream >> metadata; + } + catch ( ... ) { - size = static_cast( res.st_size ); + FWE_LOG_ERROR( "Error reading JSON file with metadata" ); + static_cast( erase( mPayloadMetadataFile ) ); + return ErrorCode::FILESYSTEM_ERROR; } + return ErrorCode::SUCCESS; +} - return size; +ErrorCode +CacheAndPersist::erase( DataType dataType, const std::string &filename ) +{ + std::string path = getFileName( dataType ); + if ( dataType == DataType::EDGE_TO_CLOUD_PAYLOAD ) + { + if ( filename.empty() ) + { + FWE_LOG_ERROR( "Failed to erase persisted data: filename for the edge to cloud payload is empty" ); + return ErrorCode::INVALID_DATATYPE; + } + else + { + path += filename; + } + } + return erase( path ); } ErrorCode -CacheAndPersist::read( uint8_t *const readBufPtr, size_t size, DataType dataType ) +CacheAndPersist::erase( std::string &path ) { - ErrorCode status = ErrorCode::SUCCESS; - std::string fileName; + if ( path.empty() ) + { + FWE_LOG_ERROR( "Failed to delete persisted file: path is empty " ); + return ErrorCode::INVALID_DATATYPE; + } + // coverity[misra_cpp_2008_rule_14_8_2_violation] - boost filesystem path header defines both template and and + // non-template function + if ( !boost::filesystem::exists( path ) ) + { + FWE_LOG_INFO( "File does not exist, nothing to delete: " + path ); + return ErrorCode::SUCCESS; + } + // Delete the file + if ( std::remove( path.c_str() ) != 0 ) + { + FWE_LOG_ERROR( "Failed to delete persisted file: remove failed" ); + return ErrorCode::FILESYSTEM_ERROR; + } + return ErrorCode::SUCCESS; +} - if ( readBufPtr == nullptr ) +const char * +CacheAndPersist::getErrorString( ErrorCode err ) +{ + switch ( err ) { - return ErrorCode::INVALID_DATA; + case ErrorCode::SUCCESS: + return "ErrorCode::SUCCESS"; + case ErrorCode::MEMORY_FULL: + return "MEMORY_FULL"; + case ErrorCode::EMPTY: + return "EMPTY"; + case ErrorCode::FILESYSTEM_ERROR: + return "FILESYSTEM_ERROR"; + case ErrorCode::INVALID_DATATYPE: + return "INVALID_DATATYPE"; + case ErrorCode::INVALID_DATA: + return "INVALID_DATA"; + default: + return "UNKNOWN"; } +} +std::string +CacheAndPersist::getFileName( DataType dataType ) +{ switch ( dataType ) { case DataType::COLLECTION_SCHEME_LIST: - fileName = mCollectionSchemeListFile; - break; + return mCollectionSchemeListFile; case DataType::DECODER_MANIFEST: - fileName = mDecoderManifestFile; - break; + return mDecoderManifestFile; + + case DataType::PAYLOAD_METADATA: + return mPayloadMetadataFile; case DataType::EDGE_TO_CLOUD_PAYLOAD: - fileName = mCollectedDataFile; - break; + return mCollectedDataPath; default: - status = ErrorCode::INVALID_DATATYPE; FWE_LOG_ERROR( "Invalid data type specified" ); - return status; + return ""; } +} - std::ifstream file( fileName.c_str(), std::ios_base::binary | std::ios_base::in ); - - if ( !file.is_open() ) +std::uintmax_t +CacheAndPersist::getTotalSize() +{ + std::uintmax_t size = getMetadataSize(); + // coverity[misra_cpp_2008_rule_14_8_2_violation] - boost filesystem path header defines both template and and + // non-template function + if ( !boost::filesystem::exists( mPersistencyWorkspace ) ) { - FWE_LOG_ERROR( "Error opening file" ); - status = ErrorCode::FILESYSTEM_ERROR; + FWE_LOG_ERROR( "Directory " + mPersistencyWorkspace + " for persisted data does not exist" ); } else { - size_t fileSize = std::min( mMaxPersistencePartitionSize, getSize( dataType ) ); - if ( fileSize == 0 ) - { - status = ErrorCode::EMPTY; - } - else + try { - file.read( reinterpret_cast( readBufPtr ), - static_cast( std::min( size, fileSize ) ) ); - // coverity[uninit_use_in_call : SUPPRESS] - if ( file.fail() ) + // coverity[misra_cpp_2008_rule_14_8_2_violation] - boost filesystem path header defines both template and + // and non-template function + for ( boost::filesystem::recursive_directory_iterator it( mPersistencyWorkspace ); + it != boost::filesystem::recursive_directory_iterator(); + ++it ) { - FWE_LOG_ERROR( "Error reading file" ); - status = ErrorCode::FILESYSTEM_ERROR; + if ( !boost::filesystem::is_directory( *it ) ) + { + // actual metadata is stored in memory + if ( it->path().string() != mPayloadMetadataFile ) + { + size += boost::filesystem::file_size( *it ); + } + } } } - file.close(); + catch ( const boost::filesystem::filesystem_error &err ) + { + FWE_LOG_ERROR( "Error getting file size: " + std::string( err.what() ) ); + } } + return size; +} - return status; +void +CacheAndPersist::clearMetadata() +{ + mPersistedMetadata["files"] = Json::arrayValue; } ErrorCode -CacheAndPersist::erase( DataType dataType ) +CacheAndPersist::cleanupPersistedData() { - ErrorCode status = ErrorCode::SUCCESS; - std::string fileName; - - switch ( dataType ) + FWE_LOG_TRACE( "Cleaning up persistency workspace" ); + std::vector filenames; + for ( const auto &file : mPersistedMetadata["files"] ) { - case DataType::COLLECTION_SCHEME_LIST: - fileName = mCollectionSchemeListFile; - break; + filenames.push_back( mCollectedDataPath + file["filename"].asString() ); + } + // coverity[misra_cpp_2008_rule_14_8_2_violation] - boost filesystem path header defines both template and and + // non-template function + if ( !boost::filesystem::exists( mPersistencyWorkspace ) ) + { + FWE_LOG_ERROR( "Persistency directory " + mPersistencyWorkspace + " does not exist" ); + return ErrorCode::FILESYSTEM_ERROR; + } - case DataType::DECODER_MANIFEST: - fileName = mDecoderManifestFile; - break; + std::vector filesToDelete; + try + { + // coverity[misra_cpp_2008_rule_14_8_2_violation] - boost filesystem path header defines both template and and + // non-template function + for ( boost::filesystem::recursive_directory_iterator it( mPersistencyWorkspace ); + it != boost::filesystem::recursive_directory_iterator(); + ++it ) + { + if ( !boost::filesystem::is_directory( *it ) ) + { + std::string filename = it->path().string(); + if ( filename != mDecoderManifestFile && filename != mCollectionSchemeListFile && + filename != mPayloadMetadataFile && + ( std::find( filenames.begin(), filenames.end(), filename ) == filenames.end() ) ) + { + // Delete files after iterating over directory + filesToDelete.push_back( filename ); + } + } + } + } + catch ( const boost::filesystem::filesystem_error &err ) + { + FWE_LOG_ERROR( "Error during clean up: " + std::string( err.what() ) ); + return ErrorCode::FILESYSTEM_ERROR; + } - case DataType::EDGE_TO_CLOUD_PAYLOAD: - fileName = mCollectedDataFile; - break; + for ( auto &fileToDelete : filesToDelete ) + { + static_cast( erase( fileToDelete ) ); + } - default: - status = ErrorCode::INVALID_DATATYPE; - FWE_LOG_ERROR( "Invalid data type specified" ); - return status; + if ( !filesToDelete.empty() ) + { + FWE_LOG_TRACE( "Persistency folder was cleaned up successfully" ); } + return ErrorCode::SUCCESS; +} - // Delete the contents of the file - std::ofstream file( fileName.c_str(), std::ios_base::binary | std::ios_base::out | std::ios_base::trunc ); +ErrorCode +CacheAndPersist::cleanupDeprecatedFiles() +{ + FWE_LOG_TRACE( "Cleaning up persistency folder from old and deprecated files" ); + // coverity[misra_cpp_2008_rule_14_8_2_violation] - boost filesystem path header defines both template and and + // non-template function + if ( !boost::filesystem::exists( mPersistencyPath ) ) + { + FWE_LOG_ERROR( "Persistency directory " + mPersistencyPath + " does not exist" ); + return ErrorCode::FILESYSTEM_ERROR; + } - if ( !file.is_open() ) + std::vector filesToDelete; + try { - status = ErrorCode::FILESYSTEM_ERROR; - FWE_LOG_ERROR( "Error erasing the file" ); + // coverity[misra_cpp_2008_rule_14_8_2_violation] - boost filesystem path header defines both template and and + // non-template function + for ( boost::filesystem::directory_iterator it( mPersistencyPath ); + it != boost::filesystem::directory_iterator(); + ++it ) + { + // Deleted all old files from the persistency directory. FWE is supposed to work only in + // COLLECTED_DATA_FOLDER. + if ( !boost::filesystem::is_directory( *it ) ) + { + std::string filename = it->path().string(); + if ( ( filename == ( mPersistencyPath + DECODER_MANIFEST_FILE ) ) || + ( filename == ( mPersistencyPath + COLLECTION_SCHEME_LIST_FILE ) ) || + ( filename == ( mPersistencyPath + PAYLOAD_METADATA_FILE ) ) || + ( filename == ( mPersistencyPath + DEPRECATED_COLLECTED_DATA_FILE ) ) ) + { + // Delete files after iterating over directory + filesToDelete.push_back( filename ); + } + } + } } - else + catch ( const boost::filesystem::filesystem_error &err ) { + FWE_LOG_ERROR( "Error during clean up: " + std::string( err.what() ) ); + return ErrorCode::FILESYSTEM_ERROR; + } - file.close(); + for ( auto &fileToDelete : filesToDelete ) + { + static_cast( erase( fileToDelete ) ); } - return status; + if ( !filesToDelete.empty() ) + { + FWE_LOG_TRACE( "Deprecated files were successfully deleted from the persistency folder" ); + } + return ErrorCode::SUCCESS; } -const char * -ICacheAndPersist::getErrorString( ErrorCode err ) +bool +CacheAndPersist::writeMetadata( Json::Value &metadata ) { - switch ( err ) + Json::StreamWriterBuilder builder; + builder["indentation"] = ""; + // coverity[autosar_cpp14_a20_8_5_violation] Calling newStreamWriter() is a recommended usage from boost + // documentation + std::unique_ptr writer( builder.newStreamWriter() ); + std::ofstream outputFileStream( mPayloadMetadataFile ); + writer->write( metadata, &outputFileStream ); + + if ( !outputFileStream.good() ) { - case ErrorCode::SUCCESS: - return "ErrorCode::SUCCESS"; - case ErrorCode::MEMORY_FULL: - return "MEMORY_FULL"; - case ErrorCode::EMPTY: - return "EMPTY"; - case ErrorCode::FILESYSTEM_ERROR: - return "FILESYSTEM_ERROR"; - case ErrorCode::INVALID_DATATYPE: - return "INVALID_DATATYPE"; - case ErrorCode::INVALID_DATA: - return "INVALID_DATA"; - default: - return "UNKNOWN"; + FWE_LOG_ERROR( "Error writing metadata to the JSON file" ); + return false; } + return true; +} + +Json::Value +CacheAndPersist::getMetadata() +{ + return mPersistedMetadata["files"]; +} + +CacheAndPersist::~CacheAndPersist() +{ + writeMetadata( mPersistedMetadata ); + cleanupPersistedData(); } diff --git a/src/platform/linux/persistencymanagement/test/CacheAndPersistTest.cpp b/src/platform/linux/persistencymanagement/test/CacheAndPersistTest.cpp index 306099cb..598a8fc5 100644 --- a/src/platform/linux/persistencymanagement/test/CacheAndPersistTest.cpp +++ b/src/platform/linux/persistencymanagement/test/CacheAndPersistTest.cpp @@ -16,15 +16,16 @@ TEST( CacheAndPersistTest, testCollectionSchemePersistency ) char buffer[PATH_MAX]; if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) { - std::cout << " File being saved here: " << std::string( buffer ) << std::endl; - CacheAndPersist storage( std::string( buffer ), 131072 ); + int ret = std::system( "mkdir ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + CacheAndPersist storage( std::string( buffer ) + "/Persistency", 131072 ); ASSERT_TRUE( storage.init() ); // Delete any existing contents ASSERT_EQ( storage.erase( DataType::COLLECTION_SCHEME_LIST ), ErrorCode::SUCCESS ); // create a test obj - std::string testString = "hello CollectionScheme"; + std::string testString = "Test CollectionScheme"; size_t size = testString.size(); ASSERT_EQ( storage.write( reinterpret_cast( testString.c_str() ), @@ -42,6 +43,60 @@ TEST( CacheAndPersistTest, testCollectionSchemePersistency ) ASSERT_STREQ( out.c_str(), testString.c_str() ); ASSERT_EQ( storage.erase( DataType::COLLECTION_SCHEME_LIST ), ErrorCode::SUCCESS ); ASSERT_EQ( storage.getSize( DataType::COLLECTION_SCHEME_LIST ), 0 ); + + ret = std::system( "rm -rf ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + } +} + +// Unit Tests for the DM persistency +TEST( CacheAndPersistTest, testDecoderManifestPersistency ) +{ + char buffer[PATH_MAX]; + if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) + { + int ret = std::system( "mkdir ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + + CacheAndPersist storage( std::string( buffer ) + "/Persistency", 131072 ); + ASSERT_TRUE( storage.init() ); + // create a test obj + std::string testString = "Test Decoder Manifest"; + size_t size = testString.size(); + + ASSERT_EQ( + storage.write( reinterpret_cast( testString.c_str() ), size, DataType::DECODER_MANIFEST ), + ErrorCode::SUCCESS ); + ASSERT_EQ( storage.getSize( DataType::DECODER_MANIFEST ), size ); + + std::unique_ptr readBufPtr( new uint8_t[size]() ); + ASSERT_EQ( storage.read( readBufPtr.get(), size, DataType::DECODER_MANIFEST ), ErrorCode::SUCCESS ); + std::string out( reinterpret_cast( readBufPtr.get() ), size ); + std::cout << "File contents: " << out << std::endl; + ASSERT_STREQ( out.c_str(), testString.c_str() ); + ASSERT_EQ( storage.erase( DataType::DECODER_MANIFEST ), ErrorCode::SUCCESS ); + ASSERT_EQ( storage.getSize( DataType::DECODER_MANIFEST ), 0 ); + + ret = std::system( "rm -rf ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + } +} + +TEST( CacheAndPersistTest, testWriteEmptyBuffer ) +{ + char buffer[PATH_MAX]; + if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) + { + int ret = std::system( "mkdir ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + + CacheAndPersist storage( std::string( buffer ) + "/Persistency", 131072 ); + + ASSERT_TRUE( storage.init() ); + ASSERT_EQ( storage.write( nullptr, 0, DataType::COLLECTION_SCHEME_LIST ), ErrorCode::INVALID_DATA ); + + ret = std::system( "rm -rf ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); } } @@ -51,8 +106,10 @@ TEST( CacheAndPersistTest, testMemoryFull ) char buffer[PATH_MAX]; if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) { - std::cout << " File being saved here: " << std::string( buffer ) << std::endl; - CacheAndPersist storage( std::string( buffer ), 2 ); + int ret = std::system( "mkdir ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + + CacheAndPersist storage( std::string( buffer ) + "/Persistency", 2 ); ASSERT_TRUE( storage.init() ); // Delete any existing contents @@ -67,6 +124,9 @@ TEST( CacheAndPersistTest, testMemoryFull ) ErrorCode::MEMORY_FULL ); ASSERT_EQ( storage.erase( DataType::COLLECTION_SCHEME_LIST ), ErrorCode::SUCCESS ); ASSERT_EQ( storage.getSize( DataType::COLLECTION_SCHEME_LIST ), 0 ); + + ret = std::system( "rm -rf ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); } } @@ -76,8 +136,10 @@ TEST( CacheAndPersistTest, testInvalidDataType ) char buffer[PATH_MAX]; if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) { - std::cout << " File being saved here: " << std::string( buffer ) << std::endl; - CacheAndPersist storage( std::string( buffer ), 131072 ); + int ret = std::system( "mkdir ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + + CacheAndPersist storage( std::string( buffer ) + "/Persistency", 131072 ); ASSERT_TRUE( storage.init() ); @@ -93,34 +155,9 @@ TEST( CacheAndPersistTest, testInvalidDataType ) ASSERT_EQ( storage.getSize( DataType::DEFAULT_DATA_TYPE ), INVALID_FILE_SIZE ); ASSERT_EQ( storage.read( readBufPtr.get(), size, DataType::DEFAULT_DATA_TYPE ), ErrorCode::INVALID_DATATYPE ); ASSERT_EQ( storage.erase( DataType::DEFAULT_DATA_TYPE ), ErrorCode::INVALID_DATATYPE ); - } -} - -// Unit Tests for the DM persistency -TEST( CacheAndPersistTest, testDecoderManifestPersistency ) -{ - char buffer[PATH_MAX]; - if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) - { - std::cout << " File being saved here: " << std::string( buffer ) << std::endl; - CacheAndPersist storage( std::string( buffer ), 131072 ); - ASSERT_TRUE( storage.init() ); - // create a test obj - std::string testString = "hello Decoder Manifest"; - size_t size = testString.size(); - ASSERT_EQ( - storage.write( reinterpret_cast( testString.c_str() ), size, DataType::DECODER_MANIFEST ), - ErrorCode::SUCCESS ); - ASSERT_EQ( storage.getSize( DataType::DECODER_MANIFEST ), size ); - - std::unique_ptr readBufPtr( new uint8_t[size]() ); - ASSERT_EQ( storage.read( readBufPtr.get(), size, DataType::DECODER_MANIFEST ), ErrorCode::SUCCESS ); - std::string out( reinterpret_cast( readBufPtr.get() ), size ); - std::cout << "File contents: " << out << std::endl; - ASSERT_STREQ( out.c_str(), testString.c_str() ); - ASSERT_EQ( storage.erase( DataType::DECODER_MANIFEST ), ErrorCode::SUCCESS ); - ASSERT_EQ( storage.getSize( DataType::DECODER_MANIFEST ), 0 ); + ret = std::system( "rm -rf ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); } } @@ -130,8 +167,10 @@ TEST( CacheAndPersistTest, testCollectionSchemeListOverwrite ) char buffer[PATH_MAX]; if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) { - std::cout << " File being saved here: " << std::string( buffer ) << std::endl; - CacheAndPersist storage( std::string( buffer ), 131072 ); + int ret = std::system( "mkdir ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + + CacheAndPersist storage( std::string( buffer ) + "/Persistency", 131072 ); ASSERT_TRUE( storage.init() ); @@ -149,6 +188,11 @@ TEST( CacheAndPersistTest, testCollectionSchemeListOverwrite ) ErrorCode::SUCCESS ); ASSERT_EQ( storage.getSize( DataType::COLLECTION_SCHEME_LIST ), size1 ); + std::unique_ptr readBufPtr1( new uint8_t[size1]() ); + ASSERT_EQ( storage.read( readBufPtr1.get(), size1, DataType::COLLECTION_SCHEME_LIST ), ErrorCode::SUCCESS ); + std::string out1( reinterpret_cast( readBufPtr1.get() ), size1 ); + ASSERT_STREQ( out1.c_str(), testString1.c_str() ); + size_t size2 = testString2.size(); ASSERT_EQ( storage.write( reinterpret_cast( testString2.c_str() ), @@ -157,6 +201,11 @@ TEST( CacheAndPersistTest, testCollectionSchemeListOverwrite ) ErrorCode::SUCCESS ); ASSERT_EQ( storage.getSize( DataType::COLLECTION_SCHEME_LIST ), size2 ); + std::unique_ptr readBufPtr2( new uint8_t[size2]() ); + ASSERT_EQ( storage.read( readBufPtr2.get(), size2, DataType::COLLECTION_SCHEME_LIST ), ErrorCode::SUCCESS ); + std::string out2( reinterpret_cast( readBufPtr2.get() ), size2 ); + ASSERT_STREQ( out2.c_str(), testString2.c_str() ); + size_t size3 = testString3.size(); ASSERT_EQ( storage.write( reinterpret_cast( testString3.c_str() ), @@ -164,7 +213,15 @@ TEST( CacheAndPersistTest, testCollectionSchemeListOverwrite ) DataType::COLLECTION_SCHEME_LIST ), ErrorCode::SUCCESS ); ASSERT_EQ( storage.getSize( DataType::COLLECTION_SCHEME_LIST ), size3 ); + + std::unique_ptr readBufPtr3( new uint8_t[size3]() ); + ASSERT_EQ( storage.read( readBufPtr3.get(), size3, DataType::COLLECTION_SCHEME_LIST ), ErrorCode::SUCCESS ); + std::string out3( reinterpret_cast( readBufPtr3.get() ), size3 ); + ASSERT_STREQ( out3.c_str(), testString3.c_str() ); + ASSERT_EQ( storage.erase( DataType::COLLECTION_SCHEME_LIST ), ErrorCode::SUCCESS ); + ret = std::system( "rm -rf ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); } } @@ -174,12 +231,57 @@ TEST( CacheAndPersistTest, testReadEmptyFile ) char buffer[PATH_MAX]; if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) { - std::cout << " File being saved here: " << std::string( buffer ) << std::endl; - CacheAndPersist storage( std::string( buffer ), 131072 ); + int ret = std::system( "mkdir ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + + CacheAndPersist storage( std::string( buffer ) + "/Persistency", 131072 ); ASSERT_TRUE( storage.init() ); - std::string testString = "Empty File Test case"; + std::unique_ptr readBufPtr( new uint8_t[100]() ); + ASSERT_EQ( storage.read( readBufPtr.get(), 100, DataType::COLLECTION_SCHEME_LIST ), ErrorCode::EMPTY ); + + ret = std::system( "rm -rf ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + } +} + +// Tests reading a big file +TEST( CacheAndPersistTest, testReadBigFile ) +{ + char buffer[PATH_MAX]; + if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) + { + int ret = std::system( "mkdir ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + + CacheAndPersist storage( std::string( buffer ) + "/Persistency", 131072 ); + + ASSERT_TRUE( storage.init() ); + size_t size = 200000; + std::unique_ptr readBufPtr( new uint8_t[size]() ); + + ASSERT_EQ( storage.read( readBufPtr.get(), size, DataType::COLLECTION_SCHEME_LIST ), ErrorCode::MEMORY_FULL ); + + ret = std::system( "rm -rf ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + } +} + +// Tests read with size mismatch +TEST( CacheAndPersistTest, testReadSizeMismatch ) +{ + char buffer[PATH_MAX]; + if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) + { + int ret = std::system( "mkdir ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + + CacheAndPersist storage( std::string( buffer ) + "/Persistency", 131072 ); + + ASSERT_TRUE( storage.init() ); + + std::string testString = "Wrong size test case"; size_t size = testString.size(); ASSERT_EQ( storage.write( reinterpret_cast( testString.c_str() ), @@ -190,9 +292,10 @@ TEST( CacheAndPersistTest, testReadEmptyFile ) std::unique_ptr readBufPtr( new uint8_t[size]() ); - ASSERT_EQ( storage.read( readBufPtr.get(), size, DataType::COLLECTION_SCHEME_LIST ), ErrorCode::SUCCESS ); - ASSERT_EQ( storage.erase( DataType::COLLECTION_SCHEME_LIST ), ErrorCode::SUCCESS ); - ASSERT_EQ( storage.read( readBufPtr.get(), size, DataType::COLLECTION_SCHEME_LIST ), ErrorCode::EMPTY ); + ASSERT_EQ( storage.read( readBufPtr.get(), 123, DataType::COLLECTION_SCHEME_LIST ), ErrorCode::INVALID_DATA ); + + ret = std::system( "rm -rf ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); } } @@ -202,33 +305,38 @@ TEST( CacheAndPersistTest, testDataPersistency ) char buffer[PATH_MAX]; if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) { - std::cout << " File being saved here: " << std::string( buffer ) << std::endl; - CacheAndPersist storage( std::string( buffer ), 131072 ); + int ret = std::system( "mkdir ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + + CacheAndPersist storage( std::string( buffer ) + "/Persistency", 131072 ); - ASSERT_TRUE( storage.init() ); // Delete any existing contents - ASSERT_EQ( storage.erase( DataType::EDGE_TO_CLOUD_PAYLOAD ), ErrorCode::SUCCESS ); + ASSERT_TRUE( storage.init() ); // create a test obj std::string testString = "Store this data - 1"; size_t size = testString.size(); std::cout << "String size: " << size << std::endl; - ASSERT_EQ( storage.write( - reinterpret_cast( testString.c_str() ), size, DataType::EDGE_TO_CLOUD_PAYLOAD ), + std::string filename = "testfile.bin"; + ASSERT_EQ( storage.write( reinterpret_cast( testString.c_str() ), + size, + DataType::EDGE_TO_CLOUD_PAYLOAD, + filename ), ErrorCode::SUCCESS ); - size_t readSize = storage.getSize( DataType::EDGE_TO_CLOUD_PAYLOAD ); + size_t readSize = storage.getSize( DataType::EDGE_TO_CLOUD_PAYLOAD, filename ); std::cout << "testDataPersistency::readSize: " << readSize << std::endl; std::unique_ptr readBufPtr( new uint8_t[readSize]() ); - ASSERT_EQ( storage.read( readBufPtr.get(), readSize, DataType::EDGE_TO_CLOUD_PAYLOAD ), ErrorCode::SUCCESS ); + ASSERT_EQ( storage.read( readBufPtr.get(), readSize, DataType::EDGE_TO_CLOUD_PAYLOAD, filename ), + ErrorCode::SUCCESS ); std::string out( reinterpret_cast( readBufPtr.get() ), readSize ); ASSERT_STREQ( out.c_str(), testString.c_str() ); - ASSERT_EQ( storage.erase( DataType::EDGE_TO_CLOUD_PAYLOAD ), ErrorCode::SUCCESS ); - ASSERT_EQ( storage.getSize( DataType::EDGE_TO_CLOUD_PAYLOAD ), 0 ); + ret = std::system( "rm -rf ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); } } @@ -238,43 +346,151 @@ TEST( CacheAndPersistTest, testMultipleEventDataPersistency ) char buffer[PATH_MAX]; if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) { - std::cout << " File being saved here: " << std::string( buffer ) << std::endl; - CacheAndPersist storage( std::string( buffer ), 131072 ); + int ret = std::system( "mkdir ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + CacheAndPersist storage( std::string( buffer ) + "/Persistency", 131072 ); + + ASSERT_EQ( storage.erase( DataType::PAYLOAD_METADATA ), ErrorCode::SUCCESS ); ASSERT_TRUE( storage.init() ); - // Delete any existing contents - ASSERT_EQ( storage.erase( DataType::EDGE_TO_CLOUD_PAYLOAD ), ErrorCode::SUCCESS ); // create a test obj std::string testString1 = "abcdefjh!24$iklmnop!24$3@qrstuvwxyz"; size_t size1 = testString1.size(); std::cout << "String size: " << size1 << std::endl; + std::string filename1 = "testfile1.bin"; ASSERT_EQ( storage.write( reinterpret_cast( testString1.c_str() ), size1, - DataType::EDGE_TO_CLOUD_PAYLOAD ), + DataType::EDGE_TO_CLOUD_PAYLOAD, + filename1 ), ErrorCode::SUCCESS ); std::string testString2 = "34567089!@#$%^&*()_+?><"; size_t size2 = testString2.size(); std::cout << "String size: " << size2 << std::endl; + std::string filename2 = "testfile2.bin"; ASSERT_EQ( storage.write( reinterpret_cast( testString2.c_str() ), size2, - DataType::EDGE_TO_CLOUD_PAYLOAD ), + DataType::EDGE_TO_CLOUD_PAYLOAD, + filename2 ), + ErrorCode::SUCCESS ); + + std::vector payload1( size1 ); + ASSERT_EQ( storage.read( payload1.data(), payload1.size(), DataType::EDGE_TO_CLOUD_PAYLOAD, filename1 ), ErrorCode::SUCCESS ); + std::string out1( payload1.begin(), payload1.end() ); + ASSERT_STREQ( out1.c_str(), testString1.c_str() ); - size_t readSize = storage.getSize( DataType::EDGE_TO_CLOUD_PAYLOAD ); + ASSERT_EQ( storage.erase( DataType::EDGE_TO_CLOUD_PAYLOAD, filename1 ), ErrorCode::SUCCESS ); + ASSERT_EQ( storage.getSize( DataType::EDGE_TO_CLOUD_PAYLOAD, filename1 ), 0 ); - std::unique_ptr readBufPtr( new uint8_t[readSize]() ); + std::vector payload2( size2 ); + ASSERT_EQ( storage.read( payload2.data(), payload2.size(), DataType::EDGE_TO_CLOUD_PAYLOAD, filename2 ), + ErrorCode::SUCCESS ); + std::string out2( payload2.begin(), payload2.end() ); + ASSERT_STREQ( out2.c_str(), testString2.c_str() ); - ASSERT_EQ( storage.read( readBufPtr.get(), readSize, DataType::EDGE_TO_CLOUD_PAYLOAD ), ErrorCode::SUCCESS ); + ASSERT_EQ( storage.erase( DataType::EDGE_TO_CLOUD_PAYLOAD, filename2 ), ErrorCode::SUCCESS ); + ASSERT_EQ( storage.getSize( DataType::EDGE_TO_CLOUD_PAYLOAD, filename2 ), 0 ); - // Verify if the size of the data written is same as size of the data read back - // contents of the file need parsing and are being tested at a higher level test file - ASSERT_EQ( readSize, size1 + size2 ); + ret = std::system( "rm -rf ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + } +} + +TEST( CacheAndPersistTest, testReadEmptyBuffer ) +{ + char buffer[PATH_MAX]; + if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) + { + int ret = std::system( "mkdir ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + + CacheAndPersist storage( std::string( buffer ) + "/Persistency", 131072 ); + + ASSERT_TRUE( storage.init() ); + ASSERT_EQ( storage.read( nullptr, 0, DataType::COLLECTION_SCHEME_LIST ), ErrorCode::INVALID_DATA ); + + ret = std::system( "rm -rf ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + } +} + +TEST( CacheAndPersistTest, testNoFilenameForPayload ) +{ + char buffer[PATH_MAX]; + if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) + { + int ret = std::system( "mkdir ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + + CacheAndPersist storage( std::string( buffer ) + "/Persistency", 131072 ); + + ASSERT_TRUE( storage.init() ); + + std::string testString = "abcdefjh!24$iklmnop!24$3@qrstuvwxyz"; + size_t size = testString.size(); + + std::vector payload( size ); + + ASSERT_EQ( storage.write( + reinterpret_cast( testString.c_str() ), size, DataType::EDGE_TO_CLOUD_PAYLOAD ), + ErrorCode::INVALID_DATATYPE ); + ASSERT_EQ( storage.getSize( DataType::EDGE_TO_CLOUD_PAYLOAD ), INVALID_FILE_SIZE ); + ASSERT_EQ( storage.read( payload.data(), payload.size(), DataType::EDGE_TO_CLOUD_PAYLOAD ), + ErrorCode::INVALID_DATATYPE ); + ASSERT_EQ( storage.erase( DataType::EDGE_TO_CLOUD_PAYLOAD ), ErrorCode::INVALID_DATATYPE ); + + ret = std::system( "rm -rf ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + } +} + +TEST( CacheAndPersistTest, testCleanUpFunction ) +{ + char buffer[PATH_MAX]; + if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) + { + int ret = std::system( "mkdir ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); + + CacheAndPersist storage( std::string( buffer ) + "/Persistency", 131072 ); + + ASSERT_TRUE( storage.init() ); + + // create a test obj + std::string testString1 = "abcdefjh!24$iklmnop!24$3@qrstuvwxyz"; + size_t size1 = testString1.size(); + std::cout << "String size: " << size1 << std::endl; + + std::string filename1 = "testfile1.bin"; + ASSERT_EQ( storage.write( reinterpret_cast( testString1.c_str() ), + size1, + DataType::EDGE_TO_CLOUD_PAYLOAD, + filename1 ), + ErrorCode::SUCCESS ); + storage.clearMetadata(); + } +} + +TEST( CacheAndPersistTest, testInitAfterCleanUpFunction ) +{ + char buffer[PATH_MAX]; + if ( getcwd( buffer, sizeof( buffer ) ) != NULL ) + { + CacheAndPersist storage( std::string( buffer ) + "/Persistency", 131072 ); + ASSERT_TRUE( storage.init() ); + + std::string filename1 = "testfile1.bin"; + + ASSERT_EQ( storage.getSize( DataType::EDGE_TO_CLOUD_PAYLOAD, filename1 ), 0 ); + ASSERT_EQ( + storage.getSize( DataType::PAYLOAD_METADATA ), + 30 ); // 30 is a size for a default empty JSON metadata scheme, containing the version and the empty array - ASSERT_EQ( storage.erase( DataType::EDGE_TO_CLOUD_PAYLOAD ), ErrorCode::SUCCESS ); - ASSERT_EQ( storage.getSize( DataType::EDGE_TO_CLOUD_PAYLOAD ), 0 ); + int ret = std::system( "rm -rf ./Persistency" ); + ASSERT_FALSE( WIFEXITED( ret ) == 0 ); } } diff --git a/src/platform/linux/resourcemanagement/include/CPUUsageInfo.h b/src/platform/linux/resourcemanagement/include/CPUUsageInfo.h index ceeffe47..54b176c6 100644 --- a/src/platform/linux/resourcemanagement/include/CPUUsageInfo.h +++ b/src/platform/linux/resourcemanagement/include/CPUUsageInfo.h @@ -24,6 +24,7 @@ namespace Linux */ // coverity[cert_dcl60_cpp_violation] false positive - class only defined once // coverity[autosar_cpp14_m3_2_2_violation] false positive - class only defined once +// coverity[misra_cpp_2008_rule_3_2_2_violation] false positive - class only defined once class CPUUsageInfo { public: diff --git a/src/platform/linux/threadingmanagement/include/Signal.h b/src/platform/linux/threadingmanagement/include/Signal.h index 22a5e007..8e5fbde5 100644 --- a/src/platform/linux/threadingmanagement/include/Signal.h +++ b/src/platform/linux/threadingmanagement/include/Signal.h @@ -23,6 +23,7 @@ namespace Linux */ // coverity[cert_dcl60_cpp_violation] false positive - class only defined once // coverity[autosar_cpp14_m3_2_2_violation] false positive - class only defined once +// coverity[misra_cpp_2008_rule_3_2_2_violation] false positive - class only defined once class Signal { public: diff --git a/src/testingsupport/include/Testing.h b/src/testingsupport/include/Testing.h index 431cd14d..88ef1458 100644 --- a/src/testingsupport/include/Testing.h +++ b/src/testingsupport/include/Testing.h @@ -187,6 +187,21 @@ signalTypeToString( const testing::TestParamInfo &info ) } } +std::size_t operator""_KiB( unsigned long long sizeBytes ) +{ + return static_cast( sizeBytes * 1024 ); +} + +std::size_t operator""_MiB( unsigned long long sizeBytes ) +{ + return static_cast( sizeBytes * 1024 * 1024 ); +} + +std::size_t operator""_GiB( unsigned long long sizeBytes ) +{ + return static_cast( sizeBytes * 1024 * 1024 * 1024 ); +} + } // namespace TestingSupport } // namespace IoTFleetWise } // namespace Aws diff --git a/src/vehiclenetwork/CMakeLists.txt b/src/vehiclenetwork/CMakeLists.txt index 92f11752..92d1370c 100644 --- a/src/vehiclenetwork/CMakeLists.txt +++ b/src/vehiclenetwork/CMakeLists.txt @@ -9,9 +9,6 @@ set(SRCS src/ISOTPOverCANReceiver.cpp src/ISOTPOverCANSender.cpp src/ISOTPOverCANSenderReceiver.cpp - # Camera related - $<$:src/CameraDataSubscriber.cpp> - $<$:src/CameraDataPublisher.cpp> ) add_library( @@ -27,18 +24,11 @@ add_library( target_include_directories(${libraryTargetName} PUBLIC include) find_package(Boost 1.71.0 REQUIRED COMPONENTS thread) -if(FWE_FEATURE_CAMERA) - find_package(fastrtps REQUIRED) - find_package(fastcdr REQUIRED) -endif() set(LIBS IoTFleetWise::Platform::Linux IoTFleetWise::Platform::Utility Boost::thread - $<$:IoTFleetWise::Idls> - $<$:fastrtps> - $<$:fastcdr> ) target_link_libraries( @@ -71,20 +61,6 @@ install( include/datatypes ) -if (FWE_FEATURE_CAMERA) - install( - FILES - include/dds/SensorDataListener.h - include/dds/DDSDataTypes.h - include/dds/IDDSPublisher.h - include/dds/IDDSSubscriber.h - include/dds/CameraDataSubscriber.h - include/dds/CameraDataPublisher.h - DESTINATION - include/dds - ) -endif() - ### Tests ### # This is a list of tests that will compiled. # If adding a test, simply add the source file here @@ -93,19 +69,8 @@ set( test/ISOTPOverCANProtocolTest.cpp ) -if(FWE_FEATURE_CAMERA) - set( - testSources - ${testSources} - test/CameraDataSubscriberTest.cpp - test/CameraDataPublisherTest.cpp - ) -endif() - if(${BUILD_TESTING}) message(STATUS "Building tests for ${libraryTargetName}") - file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/test/CameraSubscriberTestPNG.png - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) # Add the executable targets foreach(testSource ${testSources}) diff --git a/src/vehiclenetwork/include/datatypes/OBDDataTypesUnitTestOnly.h b/src/vehiclenetwork/include/datatypes/OBDDataTypesUnitTestOnly.h index 1bc652cb..defbde69 100644 --- a/src/vehiclenetwork/include/datatypes/OBDDataTypesUnitTestOnly.h +++ b/src/vehiclenetwork/include/datatypes/OBDDataTypesUnitTestOnly.h @@ -1,8 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -// This files creates a big initialization function to create mode1PIDs -// So do not include this file in the agent code. Only in Unit tests +// This files creates a big initialization function to create mode1PIDs, +// so do not include this file in the main source code, only in unit tests. #include "OBDDataTypes.h" @@ -26,7 +26,7 @@ struct PIDInfo }; // This table is only used by unit test. -// The actual Edge Agent will only use decoding manifest received from AWS +// FWE will use decoder manifest received from the cloud const std::array mode1PIDs = { { { 0x00, 4, { { 0, 1.0, 0, 4 } } }, // PIDs supported [01 - 20] { 0x01, 4, { {} } }, // Monitor status since DTCs cleared.(MIL) status and number of DTCs. @@ -34,10 +34,10 @@ const std::array mode1PIDs = { { { 0x03, 2, { { 0, 1.0, 0, 1 }, { 1, 1.0, 0, 1 } } }, // Fuel system status { 0x04, 1, { { 0, (double)100 / 255, 0, 1 } } }, // Calculated engine load { 0x05, 1, { { 0, 1.0, -40.0, 1 } } }, // Engine coolant temperature - { 0x06, 1, { { 0, (double)100 / 128, -100.0, 1 } } }, // Short term fuel trim—Bank 1 - { 0x07, 1, { { 0, (double)100 / 128, -100.0, 1 } } }, // Long term fuel trim—Bank 1 - { 0x08, 1, { { 0, (double)100 / 128, -100.0, 1 } } }, // Short term fuel trim—Bank 2 - { 0x09, 1, { { 0, (double)100 / 128, -100.0, 1 } } }, // Long term fuel trim—Bank 2 + { 0x06, 1, { { 0, (double)100 / 128, -100.0, 1 } } }, // Short term fuel trim-bank 1 + { 0x07, 1, { { 0, (double)100 / 128, -100.0, 1 } } }, // Long term fuel trim-bank 1 + { 0x08, 1, { { 0, (double)100 / 128, -100.0, 1 } } }, // Short term fuel trim-bank 2 + { 0x09, 1, { { 0, (double)100 / 128, -100.0, 1 } } }, // Long term fuel trim-bank 2 { 0x0A, 1, { { 0, 3.0, 0, 1 } } }, // Fuel pressure (gauge pressure) { 0x0B, 1, { { 0, 1.0, 0, 1 } } }, // Intake manifold absolute pressure { 0x0C, 2, { { 0, 0.25, 0, 2 } } }, // Engine speed @@ -109,7 +109,7 @@ const std::array mode1PIDs = { { { 0x4E, 2, { { 0, 1.0, 0, 1 } } }, // Time since trouble codes cleared { 0x4F, 4, - { { 0, 1.0, 0, 1 }, { 1, 1.0, 0, 1 }, { 2, 1.0, 0, 1 }, { 3, 10.0, 0, 1 } } }, // Maximum value for Fuel–Air + { { 0, 1.0, 0, 1 }, { 1, 1.0, 0, 1 }, { 2, 1.0, 0, 1 }, { 3, 10.0, 0, 1 } } }, // Maximum value for Fuel-Air // equivalence ratio, // oxygen sensor voltage, oxygen sensor current, and intake manifold absolute pressure { 0x50, 4, { {} } }, // Maximum value for air flow rate from mass air flow sensor diff --git a/src/vehiclenetwork/include/dds/CameraDataPublisher.h b/src/vehiclenetwork/include/dds/CameraDataPublisher.h deleted file mode 100644 index 61a3acd7..00000000 --- a/src/vehiclenetwork/include/dds/CameraDataPublisher.h +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -// Includes -#include "CameraPubSubTypes.h" -#include "ClockHandler.h" -#include "IDDSPublisher.h" -#include "Signal.h" -#include "Thread.h" -#include "Timer.h" -#include - -namespace Aws -{ -namespace IoTFleetWise -{ -namespace VehicleNetwork -{ - -using namespace Aws::IoTFleetWise::Platform::Linux; - -/** - * @brief IDDSPublisher implementation for the Camera Sensor. - * This instance receives a request to retrieve Camera data for a given event - * for the Inspection layer, transforms that into a DDS Message and publishes it to the - * corresponding topic. - */ -class CameraDataPublisher : public IDDSPublisher -{ -public: - CameraDataPublisher(); - ~CameraDataPublisher() override; - - CameraDataPublisher( const CameraDataPublisher & ) = delete; - CameraDataPublisher &operator=( const CameraDataPublisher & ) = delete; - CameraDataPublisher( CameraDataPublisher && ) = delete; - CameraDataPublisher &operator=( CameraDataPublisher && ) = delete; - - bool init( const DDSDataSourceConfig &dataSourceConfig ) override; - - bool connect() override; - - bool disconnect() override; - - bool isAlive() override; - - void publishDataRequest( const DDSDataRequest &dataRequest ) override; - - /** - * @brief From DataWriterListener. - * method to be called when the Publisher is matched with a subscriber at the DDS Endpoint - * @param writer DataWriter, not used. - * @param info The publication matched status. The attribute we focus on current_count_change - */ - void on_publication_matched( DataWriter *writer, const PublicationMatchedStatus &info ) override; - -private: - // Start the bus thread - bool start(); - // Stop the bus thread - bool stop(); - // atomic state of the bus. If true, we should stop - bool shouldStop() const; - /** - * @brief Main work function. - * Typically this function waits on conditional variable until it's set. - * The conditional variable gets set when on_data_available is called by the DDS Stack. - * After that, in the current phase of implementation, - * we simply raise onSensorArtifactAvailable. - * Next step is to persist the data into a physical location. - * @param data data pointer from the stack. - */ - static void doWork( void *data ); - - Thread mThread; - std::atomic mShouldStop{ false }; - std::atomic mIsAlive{ false }; - std::atomic mRequestCompleted{ true }; - mutable std::mutex mThreadMutex; - Timer mTimer; - std::shared_ptr mClock = ClockHandler::getClock(); - Platform::Linux::Signal mWait; - DomainParticipant *mDDSParticipant{ nullptr }; - Publisher *mDDSPublisher{ nullptr }; - Topic *mDDSTopic{ nullptr }; - DataWriter *mDDSWriter{ nullptr }; - TypeSupport mDDStype{ new CameraDataRequestPubSubType() }; - mutable std::mutex mRequesMutex; - CameraDataRequest mRequest; -}; -} // namespace VehicleNetwork -} // namespace IoTFleetWise -} // namespace Aws diff --git a/src/vehiclenetwork/include/dds/CameraDataSubscriber.h b/src/vehiclenetwork/include/dds/CameraDataSubscriber.h deleted file mode 100644 index 9815f467..00000000 --- a/src/vehiclenetwork/include/dds/CameraDataSubscriber.h +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -// Includes -#include "CameraPubSubTypes.h" -#include "ClockHandler.h" -#include "IDDSSubscriber.h" -#include "Signal.h" -#include "Thread.h" -#include "Timer.h" -#include - -namespace Aws -{ -namespace IoTFleetWise -{ -namespace VehicleNetwork -{ - -using namespace Aws::IoTFleetWise::Platform::Linux; - -/** - * @brief IDDSSusbcriber implementation for the Camera Sensor. - * This object listens to Camera frame data on a DDS Topic and shares the - * resulting camera artifact data with the Data Inspection DDS Module via the SensorDataListener - * notification. - */ -class CameraDataSubscriber : public IDDSSubscriber -{ -public: - CameraDataSubscriber(); - ~CameraDataSubscriber() override; - - CameraDataSubscriber( const CameraDataSubscriber & ) = delete; - CameraDataSubscriber &operator=( const CameraDataSubscriber & ) = delete; - CameraDataSubscriber( CameraDataSubscriber && ) = delete; - CameraDataSubscriber &operator=( CameraDataSubscriber && ) = delete; - - bool init( const DDSDataSourceConfig &dataSourceConfig ) override; - - bool connect() override; - - bool disconnect() override; - - bool isAlive() override; - - /** - * @brief From DataReaderListener. - * method to be called by the Data Reader when the subscriber is matched with a new Writer on the Publisher side. - * @param reader DataReader, not used. - * @param info The subscription matched status. The attribute we focus on current_count_change - */ - void on_subscription_matched( DataReader *reader, const SubscriptionMatchedStatus &info ) override; - - /** - * @brief From DataReaderListener. - * method to be called by the Data Reader when a new message is put on the topic. After this - * call, the message is copied into the subscriber and it's removed from the topic. - * @param reader DataReader, temporary one that helps retrieving the data. - */ - void on_data_available( DataReader *reader ) override; - -private: - // Start the bus thread - bool start(); - // Stop the bus thread - bool stop(); - // atomic state of the bus. If true, we should stop - bool shouldStop() const; - /** - * @brief Main work function. - * Typically this function waits on conditional variable until it's set. - * The conditional variable gets set when on_data_available is called by the DDS Stack. - * After that, in the current phase of implementation, - * we simply raise onSensorArtifactAvailable. - * Next step is to persist the data into a physical location. - * @param data data pointer from the stack. - */ - static void doWork( void *data ); - // stores a Camera frame buffer on disk, in the same location provided in the fileName - // TODO: In the current form of the code, the CameraFrames received are appended to the - // the same file on disk. So the consumer of this file would not know how to recover the - // frames again/Split the file into frames. We want to do this correctly in future versions - // of this code, where we would store as a metadata the frame size and/or store the frames - // in separate artifacts. - static bool persistToStorage( const std::vector &frameBuffer, const std::string &fileName ); - - Thread mThread; - std::atomic mShouldStop{ false }; - std::atomic mIsAlive{ false }; - std::atomic mNewResponseReceived{ false }; - mutable std::mutex mThreadMutex; - Timer mTimer; - std::shared_ptr mClock = ClockHandler::getClock(); - Platform::Linux::Signal mWait; - CameraDataItem mDataItem; - DomainParticipant *mDDSParticipant{ nullptr }; - Subscriber *mDDSSubscriber{ nullptr }; - Topic *mDDSTopic{ nullptr }; - DataReader *mDDSReader{ nullptr }; - TypeSupport mDDStype{ new CameraDataItemPubSubType() }; - std::string mCachePath; - uint32_t mSourceID{ 0 }; -}; -} // namespace VehicleNetwork -} // namespace IoTFleetWise -} // namespace Aws diff --git a/src/vehiclenetwork/include/dds/DDSDataTypes.h b/src/vehiclenetwork/include/dds/DDSDataTypes.h deleted file mode 100644 index e0d5dfb9..00000000 --- a/src/vehiclenetwork/include/dds/DDSDataTypes.h +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -// Includes -#include -#include - -// Default settings for the DDS Transport. -const uint32_t SEND_BUFFER_SIZE_BYTES = 0; // Fallback to Kernel settings -const uint32_t RECEIVE_BUFFER_SIZE_BYTES = 0; // Fallback to Kernel settings -namespace Aws -{ -namespace IoTFleetWise -{ -namespace VehicleNetwork -{ -// DDS Types -// These type defs can be turned to the concrete ones based -// on the DDS implementation we use e.g. fast dds. -// For now, keeping them as basic C++ types. -using DDSDomainID = uint32_t; -using DDSTopicQoS = std::string; -using DDSTopicName = std::string; -using DDSReaderName = std::string; -using DDSWriterName = std::string; - -/** - * The category of the source e.g. Camera/Lidar etc. This is useful to help - * optimize the data reading on the DDS layer and select the right IDL. - */ -enum class SensorSourceType -{ - INVALID_SENSOR_TYPE, - CAMERA, - RADAR, - LIDAR -}; - -/** - * The Transport protocol to be used for the DDS Communication. - */ -enum class DDSTransportType -{ - INVALID_TRANSPORT, - SHM, - UDP, - TCP -}; - -/** - * @brief A structure representing the configuration of a data source - * available on a DDS Node. - * @param sourceID Unique identifier of the source accross the system. - * Can be the device identifier if the system has e.g. more than one camera. - * @param sourceType The category of the source e.g. Camera/Lidar etc. This is useful to help - * optimize the data reading on the DDS layer and select the right IDL. - * @param domainID Identifier of the DDS domain where this source node is. - * @param publishTopicName Name of the DDS topic used to request data from the device. - * @param subscribeTopicName Name of the DDS topic used to receive data from the device. - * @param topicQoS The DDS QoS to be applied on the reader and writer topics. - * @param readerName Name of the reader on the DDS network. Useful for debugging the DDS Traffic. - * @param writerName Name of the writer on the DDS network. Useful for debugging the DDS Traffic. - * @param temporaryCacheLocation AWS IoT FleetWise caches the chunks of data received from the DDS - * Network into disk locations an intermediate steps before sending them to the cloud location. - * Each source should provide a location. - */ -struct DDSDataSourceConfig -{ - uint32_t sourceID; - SensorSourceType sourceType; - DDSDomainID domainID; - DDSTopicName publishTopicName; - DDSTopicName subscribeTopicName; - DDSTopicQoS topicQoS; - DDSReaderName readerName; - DDSWriterName writerName; - std::string temporaryCacheLocation; - DDSTransportType transportType; -}; - -using DDSDataSourcesConfig = std::vector; - -} // namespace VehicleNetwork -} // namespace IoTFleetWise -} // namespace Aws diff --git a/src/vehiclenetwork/include/dds/IDDSPublisher.h b/src/vehiclenetwork/include/dds/IDDSPublisher.h deleted file mode 100644 index 8d5bdb00..00000000 --- a/src/vehiclenetwork/include/dds/IDDSPublisher.h +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -// Includes -#include "DDSDataTypes.h" -#include "datatypes/VehicleDataSourceTypes.h" -// DDS lib related headers -#include -#include -#include -#include -#include -#include -#include - -namespace Aws -{ -namespace IoTFleetWise -{ -namespace VehicleNetwork -{ -using namespace eprosima::fastdds::dds; -using namespace Aws::IoTFleetWise::Platform::Linux; -/** - * Metadata representing an DDS Data Request. - * Refer to DataInspection::EventMetadata for more details - */ -struct DDSDataRequest -{ - uint32_t eventID; - uint32_t negativeOffsetMs; - uint32_t positiveOffsetMs; -}; - -/** - * @brief Abstract DDS Publisher Interface. Every DDS Node that wants to publish data to a - * DDS topic should implement from this interface. - */ -class IDDSPublisher : public DataWriterListener -{ -public: - ~IDDSPublisher() override = default; - - /** - * @brief Inits a DDS Node publisher using the source properties. - * @param dataSourceConfig DDS Source configuration. - * @return True if the publisher has been setup correctly. This includes that the domain - * has been found and the participant has been attached the domain, and the topic has been - * created. - */ - virtual bool init( const DDSDataSourceConfig &dataSourceConfig ) = 0; - - /** - * @brief Connect to the DDS Topic and start the worker thread of the publisher. - * @return True if the thread is active and connection has been setup. - */ - virtual bool connect() = 0; - - /** - * @brief Stops the thread and disconnect from the DDS Topic. - * @return True if the thread is stopped and connection has been closed. - */ - virtual bool disconnect() = 0; - - /** - * @brief Checks if doamin participant is alive and still connected to the DDS domain. - * @return True if the connection is healthy. - */ - virtual bool isAlive() = 0; - - /** - * @brief Sends the content of dataRequest over the DDS Topic. - * This function is not thread safe, and thus as per the design, expected to be called from - * the DDS Module thread only. - * @return No return, this is a fire and forget - * - */ - virtual void publishDataRequest( const DDSDataRequest &dataRequest ) = 0; - - /** - * @return the DDS Domain ID - */ - inline DDSDomainID - getDDSDomainID() const - { - return mDDSDomainID; - } - - /** - * @return the DDS Topic - */ - inline DDSTopicName - getDDSTopic() const - { - return mDDSTopic; - } - - /** - * @return the Publisher name - */ - inline DDSWriterName - getDDSPublisherName() const - { - return mDDSwriterName; - } - - /** - * @return the Source Type - */ - inline SensorSourceType - getSensorSourceType() const - { - return mType; - } - -protected: - /** - * @brief Thread safe Channel ID generator - * @return returns a unique identifier of a channel. - */ - static uint32_t - generateChannelID() - { - static std::atomic channelID{ 0 }; - return ++channelID; - } - - SensorSourceType mType; - uint32_t mID; - DDSDomainID mDDSDomainID; - DDSTopicName mDDSTopic; - DDSWriterName mDDSwriterName; -}; -using DDSPublisherPtr = std::unique_ptr; -} // namespace VehicleNetwork -} // namespace IoTFleetWise -} // namespace Aws diff --git a/src/vehiclenetwork/include/dds/IDDSSubscriber.h b/src/vehiclenetwork/include/dds/IDDSSubscriber.h deleted file mode 100644 index 89f9b22c..00000000 --- a/src/vehiclenetwork/include/dds/IDDSSubscriber.h +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -// Includes -#include "DDSDataTypes.h" -#include "Listener.h" - -#include "datatypes/VehicleDataSourceTypes.h" -// DDS lib related headers -#include "SensorDataListener.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Aws -{ -namespace IoTFleetWise -{ -namespace VehicleNetwork -{ -using namespace eprosima::fastdds::dds; -using namespace Aws::IoTFleetWise::Platform::Linux; - -/** - * @brief Abstract DDS Subscriber Interface. Every DDS Node that wants to subscribe to data on a - * DDS topic should implement from this interface. - * This interface raises a notification from the SensorDataListener upon data arrival. - */ -class IDDSSubscriber : public ThreadListeners, public DataReaderListener -{ -public: - ~IDDSSubscriber() override = default; - - /** - * @brief Inits a DDS Node subscriber using the source properties. - * @param dataSourceConfig DDS Source configuration. - * @return True if the subscriber has been setup correctly. This includes that the domain - * has been found and the participant has been attached the domain, and the topic has been - * created. - */ - virtual bool init( const DDSDataSourceConfig &dataSourceConfig ) = 0; - - /** - * @brief Connect to the DDS Topic and start the worker thread of the subscriber. - * @return True if the thread is active and connection has been setup. - */ - virtual bool connect() = 0; - - /** - * @brief Stops the thread and disconnect from the DDS Topic. - * @return True if the thread is stopped and connection has been closed. - */ - virtual bool disconnect() = 0; - - /** - * @brief Checks if domain participant is alive and still connected to the DDS domain. - * @return True if the connection is healthy. - */ - virtual bool isAlive() = 0; - - /** - * @return the DDS Domain ID - */ - inline DDSDomainID - getDDSDomainID() const - { - return mDDSDomainID; - } - - /** - * @return the DDS Topic - */ - inline DDSTopicName - getDDSTopic() const - { - return mDDSTopic; - } - - /** - * @return the Subscriber name - */ - inline DDSWriterName - getDDSSubscriberName() const - { - return mDDSReaderName; - } - - /** - * @return the Source Type - */ - inline SensorSourceType - getSensorSourceType() const - { - return mType; - } - - /** - * @return the DDS Topic QoS - */ - inline DDSTopicQoS - getDDSTopicQoS() const - { - return mTopicQoS; - } - -protected: - /** - * @brief Thread safe Channel ID generator - * @return returns a unique identifier of a channel. - */ - static uint32_t - generateChannelID() - { - static std::atomic channelID{ 0 }; - return ++channelID; - } - - SensorSourceType mType; - uint32_t mID; - DDSDomainID mDDSDomainID; - DDSTopicName mDDSTopic; - DDSWriterName mDDSReaderName; - std::string mTemporaryCacheLocation; - DDSTopicQoS mTopicQoS; -}; -using DDSSubscriberPtr = std::unique_ptr; -} // namespace VehicleNetwork -} // namespace IoTFleetWise -} // namespace Aws diff --git a/src/vehiclenetwork/include/dds/SensorDataListener.h b/src/vehiclenetwork/include/dds/SensorDataListener.h deleted file mode 100644 index cfaeb23a..00000000 --- a/src/vehiclenetwork/include/dds/SensorDataListener.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -#pragma once - -#include - -namespace Aws -{ -namespace IoTFleetWise -{ -namespace VehicleNetwork -{ - -struct SensorArtifactMetadata -{ - uint32_t sourceID{ 0 }; - std::string path; -}; - -class SensorDataListener -{ - -public: - virtual ~SensorDataListener() = default; - - /** - * @brief This notification is intended to inform listeners that a Sensor Artifact - * has been made available to the system. The location of the artifact and its - * data format are specified in the Metadata Object. This is a fire and forget - * notification. - * @param artifactMetadata consiting of ( initially ): - * - path : path of the artifact in the file system - * - further other metadata can be added as we progress on dev. - */ - virtual void onSensorArtifactAvailable( const SensorArtifactMetadata &artifactMetadata ) = 0; -}; -} // namespace VehicleNetwork -} // namespace IoTFleetWise -} // namespace Aws diff --git a/src/vehiclenetwork/src/CameraDataPublisher.cpp b/src/vehiclenetwork/src/CameraDataPublisher.cpp deleted file mode 100644 index c0ccf91b..00000000 --- a/src/vehiclenetwork/src/CameraDataPublisher.cpp +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Includes -#include "dds/CameraDataPublisher.h" -#include "ClockHandler.h" -#include "LoggingModule.h" -#include -#include -#include - -namespace Aws -{ -namespace IoTFleetWise -{ -namespace VehicleNetwork -{ -CameraDataPublisher::CameraDataPublisher() -{ - mID = generateChannelID(); -} - -CameraDataPublisher::~CameraDataPublisher() -{ - // To make sure the thread stops during teardown of tests. - if ( mThread.isValid() && mThread.isActive() ) - { - stop(); - } - - // Clean up the resources - if ( mDDSWriter != nullptr ) - { - mDDSPublisher->delete_datawriter( mDDSWriter ); - } - - if ( mDDSPublisher != nullptr ) - { - mDDSParticipant->delete_publisher( mDDSPublisher ); - } - if ( mDDSTopic != nullptr ) - { - mDDSParticipant->delete_topic( mDDSTopic ); - } - - DomainParticipantFactory::get_instance()->delete_participant( mDDSParticipant ); -} - -bool -CameraDataPublisher::init( const DDSDataSourceConfig &dataSourceConfig ) -{ - - // DDS Settings - DomainParticipantQos participantQos; - participantQos.name( dataSourceConfig.writerName ); - // Configure the Transport - // First disable the default Transport - participantQos.transport().use_builtin_transports = false; - // UDP Transport - if ( dataSourceConfig.transportType == DDSTransportType::UDP ) - { - auto udpTransport = std::make_shared(); - udpTransport->sendBufferSize = SEND_BUFFER_SIZE_BYTES; - udpTransport->receiveBufferSize = RECEIVE_BUFFER_SIZE_BYTES; - udpTransport->non_blocking_send = true; - participantQos.transport().user_transports.push_back( udpTransport ); - } - // Shared Memory Transport - else if ( dataSourceConfig.transportType == DDSTransportType::SHM ) - { - std::shared_ptr shm_transport = - std::make_shared(); - // Link the Transport Layer to the Participant. - participantQos.transport().user_transports.push_back( shm_transport ); - } - else if ( dataSourceConfig.transportType == DDSTransportType::TCP ) - { - FWE_LOG_TRACE( "TCP Transport is NOT yet supported" ); - return false; - } - // Create the DDS participant - mDDSParticipant = - DomainParticipantFactory::get_instance()->create_participant( dataSourceConfig.domainID, participantQos ); - - if ( mDDSParticipant == nullptr ) - { - return false; - } - // Register the DDS participant - mDDStype.register_type( mDDSParticipant ); - - // Create the DDS Topic. - mDDSTopic = mDDSParticipant->create_topic( - dataSourceConfig.publishTopicName, mDDStype.get_type_name(), /*dataSourceConfig.topicQoS*/ TOPIC_QOS_DEFAULT ); - if ( mDDSTopic == nullptr ) - { - return false; - } - - mDDSPublisher = mDDSParticipant->create_publisher( PUBLISHER_QOS_DEFAULT ); - if ( mDDSPublisher == nullptr ) - { - return false; - } - - mDDSWriter = mDDSPublisher->create_datawriter( mDDSTopic, DATAWRITER_QOS_DEFAULT, this ); - if ( mDDSWriter == nullptr ) - { - return false; - } - return true; -} - -bool -CameraDataPublisher::start() -{ - // Prevent concurrent stop/init - std::lock_guard lock( mThreadMutex ); - // On multi core systems the shared variable mShouldStop must be updated for - // all cores before starting the thread otherwise thread will directly end - mShouldStop.store( false ); - if ( !mThread.create( doWork, this ) ) - { - FWE_LOG_TRACE( "Thread failed to start" ); - } - else - { - FWE_LOG_TRACE( "Thread started" ); - mThread.setThreadName( "fwVNDDSCamPub" + std::to_string( mID ) ); - } - return mThread.isActive() && mThread.isValid(); -} - -bool -CameraDataPublisher::stop() -{ - std::lock_guard lock( mThreadMutex ); - mShouldStop.store( true, std::memory_order_relaxed ); - mWait.notify(); - mThread.release(); - mShouldStop.store( false, std::memory_order_relaxed ); - FWE_LOG_TRACE( "Thread stopped" ); - return !mThread.isActive(); -} - -bool -CameraDataPublisher::shouldStop() const -{ - return mShouldStop.load( std::memory_order_relaxed ); -} - -void -CameraDataPublisher::doWork( void *data ) -{ - - CameraDataPublisher *publisher = static_cast( data ); - - while ( !publisher->shouldStop() ) - { - // Wait for data to arrive from the DDS Network. - publisher->mWait.wait( Platform::Linux::Signal::WaitWithPredicate ); - // We need now to send the request. - // Reset the request to avoid sending it during shutdown - std::lock_guard lock( publisher->mRequesMutex ); - if ( !publisher->mRequestCompleted.load() ) - { - publisher->mDDSWriter->write( &publisher->mRequest ); - publisher->mRequestCompleted.store( true ); - FWE_LOG_TRACE( "Data request send to the remote node" ); - } - } -} - -bool -CameraDataPublisher::connect() -{ - - return start(); -} - -bool -CameraDataPublisher::disconnect() -{ - return stop(); -} - -bool -CameraDataPublisher::isAlive() -{ - return ( mIsAlive.load( std::memory_order_relaxed ) && mThread.isValid() && mThread.isActive() ); -} - -void -CameraDataPublisher::on_publication_matched( DataWriter *writer, const PublicationMatchedStatus &info ) -{ - (void)writer; // unused variable - - if ( info.current_count_change == 1 ) - { - mIsAlive.store( true, std::memory_order_relaxed ); - FWE_LOG_TRACE( "A subscriber is available" ); - } - else if ( info.current_count_change == -1 ) - { - mIsAlive.store( false, std::memory_order_relaxed ); - } -} - -void -CameraDataPublisher::publishDataRequest( const DDSDataRequest &dataRequest ) -{ - // Critical section only to make sure that we don't overwrite the - // ongoing request processing in doWork. - std::lock_guard lock( mRequesMutex ); - { - mRequest.dataItemId( dataRequest.eventID ); - mRequest.positiveOffsetMs( dataRequest.positiveOffsetMs ); - mRequest.negativeOffsetMs( dataRequest.negativeOffsetMs ); - mRequestCompleted.store( false ); - } - - FWE_LOG_TRACE( "Request queued for sending" ); - mWait.notify(); -} - -} // namespace VehicleNetwork -} // namespace IoTFleetWise -} // namespace Aws diff --git a/src/vehiclenetwork/src/CameraDataSubscriber.cpp b/src/vehiclenetwork/src/CameraDataSubscriber.cpp deleted file mode 100644 index e4298170..00000000 --- a/src/vehiclenetwork/src/CameraDataSubscriber.cpp +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Includes -#include "dds/CameraDataSubscriber.h" -#include "ClockHandler.h" -#include "LoggingModule.h" -#include -#include -#include -#include -#include -namespace Aws -{ -namespace IoTFleetWise -{ -namespace VehicleNetwork -{ -CameraDataSubscriber::CameraDataSubscriber() -{ - mID = generateChannelID(); -} - -CameraDataSubscriber::~CameraDataSubscriber() -{ - // To make sure the thread stops during teardown of tests. - if ( mThread.isValid() && mThread.isActive() ) - { - stop(); - } - - // Clean up the resources - if ( mDDSReader != nullptr ) - { - mDDSSubscriber->delete_datareader( mDDSReader ); - } - - if ( mDDSSubscriber != nullptr ) - { - mDDSParticipant->delete_subscriber( mDDSSubscriber ); - } - if ( mDDSTopic != nullptr ) - { - mDDSParticipant->delete_topic( mDDSTopic ); - } - DomainParticipantFactory::get_instance()->delete_participant( mDDSParticipant ); -} - -bool -CameraDataSubscriber::init( const DDSDataSourceConfig &dataSourceConfig ) -{ - // DDS Settings - mCachePath = dataSourceConfig.temporaryCacheLocation; - DomainParticipantQos participantQos; - participantQos.name( dataSourceConfig.readerName ); - // Configure the Transport - // First disable the default Transport - participantQos.transport().use_builtin_transports = false; - // UDP Transport - if ( dataSourceConfig.transportType == DDSTransportType::UDP ) - { - auto udpTransport = std::make_shared(); - udpTransport->sendBufferSize = SEND_BUFFER_SIZE_BYTES; - udpTransport->receiveBufferSize = RECEIVE_BUFFER_SIZE_BYTES; - udpTransport->non_blocking_send = true; - participantQos.transport().user_transports.push_back( udpTransport ); - } - // Shared Memory Transport - else if ( dataSourceConfig.transportType == DDSTransportType::SHM ) - { - std::shared_ptr shm_transport = - std::make_shared(); - // Link the Transport Layer to the Participant. - participantQos.transport().user_transports.push_back( shm_transport ); - } - else if ( dataSourceConfig.transportType == DDSTransportType::TCP ) - { - FWE_LOG_TRACE( "TCP Transport is NOT yet supported" ); - return false; - } - // Create the DDS participant - mDDSParticipant = - DomainParticipantFactory::get_instance()->create_participant( dataSourceConfig.domainID, participantQos ); - - if ( mDDSParticipant == nullptr ) - { - return false; - } - // Register the DDS participant - mDDStype.register_type( mDDSParticipant ); - - // Create the DDS Topic. - mDDSTopic = mDDSParticipant->create_topic( dataSourceConfig.subscribeTopicName, - mDDStype.get_type_name(), - /*dataSourceConfig.topicQoS*/ TOPIC_QOS_DEFAULT ); - if ( mDDSTopic == nullptr ) - { - return false; - } - - mDDSSubscriber = mDDSParticipant->create_subscriber( SUBSCRIBER_QOS_DEFAULT ); - if ( mDDSSubscriber == nullptr ) - { - return false; - } - - mDDSReader = mDDSSubscriber->create_datareader( mDDSTopic, DATAREADER_QOS_DEFAULT, this ); - if ( mDDSReader == nullptr ) - { - return false; - } - // Store the source ID to be able to report on the right Source when we receive data. - mSourceID = dataSourceConfig.sourceID; - return true; -} - -bool -CameraDataSubscriber::start() -{ - // Prevent concurrent stop/init - std::lock_guard lock( mThreadMutex ); - // On multi core systems the shared variable mShouldStop must be updated for - // all cores before starting the thread otherwise thread will directly end - mShouldStop.store( false ); - if ( !mThread.create( doWork, this ) ) - { - FWE_LOG_TRACE( "Thread failed to start" ); - } - else - { - FWE_LOG_TRACE( "Thread started" ); - mThread.setThreadName( "fwVNDDSCamSub" + std::to_string( mID ) ); - } - return mThread.isActive() && mThread.isValid(); -} - -bool -CameraDataSubscriber::stop() -{ - std::lock_guard lock( mThreadMutex ); - mShouldStop.store( true, std::memory_order_relaxed ); - mWait.notify(); - mThread.release(); - mShouldStop.store( false, std::memory_order_relaxed ); - FWE_LOG_TRACE( "Thread stopped" ); - return !mThread.isActive(); -} - -bool -CameraDataSubscriber::shouldStop() const -{ - return mShouldStop.load( std::memory_order_relaxed ); -} - -void -CameraDataSubscriber::doWork( void *data ) -{ - - CameraDataSubscriber *subscriber = static_cast( data ); - - while ( !subscriber->shouldStop() ) - { - // Wait for data to arrive from the DDS Network. - subscriber->mWait.wait( Platform::Linux::Signal::WaitWithPredicate ); - // Ok we have received data and it should have been loaded into our CameraDataItem. - // We should log it into local storage location and then notify the DDS Handler that - // the data is ready. - // Make sure that we only notify during normal cycle and NOT on shutdown - if ( subscriber->mNewResponseReceived.load() ) - { - - SensorArtifactMetadata cameraArtifact; - cameraArtifact.path = subscriber->mCachePath + subscriber->mDataItem.dataItemId(); - cameraArtifact.sourceID = subscriber->mSourceID; - if ( Aws::IoTFleetWise::VehicleNetwork::CameraDataSubscriber::persistToStorage( - subscriber->mDataItem.frameBuffer(), cameraArtifact.path ) ) - { - subscriber->notifyListeners( - &SensorDataListener::onSensorArtifactAvailable, cameraArtifact ); - FWE_LOG_INFO( "Data Collected from the Camera and made available" ); - } - else - { - FWE_LOG_ERROR( "Could not persist the data received into disk" ); - } - - // Reset the response - subscriber->mNewResponseReceived.store( false, std::memory_order_relaxed ); - } - } -} - -bool -CameraDataSubscriber::connect() -{ - return start(); -} - -bool -CameraDataSubscriber::disconnect() -{ - return stop(); -} - -bool -CameraDataSubscriber::isAlive() -{ - return ( mIsAlive.load( std::memory_order_relaxed ) && mThread.isValid() && mThread.isActive() ); -} - -void -CameraDataSubscriber::on_subscription_matched( DataReader *reader, const SubscriptionMatchedStatus &info ) -{ - (void)reader; // unused variable - - if ( info.current_count_change == 1 ) - { - mIsAlive.store( true, std::memory_order_relaxed ); - FWE_LOG_TRACE( "A publisher is available" ); - } - else if ( info.current_count_change == -1 ) - { - mIsAlive.store( false, std::memory_order_relaxed ); - } -} - -void -CameraDataSubscriber::on_data_available( DataReader *reader ) -{ - SampleInfo info; - if ( reader->take_next_sample( &mDataItem, &info ) == ReturnCode_t::RETCODE_OK ) - { - if ( info.valid_data ) - { - mNewResponseReceived.store( true, std::memory_order_relaxed ); - FWE_LOG_TRACE( "Data received from the DDS Node" ); - mWait.notify(); - } - } -} - -bool -CameraDataSubscriber::persistToStorage( const std::vector &frameBuffer, const std::string &fileName ) -{ - // To be sure, cleanup any file with the same name - (void)remove( fileName.c_str() ); - // First create the file - std::ofstream newFile( fileName.c_str(), std::ios_base::binary | std::ios_base::app ); - if ( !newFile.is_open() ) - { - return false; - } - else - { - // Iterate over all frames and append their data to the file. - for ( const auto &frameData : frameBuffer ) - { // Write buffer to IO. - newFile.write( reinterpret_cast( frameData.frameData().data() ), - static_cast( frameData.frameData().size() ) ); - } - - newFile.close(); - return newFile.good(); - } -} - -} // namespace VehicleNetwork -} // namespace IoTFleetWise -} // namespace Aws diff --git a/src/vehiclenetwork/test/CameraDataPublisherTest.cpp b/src/vehiclenetwork/test/CameraDataPublisherTest.cpp deleted file mode 100644 index b36f5c4f..00000000 --- a/src/vehiclenetwork/test/CameraDataPublisherTest.cpp +++ /dev/null @@ -1,266 +0,0 @@ - -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -#include "dds/CameraDataPublisher.h" -#include "WaitUntil.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace Aws::IoTFleetWise::VehicleNetwork; -using namespace Aws::IoTFleetWise::TestingSupport; - -class TestSubscriber : public DataReaderListener -{ - -public: - TestSubscriber() - : mTestParticipant( nullptr ) - , mTestSubscriber( nullptr ) - , mTestTopic( nullptr ) - , mTestReader( nullptr ) - , mTestType( new CameraDataRequestPubSubType() ) - { - } - - ~TestSubscriber() - { - if ( mTestReader != nullptr ) - { - mTestSubscriber->delete_datareader( mTestReader ); - } - if ( mTestSubscriber != nullptr ) - { - mTestParticipant->delete_subscriber( mTestSubscriber ); - } - if ( mTestTopic != nullptr ) - { - mTestParticipant->delete_topic( mTestTopic ); - } - DomainParticipantFactory::get_instance()->delete_participant( mTestParticipant ); - } - - //! Initialize the publisher - bool - init( DDSTransportType type ) - { - - DomainParticipantQos participantQos; - participantQos.name( "TestSubscriber" ); - participantQos.transport().use_builtin_transports = false; - if ( type == DDSTransportType::UDP ) - { - auto udpTransport = std::make_shared(); - udpTransport->sendBufferSize = SEND_BUFFER_SIZE_BYTES; - udpTransport->receiveBufferSize = RECEIVE_BUFFER_SIZE_BYTES; - udpTransport->non_blocking_send = true; - participantQos.transport().user_transports.push_back( udpTransport ); - } - else if ( type == DDSTransportType::SHM ) - { - std::shared_ptr shm_transport = - std::make_shared(); - // Link the Transport Layer to the Participant. - participantQos.transport().user_transports.push_back( shm_transport ); - } - - mTestParticipant = DomainParticipantFactory::get_instance()->create_participant( 0, participantQos ); - - if ( mTestParticipant == nullptr ) - { - return false; - } - - // Register the Type - mTestType.register_type( mTestParticipant ); - - // Create the publications Topic - mTestTopic = mTestParticipant->create_topic( "testTopic", mTestType.get_type_name(), TOPIC_QOS_DEFAULT ); - - if ( mTestTopic == nullptr ) - { - return false; - } - - // Create the Subscriber - mTestSubscriber = mTestParticipant->create_subscriber( SUBSCRIBER_QOS_DEFAULT ); - - if ( mTestSubscriber == nullptr ) - { - return false; - } - - // Create the DataReader - mTestReader = mTestSubscriber->create_datareader( mTestTopic, DATAREADER_QOS_DEFAULT, this ); - - if ( mTestReader == nullptr ) - { - return false; - } - return true; - } - - void - on_data_available( DataReader *reader ) override - { - SampleInfo info; - reader->take_next_sample( &dataItem, &info ); - } - -private: - DomainParticipant *mTestParticipant; - - Subscriber *mTestSubscriber; - - Topic *mTestTopic; - - DataReader *mTestReader; - - TypeSupport mTestType; - -public: - CameraDataRequest dataItem; -}; - -/** - * @brief Validate the life cycle of a Camera Data Publisher - * 1- Create a default source config - * 2- Assert that the Publisher is initialized - * 3- As the Publisher is connected to the topic, but no subscriber is connected on the other side, - * the Publisher must be no alive. - * 4- Trigger a notification that mocks a subscriber and assert that the Publisher is alive. - * 5- Disconnect the Publisher - */ - -TEST( CameraDataPublisherTest, testLifeCycle ) -{ - CameraDataPublisher publisher; - DDSDataSourceConfig config = { 1, - SensorSourceType::CAMERA, - 0, - "testTopic", - "testTopic", - "TOPIC_QOS_DEFAULT", - "testReader", - "TestWriter", - "/tmp/camera/test/", - DDSTransportType::SHM }; - ASSERT_TRUE( publisher.init( config ) ); - ASSERT_TRUE( publisher.connect() ); - ASSERT_FALSE( publisher.isAlive() ); - PublicationMatchedStatus info; - info.current_count_change = 1; - publisher.on_publication_matched( nullptr, info ); - ASSERT_TRUE( publisher.isAlive() ); - ASSERT_TRUE( publisher.disconnect() ); - ASSERT_FALSE( publisher.isAlive() ); -} - -/** - * @brief Validate the use case of sending data on the topic( based on UDP DDS Transport) - * of a CameraDataRequest over DDS using a mocked data. - * 1- Create a default source config - * 2- Assert that the Publisher is initialized but not alive - * 3- Init a mock subscriber on the Topic - * 4- Assert that the Publisher is Alive - * 5- Send a request on the topic - * 6- Assert that the Subscriber has received the exact same request - * 7- Disconnect the Publisher - */ -TEST( CameraDataPublisherTest, testSendDataUPDTransport ) -{ - CameraDataPublisher publisher; - - DDSDataSourceConfig config = { 1, - SensorSourceType::CAMERA, - 0, - "testTopic", - "testTopic", - "TOPIC_QOS_DEFAULT", - "testReader", - "testWriter", - "/tmp/camera/test/", - DDSTransportType::UDP - - }; - - TestSubscriber subscriber; - // Init the publisher - ASSERT_TRUE( publisher.init( config ) ); - ASSERT_TRUE( publisher.connect() ); - // No subscriber connected, thus not Alive - ASSERT_FALSE( publisher.isAlive() ); - // Init the subscriber and give some time for the DDS stack to notify the publisher - ASSERT_TRUE( subscriber.init( DDSTransportType::UDP ) ); - std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); - ASSERT_TRUE( publisher.isAlive() ); - - // Publish data - DDSDataRequest dataRequest{ 123, 1, 1 }; - publisher.publishDataRequest( dataRequest ); - // Give some time so that the Subscriber receives the message - - // Verify that the data arrived - WAIT_ASSERT_EQ( subscriber.dataItem.dataItemId(), 123U ); - ASSERT_TRUE( publisher.disconnect() ); - ASSERT_FALSE( publisher.isAlive() ); -} - -/** - * @brief Validate the use case of sending data on the topic( based on SHM DDS Transport) - * of a CameraDataRequest over DDS using a mocked data. - * 1- Create a default source config - * 2- Assert that the Publisher is initialized but not alive - * 3- Init a mock subscriber on the Topic - * 4- Assert that the Publisher is Alive - * 5- Send a request on the topic - * 6- Assert that the Subscriber has received the exact same request - * 7- Disconnect the Publisher - */ -TEST( CameraDataPublisherTest, testSendDataSHMTransport ) -{ - CameraDataPublisher publisher; - - DDSDataSourceConfig config = { 1, - SensorSourceType::CAMERA, - 0, - "testTopic", - "testTopic", - "TOPIC_QOS_DEFAULT", - "testReader", - "testWriter", - "/tmp/camera/test/", - DDSTransportType::SHM - - }; - - TestSubscriber subscriber; - // Init the publisher - ASSERT_TRUE( publisher.init( config ) ); - ASSERT_TRUE( publisher.connect() ); - // No subscriber connected, thus not Alive - ASSERT_FALSE( publisher.isAlive() ); - // Init the subscriber and give some time for the DDS stack to notify the publisher - ASSERT_TRUE( subscriber.init( DDSTransportType::SHM ) ); - std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); - ASSERT_TRUE( publisher.isAlive() ); - - // Publish data - DDSDataRequest dataRequest{ 123, 1, 1 }; - publisher.publishDataRequest( dataRequest ); - // Give some time so that the Subscriber receives the message - - // Verify that the data arrived - WAIT_ASSERT_EQ( subscriber.dataItem.dataItemId(), 123U ); - ASSERT_TRUE( publisher.disconnect() ); - ASSERT_FALSE( publisher.isAlive() ); -} diff --git a/src/vehiclenetwork/test/CameraDataSubscriberTest.cpp b/src/vehiclenetwork/test/CameraDataSubscriberTest.cpp deleted file mode 100644 index 30f311f8..00000000 --- a/src/vehiclenetwork/test/CameraDataSubscriberTest.cpp +++ /dev/null @@ -1,542 +0,0 @@ - -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -#include "dds/CameraDataSubscriber.h" -#include "WaitUntil.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace Aws::IoTFleetWise::TestingSupport; -using namespace Aws::IoTFleetWise::VehicleNetwork; - -/** - * @brief Utlity function to check if the input and output files - * that we send and receive are identical. - * @param input the png we send from the publisher side - * @param output the png we received on the subscriber side - * @return true - * @return false - */ -bool -areIdentical( const std::string &input, const std::string &output ) -{ - std::ifstream inputFD( input, std::ifstream::ate | std::ifstream::binary ); - std::ifstream outputFD( output, std::ifstream::ate | std::ifstream::binary ); - - // Check first the size - if ( inputFD.tellg() != outputFD.tellg() ) - { - return false; - } - // Rewind - inputFD.seekg( 0 ); - outputFD.seekg( 0 ); - - return std::equal( std::istreambuf_iterator( inputFD ), - std::istreambuf_iterator(), - std::istreambuf_iterator( outputFD ) ); -} -class localSensorDataListener : public SensorDataListener -{ -public: - localSensorDataListener() - : gotNotification( false ) - { - } - inline void - onSensorArtifactAvailable( const SensorArtifactMetadata &artifactMetadata ) - { - artifact = artifactMetadata; - gotNotification = true; - } - - SensorArtifactMetadata artifact; - bool gotNotification; -}; - -class TestPublisher -{ - -public: - TestPublisher() - : mTestParticipant( nullptr ) - , mTestPublisher( nullptr ) - , mTestTopic( nullptr ) - , mTestWriter( nullptr ) - , mTestType( new CameraDataItemPubSubType() ) - { - } - - ~TestPublisher() - { - if ( mTestWriter != nullptr ) - { - mTestPublisher->delete_datawriter( mTestWriter ); - } - if ( mTestPublisher != nullptr ) - { - mTestParticipant->delete_publisher( mTestPublisher ); - } - if ( mTestTopic != nullptr ) - { - mTestParticipant->delete_topic( mTestTopic ); - } - DomainParticipantFactory::get_instance()->delete_participant( mTestParticipant ); - } - - //! Initialize the publisher - bool - init( DDSTransportType type ) - { - - DomainParticipantQos participantQos; - participantQos.name( "TestPublisher" ); - participantQos.transport().use_builtin_transports = false; - if ( type == DDSTransportType::UDP ) - { - auto udpTransport = std::make_shared(); - udpTransport->sendBufferSize = SEND_BUFFER_SIZE_BYTES; - udpTransport->receiveBufferSize = RECEIVE_BUFFER_SIZE_BYTES; - udpTransport->non_blocking_send = true; - participantQos.transport().user_transports.push_back( udpTransport ); - } - else if ( type == DDSTransportType::SHM ) - { - std::shared_ptr shm_transport = - std::make_shared(); - // Link the Transport Layer to the Participant. - participantQos.transport().user_transports.push_back( shm_transport ); - } - - mTestParticipant = DomainParticipantFactory::get_instance()->create_participant( 0, participantQos ); - - if ( mTestParticipant == nullptr ) - { - return false; - } - - // Register the Type - mTestType.register_type( mTestParticipant ); - - // Create the publications Topic - mTestTopic = mTestParticipant->create_topic( "testTopic", mTestType.get_type_name(), TOPIC_QOS_DEFAULT ); - - if ( mTestTopic == nullptr ) - { - return false; - } - - // Create the Publisher - mTestPublisher = mTestParticipant->create_publisher( PUBLISHER_QOS_DEFAULT ); - - if ( mTestPublisher == nullptr ) - { - return false; - } - - // Create the DataWriter - mTestWriter = mTestPublisher->create_datawriter( mTestTopic, DATAWRITER_QOS_DEFAULT ); - - if ( mTestWriter == nullptr ) - { - return false; - } - return true; - } - - void - publishTestData( const std::string &id ) - { - mTestItem.dataItemId( id ); - mTestWriter->write( &mTestItem ); - } - - void - publishTestDataWithRealPNG( const std::string &id, const std::string &filePath ) - { - mTestItem.dataItemId( id ); - std::ifstream file( filePath.c_str(), std::ios_base::binary | std::ios_base::in ); - struct stat res; - size_t pngSize = 0; - if ( file.is_open() ) - { - - if ( stat( filePath.c_str(), &res ) == 0 ) - { - pngSize = static_cast( res.st_size ); - } - // Try to create frames of size of 1kb each. - std::unique_ptr readBufPtr( new uint8_t[pngSize]() ); - file.read( reinterpret_cast( readBufPtr.get() ), pngSize ); - std::vector frameBuffer; - - CameraFrame frame; - std::vector frameData( readBufPtr.get(), readBufPtr.get() + pngSize ); - frame.frameData( frameData ); - frameBuffer.emplace_back( frame ); - mTestItem.frameBuffer( frameBuffer ); - mTestWriter->write( &mTestItem ); - } - } - -private: - CameraDataItem mTestItem; - - DomainParticipant *mTestParticipant; - - Publisher *mTestPublisher; - - Topic *mTestTopic; - - DataWriter *mTestWriter; - - TypeSupport mTestType; -}; - -/** - * @brief Validate the life cycle of a Camera Data Subscriber - * 1- Create a default source config - * 2- Assert that the Subscriber is initialized - * 3- As the subscriber is connected to the topic, but no pulisher is connected on the other side, - * the subscriber must be no alive. - * 4- Trigger a notification that mocks a publisher and assert that the subscriber is alive. - * 5- Disconnect the subscriber - */ -TEST( CameraDataSubscriberTest, testLifeCycle ) -{ - CameraDataSubscriber subscriber; - DDSDataSourceConfig config = { 1, - SensorSourceType::CAMERA, - 0, - "Test", - "testTopic", - "TOPIC_QOS_DEFAULT", - "testReader", - "Test", - "/tmp/camera/test/", - DDSTransportType::SHM }; - ASSERT_TRUE( subscriber.init( config ) ); - ASSERT_TRUE( subscriber.connect() ); - ASSERT_FALSE( subscriber.isAlive() ); - SubscriptionMatchedStatus info; - info.current_count_change = 1; - subscriber.on_subscription_matched( nullptr, info ); - ASSERT_TRUE( subscriber.isAlive() ); - ASSERT_TRUE( subscriber.disconnect() ); - ASSERT_FALSE( subscriber.isAlive() ); -} - -/** - * @brief Validate the data retrieval ( based on UDP DDS Transport) - * of a CameraDataItem over DDS using a mocked data publisher. - * 1- Create a default source config - * 2- Assert that the Subscriber is initialized - * 3- Register a listener to the subscriber to get notified when it has received data. - * 4- Create a mocked publisher and attach it to the same topic. - * 5- Validate that the subscriber detected this publisher via an isAlive check. - * 6- Publish a message on the Topic - * 7- Assert that the Subscriber received the message - * 8- Assert that the Subscriber propagated the data to the listener - * 9- Disconnect the subscriber - */ - -TEST( CameraDataSubscriberTest, testReceiveDataUPDTransport ) -{ - CameraDataSubscriber subscriber; - DDSDataSourceConfig config = { 1, - SensorSourceType::CAMERA, - 0, - "", - "testTopic", - "TOPIC_QOS_DEFAULT", - "testReader", - "", - "./", - DDSTransportType::UDP - - }; - ASSERT_TRUE( subscriber.init( config ) ); - ASSERT_TRUE( subscriber.connect() ); - ASSERT_FALSE( subscriber.isAlive() ); - localSensorDataListener dataListener; - subscriber.subscribeListener( &dataListener ); - TestPublisher publisher; - ASSERT_TRUE( publisher.init( DDSTransportType::UDP ) ); - // Give some time so that the DDS Stack gets loaded and the multicast gets agreed. - std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); - - // Send one message - publisher.publishTestData( "dataItem1" ); - // Give some time for the worker to wake up and consume the message. - std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); - ASSERT_TRUE( subscriber.isAlive() ); - ASSERT_TRUE( dataListener.gotNotification ); - // Current implementation simply appaends the text to the base path. - // This will be the actual path of the camera data once we implement storage. - ASSERT_EQ( dataListener.artifact.path, "./dataItem1" ); - - ASSERT_TRUE( subscriber.disconnect() ); - ASSERT_FALSE( subscriber.isAlive() ); -} - -/** - * @brief Validate the data retrieval ( based on Shared Memory DDS Transport) - * of a CameraDataItem over DDS using a mocked data publisher. - * 1- Create a default source config - * 2- Assert that the Subscriber is initialized - * 3- Register a listener to the subscriber to get notified when it has received data. - * 4- Create a mocked publisher and attach it to the same topic. - * 5- Validate that the subscriber detected this publisher via an isAlive check. - * 6- Publish a message of the Topic - * 7- Assert that the Subscriber received the message - * 8- Assert that the Subscriber propagated the data to the listener - * 9- Disconnect the subscriber - */ -TEST( CameraDataSubscriberTest, testReceiveDataSHMTransport ) -{ - CameraDataSubscriber subscriber; - DDSDataSourceConfig config = { 1, - SensorSourceType::CAMERA, - 0, - "", - "testTopic", - "TOPIC_QOS_DEFAULT", - "testReader", - "", - "./", - DDSTransportType::SHM - - }; - ASSERT_TRUE( subscriber.init( config ) ); - ASSERT_TRUE( subscriber.connect() ); - ASSERT_FALSE( subscriber.isAlive() ); - localSensorDataListener dataListener; - subscriber.subscribeListener( &dataListener ); - TestPublisher publisher; - ASSERT_TRUE( publisher.init( DDSTransportType::SHM ) ); - // Give some time so that the DDS Stack gets loaded and the multicast gets agreed. - std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); - - // Send one message - publisher.publishTestData( "dataItem1" ); - // Give some time for the worker to wake up and consume the message. - std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); - ASSERT_TRUE( subscriber.isAlive() ); - ASSERT_TRUE( dataListener.gotNotification ); - // Current implementation simply appaends the text to the base path. - // This will be the actual path of the camera data once we implement storage. - ASSERT_EQ( dataListener.artifact.path, "./dataItem1" ); - - ASSERT_TRUE( subscriber.disconnect() ); - ASSERT_FALSE( subscriber.isAlive() ); -} - -/** - * @brief Validate the data retrieval ( based on Shared Memory DDS Transport) - * of a CameraDataItem over DDS using a mocked real publisher. - * 1- Create a default source config - * 2- Assert that the Subscriber is initialized - * 3- Register a listener to the subscriber to get notified when it has received data. - * 4- Create a mocked publisher and attach it to the same topic. - * 5- Validate that the subscriber detected this publisher via an isAlive check. - * 6- Publish a message of the Topic. The message consists of a PNG file. - * 7- Assert that the Subscriber received the message - * 8- Assert that the Subscriber propagated the data to the listener - * 9- Check that the PNG data send is exactly the same received. - * 9- Disconnect the subscriber - */ -TEST( CameraDataSubscriberTest, testReceiveDataAndPersistSHM ) -{ - char currentDir[PATH_MAX]; - if ( getcwd( currentDir, sizeof( currentDir ) ) != NULL ) - { - CameraDataSubscriber subscriber; - DDSDataSourceConfig config = { 1, - SensorSourceType::CAMERA, - 0, - "", - "testTopic", - "TOPIC_QOS_DEFAULT", - "testReader", - "", - std::string( currentDir ) + "/", - DDSTransportType::SHM - - }; - ASSERT_TRUE( subscriber.init( config ) ); - - ASSERT_TRUE( subscriber.connect() ); - ASSERT_FALSE( subscriber.isAlive() ); - localSensorDataListener dataListener; - subscriber.subscribeListener( &dataListener ); - TestPublisher publisher; - ASSERT_TRUE( publisher.init( DDSTransportType::SHM ) ); - // Give some time so that the DDS Stack gets loaded and the multicast gets agreed. - std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); - ASSERT_TRUE( subscriber.isAlive() ); - // Send one message - publisher.publishTestDataWithRealPNG( "testPersitSHM.png", - std::string( currentDir ) + "/CameraSubscriberTestPNG.png" ); - // Give some time for the worker to wake up and consume the message. - std::this_thread::sleep_for( std::chrono::seconds( 2 ) ); - ASSERT_TRUE( dataListener.gotNotification ); - ASSERT_EQ( dataListener.artifact.path, config.temporaryCacheLocation + "testPersitSHM.png" ); - // Verify that what we send on DDS is exactly what we received i.e the same PNG file. - ASSERT_TRUE( - areIdentical( std::string( currentDir ) + "/CameraSubscriberTestPNG.png", dataListener.artifact.path ) ); - ASSERT_TRUE( subscriber.disconnect() ); - ASSERT_FALSE( subscriber.isAlive() ); - } - else - { - std::cout << "Could not find current working directory" << std::endl; - } -} - -/** - * @brief Validate the data retrieval ( based on UDP DDS Transport) - * of a CameraDataItem over DDS using a mocked real publisher. - * 1- Create a default source config - * 2- Assert that the Subscriber is initialized - * 3- Register a listener to the subscriber to get notified when it has received data. - * 4- Create a mocked publisher and attach it to the same topic. - * 5- Validate that the subscriber detected this publisher via an isAlive check. - * 6- Publish a message of the Topic. The message consists of a PNG file. - * 7- Assert that the Subscriber received the message - * 8- Assert that the Subscriber propagated the data to the listener - * 9- Check that the PNG data send is exactly the same received. - * 9- Disconnect the subscriber - */ -TEST( CameraDataSubscriberTest, testReceiveDataAndPersistUDP ) -{ - char currentDir[PATH_MAX]; - if ( getcwd( currentDir, sizeof( currentDir ) ) != NULL ) - { - CameraDataSubscriber subscriber; - DDSDataSourceConfig config = { 1, - SensorSourceType::CAMERA, - 0, - "", - "testTopic", - "TOPIC_QOS_DEFAULT", - "testReader", - "", - std::string( currentDir ) + "/", - DDSTransportType::UDP - - }; - ASSERT_TRUE( subscriber.init( config ) ); - - ASSERT_TRUE( subscriber.connect() ); - ASSERT_FALSE( subscriber.isAlive() ); - localSensorDataListener dataListener; - subscriber.subscribeListener( &dataListener ); - TestPublisher publisher; - ASSERT_TRUE( publisher.init( DDSTransportType::UDP ) ); - // Give some time so that the DDS Stack gets loaded and the multicast gets agreed. - std::this_thread::sleep_for( std::chrono::seconds( 2 ) ); - ASSERT_TRUE( subscriber.isAlive() ); - // Send one message - publisher.publishTestDataWithRealPNG( "testPersitUDP.png", - std::string( currentDir ) + "/CameraSubscriberTestPNG.png" ); - // Give some time for the worker to wake up and consume the message. - std::this_thread::sleep_for( std::chrono::seconds( 5 ) ); - ASSERT_TRUE( dataListener.gotNotification ); - ASSERT_EQ( dataListener.artifact.path, config.temporaryCacheLocation + "testPersitUDP.png" ); - // Verify that what we send on DDS is exactly what we received i.e the same PNG file. - ASSERT_TRUE( - areIdentical( std::string( currentDir ) + "/CameraSubscriberTestPNG.png", dataListener.artifact.path ) ); - ASSERT_TRUE( subscriber.disconnect() ); - ASSERT_FALSE( subscriber.isAlive() ); - } - else - { - std::cout << "Could not find current working directory" << std::endl; - } -} - -/** - * @brief Validate the data retrieval ( based on UDP DDS Transport) - * of a CameraDataItem over DDS using a mocked real publisher. This test validates that - * the system can support more than image message coming over DDS - * 1- Create a default source config - * 2- Assert that the Subscriber is initialized - * 3- Register a listener to the subscriber to get notified when it has received data. - * 4- Create a mocked publisher and attach it to the same topic. - * 5- Validate that the subscriber detected this publisher via an isAlive check. - * 6- Publish a message of the Topic. The message consists of a PNG file. - * 7- Assert that the Subscriber received the message - * 8- Assert that the Subscriber propagated the data to the listener - * 9- Check that the PNG data send is exactly the same received. - * 9- Disconnect the subscriber - */ -TEST( CameraDataSubscriberTest, testReceiveDataAndPersistUDPMultipleImages ) -{ - char currentDir[PATH_MAX]; - if ( getcwd( currentDir, sizeof( currentDir ) ) != NULL ) - { - CameraDataSubscriber subscriber; - DDSDataSourceConfig config = { 1, - SensorSourceType::CAMERA, - 0, - "", - "testTopic", - "TOPIC_QOS_DEFAULT", - "testReader", - "", - std::string( currentDir ) + "/", - DDSTransportType::UDP - - }; - ASSERT_TRUE( subscriber.init( config ) ); - - ASSERT_TRUE( subscriber.connect() ); - ASSERT_FALSE( subscriber.isAlive() ); - localSensorDataListener dataListener; - subscriber.subscribeListener( &dataListener ); - TestPublisher publisher; - ASSERT_TRUE( publisher.init( DDSTransportType::UDP ) ); - // Give some time so that the DDS Stack gets loaded and the multicast gets agreed. - std::this_thread::sleep_for( std::chrono::seconds( 2 ) ); - ASSERT_TRUE( subscriber.isAlive() ); - // Send the first message - publisher.publishTestDataWithRealPNG( "Image1.png", - std::string( currentDir ) + "/CameraSubscriberTestPNG.png" ); - // Give some time for the worker to wake up and consume the message. - - WAIT_ASSERT_TRUE( dataListener.gotNotification ); - ASSERT_EQ( dataListener.artifact.path, config.temporaryCacheLocation + "Image1.png" ); - // Verify that what we send on DDS is exactly what we received i.e the same PNG file. - ASSERT_TRUE( - areIdentical( std::string( currentDir ) + "/CameraSubscriberTestPNG.png", dataListener.artifact.path ) ); - - // Send the second message - publisher.publishTestDataWithRealPNG( "Image2.png", - std::string( currentDir ) + "/CameraSubscriberTestPNG.png" ); - - // Give some time for the worker to wake up and consume the message. - - WAIT_ASSERT_TRUE( dataListener.gotNotification ); - WAIT_ASSERT_EQ( dataListener.artifact.path, config.temporaryCacheLocation + "Image2.png" ); - // Verify that what we send on DDS is exactly what we received i.e the same PNG file. - ASSERT_TRUE( - areIdentical( std::string( currentDir ) + "/CameraSubscriberTestPNG.png", dataListener.artifact.path ) ); - ASSERT_TRUE( subscriber.disconnect() ); - ASSERT_FALSE( subscriber.isAlive() ); - } - else - { - std::cout << "Could not find current working directory" << std::endl; - } -} diff --git a/src/vehiclenetwork/test/CameraSubscriberTestPNG.png b/src/vehiclenetwork/test/CameraSubscriberTestPNG.png deleted file mode 100644 index 3cbaaf5a2636ef461118e0162284f618f9671819..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9488 zcmeHsWmKD6(`axg1p*W)?oROH4lPpLy;#u_+#x`VdnxV?#jQx7PzZtI6o(=WTBJa6 z|I%~LE9>6hcilhVUTdD2y~i@MXZC)QL~E+QzB<+rkiF_Ue zNCW@?k8K>}@rt!LJ>hZ2F?9$fx$^ax8lz;!9D0*&_iCXpKLAw>|5m|t2C4F{F-TH_c=&ZN&q|LGE{89 zlW-uboCJe3B<2yB{665o7I+wvk$Z(+mRe87Dffl0OKQzeHBI(GX>1ufg zAht4~mlk|vr4!Xx>c=-p5%fBZDF=(slzYch8HGnnz^71&FsytWxJ|mqaceb?d|wML$B0E)p0^OfUMVX`m;H zX^vyCgt0y_y&{2leDFtNw9dd=vM~D|jKUC%5B0WT>~vU4iyf35KX}fRMApzm2QeAe ziY6bADLo}m?zKZ@LxspOp~e*Y(W4bJXkwqyMmF!3;|@}gb5ms`3I5ERMK+}2QK zFjN0ddrTu6zd?D3O72AC{ILP-MO6_!YyaVGCs(elsuelk2Xf`dtYQcjU%Fq3FZ%3w zoqvf?UQoxKH%b2BE6hC#aZR0&Z4))pW_aL8YbZeb9qGhBcFS-59pBym2fZ)ACUQiC zSQrJ=#(LWK20=&oo2o@-f{Sm8F@&9m>5dt-{f69SNWKXHPMSx4&m+T%%57|Oq0 zL0}=ihD*E$-yRD|>80wn&31UV2T#O>qE4Qo&S0*qCyyqnHuht1;Y(mBaOLUpx}y&- z&n)8sW-^O{)w*o(b4ajH_U)DfRRO54PcJTLud8D_uzT+NT(eiNtD6qr)xB*3cV!}fVld!eFFd5qMnU1^j;*Fr5$gs0v z4fi`BZ^R9JXYd(A8Ny>tq?sTx5ccatmx&@p8nI(e}^8NVw(53REXlnkkmUXNw0WJa+V&(Sjn*tpHQK85zu5 zVGVpB(%4cSulrGn0xk%}%S1X`JLl%>SPAAPPs+eWDq zEegwo^~3OBlCalS;>Yw2=?V-_Q*xA1N(_neb!9l1YnTrhsF}?eyxy<8Zy6-D@o^Us zjpt%cPvRUD|N3GueNbaC=!sbjcYh+0Dxvmk%{1*#+F5L5Y`Gt#KGMwOQOKy3Y-w(5 zaaMXw6HkjzryCm^>l=SIUT~4roz#TAuTNF;y-)+1Bit_5RblPr0)%p~?Zu zYcUrq)6QG#%~B7$>*6Yur23njI)%eklXs?18w>VlMS+VK?!^dpM}l*^UM{&)z_o3i=oDfup?KLrb=x^OkWg!B9tScdmY z8iB<25ABJ5@mIka3%p~zll6*yyAfs-=WzQrt@`A_iV^$7Nxk9=IM+Jwy6O5R91rfJ z=1R<(;>I%d@>y06?@t;R-tUmMSH25JL4DxNqB8Ze<1zuY){H#i10ez7Sl*P(8X<9) z**9Z?1kc9#ENp{eox@?4g!c3ehPH0g2!b^F*uEiay+&gr$+WVx%?}acg4hcjy>& zcmU4YXV*3E^$A5hp(`V3F@22^AwGAJbSN$j?ndnn3Wi)kFSoA`Pa$H9!Oixkvy%j9 z)u@*!*=S;@Mxjqbr7^wHjWCbjetb)VZi0pTwiQGAhlsno2vlTT-c){r9!qY;^{%a{ zeYDWfUcuhT-XtT~PI%; ztc{E9wQ#idif9zX6^Dzn;YHR1ab}|D>Tz%41k312VZW^m;4AS>iP;nvdm@OoHaER> zngMA4c#L#vk5WcXy}%DID<#A?*pf27@=!^dP433&M%IQfdm%fgwirB-LOS5KT{lHn z+0mMUom=#ciSgmPdYmd&&$58h^fHTVdug|8m_W))mQ6J=2_rjx$nX2da?e%ZV{rtx zhQUJJ!YS$PQcW*uaG(k(MI=S6Mkq#{jd`!JZ0bgS5n)Ah!j_3@k-inx zZN|!Sgxee1mppDxw11y(q?`!+CQvymZT~jQzNJ&9;gBvmtYS;8b5~ZIx5$|I9;`zb z6CDFbMY}u>Hvd_0tM1p$TVMb2=%9JS;ih~~EnQt&y3YY#^i(6JH#Rscdi=?VbEd7R z(@OPClW3Fhxa^kAm_w$Mq|cY{V~8jO^-0Bv`}Una!nm<$W9Ih5l^X5{=@TB`OEZ{H zOX*_i?{9mnH;`@z`p=q;^>2~~idtf&QgMdI(x7PuA0UQYhJoi;t+X7F9gFuZC2uM( zzQqqm4VMhpauXPTwhmv3DIy^-NH8<%j%oSmJX1B3Vvt{XR6XAu(eb>!teC%}(XD25 z(bh@2nz#;XOrqUl*Y)#Sac|Wb=+f5A=WD&p(xkSTz4E|zbqwE%8a^D+BQ-2rVFD|I=c z*~zb^yjp@{X4Jj`TNFuz`Bf)q_%YN=W^Na6O$Q>^TDnoU(#3} z%FwzKZoO{khvoGm@d$V%Czq)czeuQvu?bku8=Dd z#DBYStdpbjLIW#D$`gJ#@}+6N32(b|d?x=w46-I%w~F8F*E(Zy)IQ-md10k8~Cj#NUbA`gk4ce;4R7 zxPz`A28eZv6*fwUtB+os_o~9eZKfoUEj8>lA_r z?idE@0t#Y!hy#}&aEn!9PC4qI8zR4U#Ml}tzgANNa3E=H06Gc<00T*(AVCU+^567x z6gB|bU+bspp!knl(6Z;bXn0)T~_5h2010QFzeD4+tgf6)MUBo82~ zBd@HC#5&gQwze)F_Fzx3CfXt70*-Ih#~1e%RCHpe~EZHNirC!Y0}Aq z-EHZFK!PA%2B}AMbaWE#Hm}9B6`ud2j+{v{*n4`qit+IH`1pW)1VCVSJ03n!QBfXV zeja{)Zlnaaho6h5r7yRO2jkyH{$)qO*2CJ}!PV0N>_Yd)uB8>&%Ttnp;g6$#kH0^s zt*^s>Jh^!M6BaT+oUr3@%Y)w_6+NZ?lk|U-|C{-LD*olt=szy`_yqsk<$rPh(Ujo%6ZwB-;_o#7 zwTjHM)FTO=fA5*pqY(K@Y~(glIVfo8A~CYa{u-jlCmRy}U?iYz8&(3@k+?}&K~~on zWq+fO1$-wmAluQLA|#h<%zsHRKZy@2v*L#ua_R2dR8cz$$tq5Nt&v++ zjLNb)fqcN!#==spxiSjdq!=Hb;UoymMQr+{M~>fI1FZ>YCEEmpfieIT`cU$IW{G*>Tr(uag&%o`W-HbO&20#j}pTv zNKYUG2(7?itF7q2l0XMybIZ7evuvPX(*aum{Uj|S`H=wBLJS7>+VgYoy`3FbOU_i4 zz6mokGt$`p?>94!Gh^f9lzC?X>sJ#=woOLk4P9N50RoZ-?e}#&Oex9%;sWb5(3Tck z3vX{!h=PVjVpn#q*F{ZT-O~2KLC^SF#0|;L{(kf9to|y2sJM93`nvV1M+@4>az6We z#K*g1Jbe5PL7oA4wJwL>dt3ge!^>=0$H#6<(jrGW(7o`Hor+C&(eY!%{k;Y5s0Z&x z_&`_Jr%#`@@)|okooUHPQKk7(RTg%KVxV=JZ1d&){eSypqoy&Ipt-n9nwruH;OAWn zsPztMzY$Q>Hl^L#=FU}z3NBRfvy;X`m;(4DoQa8v&z!CTS4h*-cDsd7ilERX?VzAv zv%V(TU0FGvb?mjT1>4J$ds>Sbog5q-PQCB0`}@O7uW$CcSI@%`9ctUxT@U7_@&*sz zy7IC)G*}*PZ{4O~iD=z$Je*y$c2>l2CM(Sk4o0cAL!nN{pkxqx#aynh8VA8` zMy9Pnj8dYa4Wpx@;+xjL?q7BzJNV(j>c&QefM3j^bv~Nw?Ni)GO!)pW1%u(;LTb z$C;0Ur>O=z9ecGp1Gr?|MfCC!6+z|sf)3v%W~vNkq|2SEH|H!Mk_U_JIwatw&$)}O zU~m@`;uuf(yG%OBEXFt9f_3zB3@NvYz)GNwkfhhHZkC9{dy8e5(>9zETq1pc$!MOr zg!bxYJ0J4R0^(=<+y$yN=a(6vE6As&8wmp4+lOK!R?CCVj`vFX+!m+TeoaTbaPq z$rplw^SoY})NVgMFMe{6@4pIdf4ILPX}8L;2t3A6%a+Q;rxq)LMzxM6Nl6Mh&BlGy ztt`4$&3;z%G2nXS>Eg{-*5+3~TwFiqPZuUEQ zoh1yBq89fa>C7P#SC`PNOQ2A6xV-lcyyY{yz4H+wBapf**xfrBmo86@`-xyyMVwjF z_-0BseO|gN0jAIS8JvlE74;HGSI=&_!D&vO`K(AdJ|!-W`0RY;E<`(f6GaM|4Zf37 zCq;C)Z$65r6;|)Uag(Q%z6|j{-H~N$9TEq3*U;EIwM$_}$6KN1j1$DAFh#T-0Wa)O zYsQ#1m<*qwVc|a(Uv33kIBIqW8(w*`B;$ZP3H1}g!Z2{Zwar-=ymJnat(Z_T%@nZH zKl{Bs4mQD~Kxndbm8z!0#UWE_!jl0_ueE-CZ(mQ%#-dvQnL)D@9i>lleZB!_lVZs@ zNxjv{0!I{10-@g5Nu6;5*6_IIt3HDDMc2j5QJ$LQm#$?@w)Z36-+u)#gX&K3f5V$n z*mcUHS>KpiSB68FftLjahukM6tv5z=ZPs&&eoM9OEJ@Nx`ObpvpbpZ8p;qc19OtWsBz2Alh&$Iy()bbXx}libmi!>`l<^{ZRG+oR@V|G zenV!UJm~hsA>eyF{V6if_tut;aq3!%|wXS)hv)afpjxS-h z#$>8?8QM_A#C}q~q%@*WOt$+t_{w(RW2BME!+z5^Vk{u{;;LG2)0Pwc_mqb8Wp^N>(Ms5N88nV%!_TW9?PmsM-Tf8q;eRP2{;#td`;1iQtrOL<_zjFej3PL*3(=) z{thFP&l)qHQhG&#y7e%v?N|FYO>bE5*~Gn=&(ER}p=C`Hn!Ukf#u<82HQQ$``^^fb z0HI*yd9`cIir_H$RQ)Z>l`yKke@cT>fN+3WHH`_Vi0l?uaxndt4Ut)+FJGy8uJPGw zLXfw(yp)8!Zbi9r5Dm|%WnW~~vm*ijQ~QtP{C0&kGERILEUba%`X`hnk1d!t5dE<# z@+-Y%)ueGvT73~8ex5HmAYjoS=l4WvU>IThaGodeUK+Dw*@6y5*Yh1-oL;^^Fj@|` zZ-3sX*gFv!lsFkQ2B7SbAiP$FKd=`!b2o>cU7QMx!!kD&W1bZLoGPmn3O!+QW5xoJ z%O}0xhqYMsKiZXk6;dAX?g5v81s$B1*X6(PX+5oNu_<~gBItRN&uUQm^Tembvn$%? zRU1R8s;RT>z>_}JNg>ccY23=6k)atxAShst0Q7rTYCGN%@z8W7=FZ(^IH6zKw?&KN$v6woi zn~+W1TI-biUQO$_Yp*?oId8vF6Gr8gD5RtA-KW~<*dX`2>kETXzg(ZD6`Q<)uW4tf z#6%mId~>SqYekrZYZ{F}vZAoXjovQVrY|Nl%8iYE6a<$+7t7^2U!L;YXr{{vgf!70 zHqyU{w!(zMgi=`Zd{Om2*t0gx`fFkjRHV^J27D6>4%vs{xqWF3Z{;abo+?8xZ(>(# zuO3k4sbuh=?@cC1A>g+^TH}=_9g?+w`jj;z*0;Xp^yhLm1^*AZ0@1E+{}%UkSjyF5_EobSXTG;N(oPWd0Twq) z{9N=4xAT1?uf@I2Y?w`}dBO4d)>U}M9@5hN5@iFu5ov8(W0~^mV=hqd9~I_Y7A_O>Wva)d9<|hhuxlehse=@&bR+Io8gb0tiWq|4E53B|Eda%$& z!NY0XuNxLu$381fE5Dpy{S!wR7?-7459n2`Tlt2hl|)95e# z$S{KWJk-zTfiuD3b4)N(p0A;?G&-6~6~qQFW=Yp5+t(As?fGsT3P@!3UrI{cf`4a| ze5M+lEPlbBCL|!SmM(ILy{L~hhWmESa&M5{O59D=G*&e6u`xBe>^mh2?AlNWh6BJG z5z#hsKh(6U+@0(du(?ooYCKqH15zkSrc*@!R`zB|3LVR1WHhNc@wG^;jgTu!Pq&;q}mUv2BH z!C?ZGCwAu~4y(9snd>h{I@g&i(RfHQn<2KQ!YZ0u_DBpYJA?6m*P9+9QiR zm!^Tc^@p~+PUgl*L=6c-tlYVxjio2rNhT$$EY}jh^MGFO+L%Ei4afCM(UrXeFc@%z zwGJ5*%W)qTs{{Pv4E~FH5Q3xOb;kQ?Ev|gdA6ILkV`&)h$Ad@O36B=6hNu ztO_lAeZtwc4)zV_tj<=sw{L}jG$oI-TggDC6X{|uT?tc6ppCtmO+dWfOT@Q%WOL2l z=OlFfxr6h2zLXEp1iZwyoi5-I|YtB z@kXtAB5JYF_*$>L=3d+b1U$DOqQBBSRa?gTBP;2JXxxMgMdu=i{NXaS z>2 zC9rN?FdFy3kkP)3XL||UT`Gn=BO84kFqLzqX9{9zd0?AfD?eyLuJA_U* z&}AeK4$WEx$#}J|WG&*}3M$de;8fA69+0;>hG2`*O89CDy%mg7>GTDv<_YNKIB)w89eM$em0{#%8E#`x60`rR<|(<)IvHjCTe>eK zt8I?QVvvqttJbVGO)$zfKn&*>N+<#FhT&O2Yk)<;R*H&@|+b0MsWDRRLN9#5z0|*1$cBE#X{sw zxhvISb+1uFWf;FO+2Lir-16FziW*=72^hdT$6RP9p=Gyc0Q8G!jqngzrUsD0hNIdm z4-}kobyNubZA4i}+oLCJuSmlW#w|yk(4E@!LotOShz-5OV$LKIYBEpE;SS6oXs~7k zmDpO8S3&>Y?a%AzjC<@%tjAs(gOwaY+Uy1`}8_{1w z5%L+OQxS;MmrY$1Iv{LzgtIelb$WLB7%P~5g%Hx6=jTUQ)8kSXrr%31k4SJze_pNI zaUb<}-HnM)i#}lwe;y9g&q1C>8sh<+H4b}tSMl{z{ zF38kOJ@aMX6w8?^Q%k(plNIC5P13}f!h>MY+sk(Q_INZnghyfqhp*q(l;lF#Dy?O2 z2BC5mbb#`>KxlB1a>C)wbSm#hd-s{hXYjF_#SX?*kv?$^Tl`EfvU-+_O*e@N*J8Q#DU$p5R@rN2t z8R=R+x+X-Z$fjg!yC_Bt`?2h~qk93c>R!o#zqchPxVd6%F?|W~8?EtB zojZR@U+WZbuf4Go-j-8N;s-gp62n0?sab$Hs;PT*+#7R~Av*N!Kp2UX>Kkgec5y&H z^HB>7x?PUn5OiSmZVv>G4G|xWOX?dB?^b;l)BR(*Qj-0t0DbKT38*`modN}&>xdwf zL!t0N?L7x1^!t|w9;^ozg-vq|6j?e~^mlEjk%nSGbaa$%LLlNdrIv#R5QvWYLMDpS zHL4U%Rt9)Pi9!$RCQLm?|6hgwzgY_HM){>Ur!GPjxq"."VehicleDataTable" WHERE vehicleName='fwdemo-android-' ORDER BY time DESC LIMIT 1000 + ``` + +1. **Optional:** If you want to clean up the resources created by the `provision.sh` and + `setup-iotfleetwise.sh` scripts, run the following command. **Note:** this will not delete the + Amazon Timestream database. + + ```bash + ./clean-up.sh + ``` + +## Android Automotive User Guide + +This guide demonstrates AWS IoT FleetWise using the Android emulator running +[Android Automotive (AAOS)](https://source.android.com/docs/automotive/start/what_automotive). +**Note:** this guide uses the pre-built image for AAOS, which means the app will not have access to +privileged VHAL properties. To demonstrate the app accessing privileged VHAL properties, follow the +[Android Automotive system image build guide](#android-automotive-system-image-build-guide). + +**Prerequisites:** + +- A local x86_64 Ubuntu 20.04 machine with the + [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) + installed. + +### Android emulator setup and app installation + +1. Install the preview version of Android Studio: https://developer.android.com/studio/preview + +1. From the 'Welcome to Android Studio' window (close any open project), click on `...` -> SDK + Manager. Under 'SDK Platforms', select 'Show Package Details'. Under 'Android 12L ("Sv2")', + select 'Automotive with Play Store Intel x86_64 Atom System Image'. Under 'SDK Tools', select + 'Android SDK Platform-Tools'. Click Apply, to download and install the packages. + +1. From the 'Welcome to Android Studio' window, click on `...` -> Virtual Device Manager. Under + Virtual, select 'Create Device'. Under category, select Automotive, then Next, leave the + recommended release selected, then Next and Finish. + +1. Click the refresh button, then the play button to start the emulator. After some time the AAOS + home screen is shown. + +1. Run the following on your local machine to download and install the app: + + ```bash + wget https://github.com/aws/aws-iot-fleetwise-edge/releases/latest/download/aws-iot-fleetwise-edge.apk \ + && ~/Android/Sdk/platform-tools/adb install aws-iot-fleetwise-edge.apk + ``` + +### Provision credentials and collect data + +1. Run the following script to provision credentials for the app to connect to your AWS account. + + ```bash + git clone https://github.com/aws/aws-iot-fleetwise-edge.git ~/aws-iot-fleetwise-edge \ + && cd ~/aws-iot-fleetwise-edge/tools/android-app/cloud \ + && ./provision.sh + ``` + +1. Run the following to configure the app with the credentials. This will cause the app to connect + to the cloud and you should shortly see the status `MQTT connection: CONNECTED`. + + ```bash + CREDS=`cat config/creds.json` \ + && ESCAPED_CREDS=`printf "%q" "${CREDS}"` \ + && ~/Android/Sdk/platform-tools/adb shell am start \ + -a android.intent.action.VIEW \ + -n com.aws.iotfleetwise/.MainActivity \ + -e credentials "${ESCAPED_CREDS}" + ``` + +1. Run the following command to create an AWS IoT FleetWise campaign to collect GPS and AAOS VHAL + data and send it to Amazon Timestream. After a short time you should see that the status in the + app is updated with: + `Campaign ARNs: arn:aws:iotfleetwise:us-east-1:XXXXXXXXXXXX:campaign/fwdemo-android-XXXXXXXXXX-campaign`. + + ```bash + ./setup-iotfleetwise.sh + ``` + +1. Go to the + [Amazon Timestream Query editor](https://us-east-1.console.aws.amazon.com/timestream/home?region=us-east-1#query-editor:) + and run the query suggested by the `setup-iotfleetwise.sh` script, which will be of the form: + + ``` + SELECT * FROM "IoTFleetWiseDB-"."VehicleDataTable" WHERE vehicleName='fwdemo-android-' ORDER BY time DESC LIMIT 1000 + ``` 1. **Optional:** If you want to clean up the resources created by the `provision.sh` and `setup-iotfleetwise.sh` scripts, run the following command. **Note:** this will not delete the @@ -78,14 +171,14 @@ available [ELM327 Bluetooth OBD adapter](https://www.amazon.com/s?k=elm327+bluet ./clean-up.sh ``` -## Developer guide +## Android Developer Guide This guide details how to build the app from source code and use the shared library in your own Android apps. -### Build guide +### App build guide -An Ubuntu 20.04 development machine with 200GB free disk space should be used. +An x86_64 Ubuntu 20.04 development machine with 200GB free disk space should be used. 1. Clone the source code: @@ -105,6 +198,7 @@ An Ubuntu 20.04 development machine with 200GB free disk space should be used. ```bash ./tools/build-fwe-cross-android.sh \ && ./tools/build-dist.sh \ + build/x86_64/src/executionmanagement/libaws-iot-fleetwise-edge.so:x86_64 \ build/arm64-v8a/src/executionmanagement/libaws-iot-fleetwise-edge.so:arm64-v8a \ build/armeabi-v7a/src/executionmanagement/libaws-iot-fleetwise-edge.so:armeabi-v7a ``` @@ -113,7 +207,7 @@ An Ubuntu 20.04 development machine with 200GB free disk space should be used. ```bash mkdir -p tools/android-app/app/src/main/jniLibs \ - && cp -r build/dist/arm64-v8a build/dist/armeabi-v7a tools/android-app/app/src/main/jniLibs \ + && cp -r build/dist/x86_64 build/dist/arm64-v8a build/dist/armeabi-v7a tools/android-app/app/src/main/jniLibs \ && cp THIRD-PARTY-LICENSES tools/android-app/app/src/main/assets \ && cd tools/android-app \ && ANDROID_HOME=/usr/local/android_sdk ./gradlew assemble @@ -121,7 +215,123 @@ An Ubuntu 20.04 development machine with 200GB free disk space should be used. ### Shared library interface -The C++ code for AWS IoT FleetWise Edge is compiled into a shared library using the Android NDK. The -interface for shared library can be found in the JNI wrapper class -`app/src/main/java/com/aws/iotfleetwise/Fwe.java`. The shared library can also be used in your app -using this interface, which includes a method to ingest raw CAN frame data. +The C++ code is compiled into a shared library using the Android NDK. The interface for shared +library can be found in the JNI wrapper class `app/src/main/java/com/aws/iotfleetwise/Fwe.java`. The +shared library can also be used in your app using this interface, which includes a method to ingest +raw CAN frame data. + +### Android Automotive system image build guide + +This guide details how to build the Android Automotive system image from source, in order for the +app to have access to the privileged VHAL properties. These properties are only accessible to apps +signed with the platform certificate, which in this case will be the +[standard Android test certificate](https://android.googlesource.com/platform/build/+/master/target/product/security/platform.x509.pem). + +**Prerequisites:** + +- A high performance x86_64 Ubuntu 20.04 development machine (e.g. an `m6a.8xlarge` EC2 instance) + with 300 GB free storage space. +- A local x86_64 Ubuntu 20.04 machine with [Android Studio](https://developer.android.com/studio) + and the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) + installed. + +**References:** + +- [Android Virtual Device as a Development Platform](https://source.android.com/docs/automotive/start/avd/android_virtual_device) +- [AAOS car AVD tools](https://android.googlesource.com/device/generic/car/+/refs/heads/master/tools) + +1. _On the development machine_, install the dependencies for compiling the system image: + + ```bash + sudo apt update \ + && DEBIAN_FRONTEND=noninteractive sudo -E apt install -y default-jdk zip unzip libncurses5 binutils python curl \ + && sudo curl -o /usr/local/bin/repo https://storage.googleapis.com/git-repo-downloads/repo \ + && sudo chmod +x /usr/local/bin/repo \ + && git config user.name > /dev/null || git config --global user.name "ubuntu" \ + && git config user.email > /dev/null || git config --global user.email "ubuntu@`hostname`" \ + && git config color.ui || git config --global color.ui false + ``` + +1. Run the following commands to build the Android Automotive system image. **This will take several + hours.** + + ```bash + ANDROID_BRANCH="android12L-release" \ + && REPO_URL="https://android.googlesource.com/platform/manifest" \ + && mkdir $ANDROID_BRANCH \ + && cd $ANDROID_BRANCH \ + && repo init -u $REPO_URL -b $ANDROID_BRANCH --partial-clone \ + && repo sync -c -j8 \ + && source build/envsetup.sh \ + && lunch sdk_car_x86_64-userdebug \ + && m -j`nproc` + ``` + +1. Create the file `emu_img_zip.mk` and add the content from + [here](https://cs.android.com/android/platform/superproject/+/master:device/generic/goldfish/tasks/emu_img_zip.mk), + then create the Android Virtual Device (AVD) image ZIP file. + + ```bash + m emu_img_zip + ``` + +1. _On your local machine_, run the following to download and unzip the AVD image ZIP file in the + `~/Android/Sdk/system-images` folder: + + ```bash + scp -i ubuntu@:/home/ubuntu/android12L-release/out/target/product/emulator_car_x86_64/sdk-repo-linux-system-images-eng.ubuntu.zip . \ + && unzip -d ~/Android/Sdk/system-images/car_avd \ + sdk-repo-linux-system-images-eng.ubuntu.zip + ``` + +1. Create the file `create_avd_config.sh` and add the content from + [here](https://android.googlesource.com/device/generic/car/+/refs/heads/master/tools/create_avd_config.sh), + then create the AVD config file (with display settings 213 DPI, 1920x1080 resolution, and 3584 MB + of memory): + + ```bash + bash create_avd_config.sh \ + car_avd \ + ~ \ + $HOME/Android/Sdk/system-images/car_avd/x86_64/ \ + 213 \ + 1920 \ + 1080 \ + 3584 \ + x86_64 + ``` + +1. Run the emulator, which will start AAOS: + + ```bash + ANDROID_SDK_ROOT=~/Android/Sdk ~/Android/Sdk/emulator/emulator -avd car_avd + ``` + +1. Copy the platform key and certificate from the development machine (this is the + [standard Android test certificate](https://android.googlesource.com/platform/build/+/master/target/product/security/platform.x509.pem)): + + ```bash + scp -i ubuntu@:/home/ubuntu/android12L-release/build/target/product/security/platform.x509.pem . + scp -i ubuntu@:/home/ubuntu/android12L-release/build/target/product/security/platform.pk8 . + ``` + +1. Download the app from GitHub and re-sign it with the platform key, so that the app has access to + the AAOS VHAL properties with privileged level permissions: + + ```bash + curl -o aws-iot-fleetwise-edge-original.apk https://github.com/aws/aws-iot-fleetwise-edge/releases/latest/download/aws-iot-fleetwise-edge.apk \ + && `ls -d ~/Android/Sdk/build-tools/* | tail -n -1`/apksigner sign \ + --key platform.pk8 \ + --cert platform.x509.pem \ + --out aws-iot-fleetwise-edge.apk \ + aws-iot-fleetwise-edge-original.apk + ``` + +1. Install the app using `adb`: + + ```bash + ~/Android/Sdk/platform-tools/adb install aws-iot-fleetwise-edge.apk + ``` + +1. You can now follow the [this section](#provision-credentials-and-collect-data) to provision + credentials and collect data. diff --git a/tools/android-app/app/build.gradle b/tools/android-app/app/build.gradle index 7f319f53..8e669391 100644 --- a/tools/android-app/app/build.gradle +++ b/tools/android-app/app/build.gradle @@ -8,7 +8,7 @@ android { defaultConfig { applicationId "com.aws.iotfleetwise" - minSdk 26 + minSdk 21 targetSdk 33 versionCode 1 versionName "1.0" @@ -23,6 +23,8 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + + useLibrary 'android.car' } dependencies { diff --git a/tools/android-app/app/src/main/AndroidManifest.xml b/tools/android-app/app/src/main/AndroidManifest.xml index 0b678dbc..8bed250e 100644 --- a/tools/android-app/app/src/main/AndroidManifest.xml +++ b/tools/android-app/app/src/main/AndroidManifest.xml @@ -9,6 +9,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + deviceListAdapter = new ArrayAdapter<>(this, R.layout.bluetooth_device); ListView deviceListView = findViewById(R.id.device_list); deviceListView.setAdapter(deviceListAdapter); @@ -34,11 +40,19 @@ protected void onCreate(Bundle savedInstanceState) { return; } Set devices = adapter.getBondedDevices(); + if (devices == null) { + deviceListAdapter.add("Error getting devices"); + return; + } for (BluetoothDevice device : devices) { ParcelUuid[] uuids = device.getUuids(); + if (uuids == null) { + continue; + } for (ParcelUuid uuid : uuids) { if (uuid.getUuid().equals(Elm327.SERIAL_PORT_UUID)) { deviceListAdapter.add(device.getName() + "\t" + device.getAddress()); + break; } } } @@ -58,4 +72,10 @@ protected void onCreate(Bundle savedInstanceState) { finish(); }); } + + @Override + public boolean onSupportNavigateUp() { + finish(); + return true; + } } diff --git a/tools/android-app/app/src/main/java/com/aws/iotfleetwise/ConfigureVehicleActivity.java b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/ConfigureVehicleActivity.java index 1f466d9e..fcc98600 100644 --- a/tools/android-app/app/src/main/java/com/aws/iotfleetwise/ConfigureVehicleActivity.java +++ b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/ConfigureVehicleActivity.java @@ -10,12 +10,19 @@ import android.text.TextWatcher; import android.widget.EditText; -public class ConfigureVehicleActivity extends Activity { +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; + +public class ConfigureVehicleActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_configure_vehicle); setResult(Activity.RESULT_CANCELED); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } EditText linkEditText = findViewById(R.id.provisioning_link); linkEditText.addTextChangedListener(new TextWatcher() { @Override @@ -35,4 +42,10 @@ public void afterTextChanged(Editable editable) { } }); } + + @Override + public boolean onSupportNavigateUp() { + finish(); + return true; + } } diff --git a/tools/android-app/app/src/main/java/com/aws/iotfleetwise/Fwe.java b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/Fwe.java index 3b266150..7f499858 100644 --- a/tools/android-app/app/src/main/java/com/aws/iotfleetwise/Fwe.java +++ b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/Fwe.java @@ -58,6 +58,23 @@ public native static int run( */ public native static void setLocation(double latitude, double longitude); + /** + * Returns an array of Android Automotive vehicle property info + * @return vehicle property info, with each member containing an array with 4 values: + * - Vehicle property ID + * - Area index + * - Result index + * - Signal ID + */ + public native static int[][] getVehiclePropertyInfo(); + + /** + * Set an Android Automotive vehicle property + * @param signalId Signal ID + * @param value Vehicle property value + */ + public native static void setVehicleProperty(int signalId, double value); + /** * Get a status summary * @return Status summary diff --git a/tools/android-app/app/src/main/java/com/aws/iotfleetwise/FweApplication.java b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/FweApplication.java index a818da10..98dbd9af 100644 --- a/tools/android-app/app/src/main/java/com/aws/iotfleetwise/FweApplication.java +++ b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/FweApplication.java @@ -3,7 +3,13 @@ package com.aws.iotfleetwise; +import android.annotation.SuppressLint; import android.app.Application; +import android.car.Car; +import android.car.VehiclePropertyIds; +import android.car.hardware.CarPropertyConfig; +import android.car.hardware.CarPropertyValue; +import android.car.hardware.property.CarPropertyManager; import android.content.Context; import android.content.SharedPreferences; import android.location.Location; @@ -14,10 +20,14 @@ import android.preference.PreferenceManager; import android.util.Log; +import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; +import java.util.Set; public class FweApplication extends Application @@ -33,7 +43,10 @@ public class FweApplication private LocationManager mLocationManager = null; private Location mLastLocation = null; private List mSupportedPids = null; - private final Object mSupportedPidsLock = new Object(); + private final Object mSupportedSignalsLock = new Object(); + private CarPropertyManager mCarPropertyManager = null; + private boolean mReadVehicleProperties = false; + private List mSupportedVehicleProperties = null; @Override public void onLocationChanged(Location loc) { @@ -73,7 +86,14 @@ public void onCreate() { mPrefs = PreferenceManager.getDefaultSharedPreferences(this); mPrefs.registerOnSharedPreferenceChangeListener(this); onSharedPreferenceChanged(null, null); - mElm327 = new Elm327(); + // Try creating the CarPropertyManager to detect whether we are running on Android Automotive + try { + mCarPropertyManager = (CarPropertyManager)Car.createCar(this).getCarManager(Car.PROPERTY_SERVICE); + } + catch (NoClassDefFoundError ignored) { + // Not Android Automotive, fall back to ELM327 mode + mElm327 = new Elm327(); + } mDataAcquisitionThread.start(); } @@ -163,15 +183,24 @@ private static int[] convertResponse(String response) } } // Convert list to array: - return responseList.stream().mapToInt(Integer::intValue).toArray(); + int[] arr = new int[responseList.size()]; + for (int i = 0; i < responseList.size(); i++) { + arr[i] = responseList.get(i); + } + return arr; } Thread mDataAcquisitionThread = new Thread(() -> { while (true) { Log.i("FweApplication", "Starting data acquisition"); - String bluetoothDevice = mPrefs.getString("bluetooth_device", ""); - serviceOBD(bluetoothDevice); + if (isCar()) { + serviceCarProperties(); + } + else { + String bluetoothDevice = mPrefs.getString("bluetooth_device", ""); + serviceOBD(bluetoothDevice); + } serviceLocation(); // Wait for update time: @@ -189,10 +218,11 @@ private void serviceOBD(String bluetoothDevice) if (!checkVehicleConnected()) { return; } - int[] pidsToRequest = Arrays.stream(Fwe.getObdPidsToRequest()).sorted().toArray(); + int[] pidsToRequest = Fwe.getObdPidsToRequest(); if (pidsToRequest.length == 0) { return; } + Arrays.sort(pidsToRequest); List supportedPids = new ArrayList<>(); for (int pid : pidsToRequest) { if ((mSupportedPids != null) && !mSupportedPids.contains(pid)) { @@ -206,7 +236,7 @@ private void serviceOBD(String bluetoothDevice) Log.e("FweApplication", String.format("No response for PID: 0x%02X", pid)); // If vehicle is disconnected: if (mSupportedPids != null) { - synchronized (mSupportedPidsLock) { + synchronized (mSupportedSignalsLock) { mSupportedPids = null; } return; @@ -223,7 +253,7 @@ private void serviceOBD(String bluetoothDevice) sb.append(String.format("%02X ", b)); } Log.i("FweApplication", "Supported PIDs: " + sb.toString()); - synchronized (mSupportedPidsLock) { + synchronized (mSupportedSignalsLock) { mSupportedPids = supportedPids; } } @@ -242,6 +272,111 @@ private boolean checkVehicleConnected() return result; } + private int[] getVehiclePropertyIds(int[][] vehiclePropertyInfo) + { + Set propIds = new LinkedHashSet<>(); + for (int[] info : vehiclePropertyInfo) + { + propIds.add(info[0]); + } + int[] arr = new int[propIds.size()]; + int i = 0; + for (Integer id : propIds) + { + arr[i++] = id; + } + return arr; + } + + private int getVehiclePropertySignalId(int[][] vehiclePropertyInfo, int propId, int areaIndex, int resultIndex) + { + for (int[] info : vehiclePropertyInfo) + { + if ((propId == info[0]) && (areaIndex == info[1]) && (resultIndex == info[2])) + { + return info[3]; + } + } + return -1; + } + + @SuppressLint("DefaultLocale") + private void serviceCarProperties() + { + List supportedProps = new ArrayList<>(); + int[][] propInfo = Fwe.getVehiclePropertyInfo(); + int[] propIds = getVehiclePropertyIds(propInfo); + for (int propId : propIds) { + String propName = VehiclePropertyIds.toString(propId); + CarPropertyConfig config = mCarPropertyManager.getCarPropertyConfig(propId); + if (config == null) { + Log.d("serviceCarProperties", "Property unavailable: "+propName); + continue; + } + int[] areaIds = config.getAreaIds(); + Class clazz = config.getPropertyType(); + for (int areaIndex = 0; areaIndex < areaIds.length; areaIndex++) { + int signalId = getVehiclePropertySignalId(propInfo, propId, areaIndex, 0); + if (signalId < 0) { + Log.d("serviceCarProperties", String.format("More area IDs (%d) than expected (%d) for %s", areaIds.length, areaIndex + 1, propName)); + break; + } + CarPropertyValue propVal; + try { + propVal = mCarPropertyManager.getProperty(clazz, propId, areaIds[areaIndex]); + } catch (IllegalArgumentException ignored) { + Log.w("serviceCarProperties", String.format("Could not get %s 0x%X", propName, areaIds[areaIndex])); + continue; + } catch (SecurityException e) { + Log.w("serviceCarProperties", String.format("Access denied for %s 0x%X", propName, areaIds[areaIndex])); + continue; + } + if (areaIndex == 0) { + supportedProps.add(propName); + } + StringBuilder sb = new StringBuilder(); + sb.append(String.format("%s 0x%X: ", propName, areaIds[areaIndex])); + if (clazz.equals(Boolean.class)) { + double val = (boolean) propVal.getValue() ? 1.0 : 0.0; + sb.append(val); + Fwe.setVehicleProperty(signalId, val); + } else if (clazz.equals(Integer.class) || clazz.equals(Float.class)) { + double val = ((Number)propVal.getValue()).doubleValue(); + sb.append(val); + Fwe.setVehicleProperty(signalId, val); + } else if (clazz.equals(Integer[].class) || clazz.equals(Long[].class)) { + sb.append("["); + for (int resultIndex = 0; resultIndex < Array.getLength(propVal.getValue()); resultIndex++) { + if (resultIndex > 0) { + signalId = getVehiclePropertySignalId(propInfo, propId, areaIndex, resultIndex); + if (signalId < 0) { + Log.d("serviceCarProperties", String.format("More results (%d) than expected (%d) for %s 0x%X", Array.getLength(propVal.getValue()), resultIndex + 1, propName, areaIds[areaIndex])); + break; + } + } + double val = ((Number)Array.get(propVal.getValue(), resultIndex)).doubleValue(); + if (resultIndex > 0) { + sb.append(", "); + } + sb.append(val); + Fwe.setVehicleProperty(signalId, val); + } + sb.append("]"); + } else { + Log.w("serviceCarProperties", "Unsupported type " + clazz.toString() + " for " + propName); + continue; + } + Log.i("serviceCarProperties", sb.toString()); + } + } + if ((mSupportedVehicleProperties == null) && (supportedProps.size() > 0)) { + Collections.sort(supportedProps); + synchronized (mSupportedSignalsLock) { + mSupportedVehicleProperties = supportedProps; + } + } + } + Thread mFweThread = new Thread(() -> { Log.i("FweApplication", "Starting FWE"); String vehicleName = mPrefs.getString("vehicle_name", ""); @@ -267,25 +402,43 @@ private boolean checkVehicleConnected() public String getStatusSummary() { - String supportedPids; - synchronized (mSupportedPidsLock) { - if (mSupportedPids == null) { - supportedPids = "VEHICLE DISCONNECTED"; - } - else if (mSupportedPids.size() == 0) { - supportedPids = "NONE"; + StringBuilder sb = new StringBuilder(); + synchronized (mSupportedSignalsLock) { + if (isCar()) { + if (mSupportedVehicleProperties != null) { + if (mSupportedVehicleProperties.size() == 0) { + sb.append("NONE"); + } else { + sb.append("Supported vehicle properties: ") + .append(String.join(", ", mSupportedVehicleProperties)); + } + } } else { - StringBuilder sb = new StringBuilder(); - for (int pid : mSupportedPids) { - sb.append(String.format("%02X ", pid)); + sb.append("Bluetooth: ") + .append(mElm327.getStatus()) + .append("\n\n") + .append("Supported OBD PIDs: "); + if (mSupportedPids == null) { + sb.append("VEHICLE DISCONNECTED"); + } else if (mSupportedPids.size() == 0) { + sb.append("NONE"); + } else { + for (int pid : mSupportedPids) { + sb.append(String.format("%02X ", pid)); + } } - supportedPids = sb.toString(); } } - return "Bluetooth: " + mElm327.getStatus() + "\n\n" - + "Supported OBD PIDs: " + supportedPids + "\n\n" - + "Location: " + getLocationSummary() + "\n\n" - + Fwe.getStatusSummary(); + sb.append("\n\n") + .append("Location: ") + .append(getLocationSummary()) + .append("\n\n") + .append(Fwe.getStatusSummary()); + return sb.toString(); + } + + public boolean isCar() { + return mCarPropertyManager != null; } } diff --git a/tools/android-app/app/src/main/java/com/aws/iotfleetwise/MainActivity.java b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/MainActivity.java index 99c010c5..c58b166e 100644 --- a/tools/android-app/app/src/main/java/com/aws/iotfleetwise/MainActivity.java +++ b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/MainActivity.java @@ -3,6 +3,7 @@ package com.aws.iotfleetwise; +import android.car.Car; import android.Manifest; import android.app.Activity; import android.app.AlertDialog; @@ -14,12 +15,14 @@ import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceManager; +import android.preference.PreferenceScreen; import android.util.JsonReader; import android.util.Log; import android.view.View; import android.view.WindowManager; import android.widget.TextView; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -52,21 +55,32 @@ private void requestPermissions() { List perms = new ArrayList<>(); perms.add(Manifest.permission.ACCESS_FINE_LOCATION); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + String rationale = "Location"; + if (((FweApplication)getApplication()).isCar()) { + perms.add(Car.PERMISSION_ENERGY); + perms.add(Car.PERMISSION_SPEED); + rationale += " and car information"; + } + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { perms.add(Manifest.permission.BLUETOOTH_CONNECT); + rationale += " and Bluetooth"; } String[] permsArray = perms.toArray(new String[0]); if (!EasyPermissions.hasPermissions(this, permsArray)) { - EasyPermissions.requestPermissions(this, "Location and Bluetooth access required", REQUEST_PERMISSIONS, permsArray); + Log.i("requestPermissions", "Requesting permissions"); + EasyPermissions.requestPermissions(this, rationale+" access required", REQUEST_PERMISSIONS, permsArray); } else { + Log.i("requestPermissions", "Permissions granted, starting data acquisition"); ((FweApplication)getApplication()).requestLocationUpdates(); } } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - findPreference("bluetooth_device").setSummary(mPrefs.getString("bluetooth_device", "No device selected")); + if (!((FweApplication)getApplication()).isCar()) { + findPreference("bluetooth_device").setSummary(mPrefs.getString("bluetooth_device", "No device selected")); + } findPreference("vehicle_name").setSummary(mPrefs.getString("vehicle_name", "Not yet configured")); findPreference("update_time").setSummary(mPrefs.getString("update_time", String.valueOf(R.string.default_update_time))); } @@ -106,32 +120,54 @@ public void onCreate(Bundle savedInstanceState) requestPermissions(); onSharedPreferenceChanged(null, null); Preference bluetoothDevicePreference = (Preference)findPreference("bluetooth_device"); - bluetoothDevicePreference.setOnPreferenceClickListener(preference -> { - startActivityForResult(new Intent(MainActivity.this, BluetoothActivity.class), REQUEST_BLUETOOTH); - return false; - }); - Preference vehicleNamePreference = (Preference)findPreference("vehicle_name"); - vehicleNamePreference.setOnPreferenceClickListener(preference -> { - startActivityForResult(new Intent(MainActivity.this, ConfigureVehicleActivity.class), REQUEST_CONFIGURE_VEHICLE); - return false; - }); + if (((FweApplication)getApplication()).isCar()) { + PreferenceScreen preferenceScreen = getPreferenceScreen(); + preferenceScreen.removePreference(bluetoothDevicePreference); + } + else { + bluetoothDevicePreference.setOnPreferenceClickListener(preference -> { + startActivityForResult(new Intent(MainActivity.this, BluetoothActivity.class), REQUEST_BLUETOOTH); + return false; + }); + Preference vehicleNamePreference = (Preference)findPreference("vehicle_name"); + vehicleNamePreference.setOnPreferenceClickListener(preference -> { + startActivityForResult(new Intent(MainActivity.this, ConfigureVehicleActivity.class), REQUEST_CONFIGURE_VEHICLE); + return false; + }); + } mStatusUpdateThread.start(); // Handle deep link: - Intent appLinkIntent = getIntent(); - Uri appLinkData = appLinkIntent.getData(); + Uri appLinkData = getIntent().getData(); if (appLinkData != null) { - final String link = appLinkData.toString(); - if (link != null) { - downloadCredentials(link); + downloadCredentials(appLinkData.toString()); + } + // Handle ADB provided credentials: + String credentials = getIntent().getStringExtra("credentials"); + if (credentials != null) { + InputStream inputStream = new ByteArrayInputStream(credentials.getBytes(StandardCharsets.UTF_8)); + try { + configureCredentials(inputStream); + } catch (IOException ignored) { + runOnUiThread(() -> { + AlertDialog alertDialog = new AlertDialog.Builder(MainActivity.this).create(); + alertDialog.setTitle("Error"); + alertDialog.setMessage("Invalid credentials"); + alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", (dialog, which) -> dialog.dismiss()); + alertDialog.show(); + }); } } } @Override public void onDestroy() { - mStatusUpdateThread.interrupt(); - mPrefs.unregisterOnSharedPreferenceChangeListener(this); + if (mStatusUpdateThread != null) { + mStatusUpdateThread.interrupt(); + } + if (mPrefs != null) { + mPrefs.unregisterOnSharedPreferenceChangeListener(this); + } super.onDestroy(); } @@ -139,14 +175,14 @@ public void onAboutClick(View v) { startActivityForResult(new Intent(MainActivity.this, AboutActivity.class), REQUEST_ABOUT); } - private void downloadCredentials(String deepLink) + private void downloadCredentials(String provisioningLink) { Thread t = new Thread(() -> { - Log.i("DownloadCredentials", "Deep link: " + deepLink); + Log.i("DownloadCredentials", "Provisioning link: " + provisioningLink); final String urlParam = "url="; - // First try getting the S3 link normally from the deep link: - Uri uri = Uri.parse(deepLink); + // First try getting the S3 link normally from the provisioning link: + Uri uri = Uri.parse(provisioningLink); String fragment = uri.getFragment(); if (fragment != null) { int urlStart = fragment.indexOf(urlParam); @@ -158,10 +194,10 @@ private void downloadCredentials(String deepLink) } } - // Some QR code scanning apps url decode the deep link, so try that next: - int urlStart = deepLink.indexOf(urlParam); + // Some QR code scanning apps url decode the provisioning link, so try that next: + int urlStart = provisioningLink.indexOf(urlParam); if (urlStart >= 0) { - String s3Link = deepLink.substring(urlStart + urlParam.length()); + String s3Link = provisioningLink.substring(urlStart + urlParam.length()); if (downloadCredentialsFromS3(s3Link)) { return; } @@ -171,7 +207,7 @@ private void downloadCredentials(String deepLink) runOnUiThread(() -> { AlertDialog alertDialog = new AlertDialog.Builder(MainActivity.this).create(); alertDialog.setTitle("Error"); - alertDialog.setMessage("Invalid credentials link"); + alertDialog.setMessage("Invalid provisioning link"); alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", (dialog, which) -> dialog.dismiss()); alertDialog.show(); }); @@ -179,63 +215,68 @@ private void downloadCredentials(String deepLink) t.start(); } + private boolean configureCredentials(InputStream inputStream) throws IOException { + JsonReader reader = new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + String vehicleName = null; + String endpointUrl = null; + String certificate = null; + String privateKey = null; + String mqttTopicPrefix = ""; + reader.beginObject(); + while (reader.hasNext()) { + switch (reader.nextName()) { + case "vehicle_name": + vehicleName = reader.nextString(); + break; + case "endpoint_url": + endpointUrl = reader.nextString(); + break; + case "certificate": + certificate = reader.nextString(); + break; + case "private_key": + privateKey = reader.nextString(); + break; + case "mqtt_topic_prefix": + mqttTopicPrefix = reader.nextString(); + break; + default: + reader.skipValue(); + break; + } + } + reader.endObject(); + if (vehicleName != null && endpointUrl != null && certificate != null && privateKey != null) + { + Log.i("configureCredentials", "Configured credentials for vehicle name "+vehicleName); + SharedPreferences.Editor edit = mPrefs.edit(); + edit.putString("vehicle_name", vehicleName); + edit.putString("mqtt_endpoint_url", endpointUrl); + edit.putString("mqtt_certificate", certificate); + edit.putString("mqtt_private_key", privateKey); + edit.putString("mqtt_topic_prefix", mqttTopicPrefix); + edit.apply(); + return true; + } + return false; + } + private boolean downloadCredentialsFromS3(String s3Link) { + boolean res = false; try { Log.i("DownloadCredentials", "Trying to download from " + s3Link); URL url = new URL(s3Link); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); try { - InputStream inputStream = urlConnection.getInputStream(); - JsonReader reader = new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); - String vehicleName = null; - String endpointUrl = null; - String certificate = null; - String privateKey = null; - String mqttTopicPrefix = ""; - reader.beginObject(); - while (reader.hasNext()) { - switch (reader.nextName()) { - case "vehicle_name": - vehicleName = reader.nextString(); - break; - case "endpoint_url": - endpointUrl = reader.nextString(); - break; - case "certificate": - certificate = reader.nextString(); - break; - case "private_key": - privateKey = reader.nextString(); - break; - case "mqtt_topic_prefix": - mqttTopicPrefix = reader.nextString(); - break; - default: - reader.skipValue(); - break; - } - } - reader.endObject(); - if (vehicleName != null && endpointUrl != null && certificate != null && privateKey != null) - { - Log.i("DownloadCredentials", "Downloaded credentials for vehicle name "+vehicleName); - SharedPreferences.Editor edit = mPrefs.edit(); - edit.putString("vehicle_name", vehicleName); - edit.putString("mqtt_endpoint_url", endpointUrl); - edit.putString("mqtt_certificate", certificate); - edit.putString("mqtt_private_key", privateKey); - edit.putString("mqtt_topic_prefix", mqttTopicPrefix); - edit.apply(); - return true; - } + res = configureCredentials(urlConnection.getInputStream()); } finally { urlConnection.disconnect(); } } catch (IOException e) { e.printStackTrace(); } - return false; + return res; } Thread mStatusUpdateThread = new Thread(() -> { diff --git a/tools/android-app/app/src/main/res/layout/activity_bluetooth_device_list.xml b/tools/android-app/app/src/main/res/layout/activity_bluetooth_device_list.xml index 1c807597..656b060c 100644 --- a/tools/android-app/app/src/main/res/layout/activity_bluetooth_device_list.xml +++ b/tools/android-app/app/src/main/res/layout/activity_bluetooth_device_list.xml @@ -5,12 +5,6 @@ android:orientation="vertical" android:padding="10dp"> - - - - - AWS IoT FleetWise Edge + FWE Configure vehicle Use your QR code scanner app to scan a provisioning QR code.\n\nAlternatively paste a provisioning link below: Provisioning link @@ -11,5 +11,5 @@ 5 About Open source licenses: - Learn more about AWS IoT FleetWise Edge at:\nhttps://github.com/aws/aws-iot-fleetwise-edge + Learn more about AWS IoT FleetWise at:\nhttps://github.com/aws/aws-iot-fleetwise-edge diff --git a/tools/android-app/cloud/.gitignore b/tools/android-app/cloud/.gitignore index f733c4b5..07bffee7 100644 --- a/tools/android-app/cloud/.gitignore +++ b/tools/android-app/cloud/.gitignore @@ -1 +1,4 @@ config/ +aaos-vhal-types.h +aaos-vhal-fqns.txt +fwdemo-android-*-timestream-result.json diff --git a/tools/android-app/cloud/aaosVhalDecoders.json b/tools/android-app/cloud/aaosVhalDecoders.json new file mode 100644 index 00000000..382f28b5 --- /dev/null +++ b/tools/android-app/cloud/aaosVhalDecoders.json @@ -0,0 +1,8514 @@ +[ + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_MODEL_YEAR", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289407235, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_FUEL_CAPACITY", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 291504388, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_FUEL_TYPE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289472773, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_FUEL_TYPE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289472773, + "factor": 1, + "length": 1 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_FUEL_TYPE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289472773, + "factor": 1, + "length": 2 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_FUEL_TYPE_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289472773, + "factor": 1, + "length": 3 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_EV_BATTERY_CAPACITY", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 291504390, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_EV_CONNECTOR_TYPE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289472775, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_EV_CONNECTOR_TYPE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289472775, + "factor": 1, + "length": 1 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_EV_CONNECTOR_TYPE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289472775, + "factor": 1, + "length": 2 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_EV_CONNECTOR_TYPE_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289472775, + "factor": 1, + "length": 3 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_FUEL_DOOR_LOCATION", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289407240, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_EV_PORT_LOCATION", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289407241, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_DRIVER_SEAT", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356516106, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_EXTERIOR_DIMENSIONS_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289472779, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_EXTERIOR_DIMENSIONS_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289472779, + "factor": 1, + "length": 1 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_EXTERIOR_DIMENSIONS_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289472779, + "factor": 1, + "length": 2 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_EXTERIOR_DIMENSIONS_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289472779, + "factor": 1, + "length": 3 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_EXTERIOR_DIMENSIONS_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289472779, + "factor": 1, + "length": 4 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_EXTERIOR_DIMENSIONS_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289472779, + "factor": 1, + "length": 5 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_EXTERIOR_DIMENSIONS_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289472779, + "factor": 1, + "length": 6 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_EXTERIOR_DIMENSIONS_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289472779, + "factor": 1, + "length": 7 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_MULTI_EV_PORT_LOCATIONS_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289472780, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.INFO_MULTI_EV_PORT_LOCATIONS_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289472780, + "factor": 1, + "length": 1 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.PERF_ODOMETER", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 291504644, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.PERF_VEHICLE_SPEED", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 291504647, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.PERF_VEHICLE_SPEED_DISPLAY", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 291504648, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.PERF_STEERING_ANGLE", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 291504649, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.PERF_REAR_STEERING_ANGLE", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 291504656, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.ENGINE_COOLANT_TEMP", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 291504897, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.ENGINE_OIL_LEVEL", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289407747, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.ENGINE_OIL_TEMP", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 291504900, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.ENGINE_RPM", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 291504901, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WHEEL_TICK_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 290521862, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WHEEL_TICK_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 290521862, + "factor": 1, + "length": 1 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WHEEL_TICK_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 290521862, + "factor": 1, + "length": 2 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WHEEL_TICK_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 290521862, + "factor": 1, + "length": 3 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WHEEL_TICK_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 290521862, + "factor": 1, + "length": 4 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.FUEL_LEVEL", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 291504903, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.FUEL_DOOR_OPEN", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 287310600, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.EV_BATTERY_LEVEL", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 291504905, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.EV_CHARGE_PORT_OPEN", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 287310602, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.EV_CHARGE_PORT_CONNECTED", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 287310603, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.EV_BATTERY_INSTANTANEOUS_CHARGE_RATE", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 291504908, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.RANGE_REMAINING", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 291504904, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.TIRE_PRESSURE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 392168201, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.TIRE_PRESSURE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 392168201, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.TIRE_PRESSURE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 392168201, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.TIRE_PRESSURE_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 392168201, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.GEAR_SELECTION", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289408000, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.CURRENT_GEAR", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289408001, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.PARKING_BRAKE_ON", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 287310850, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.PARKING_BRAKE_AUTO_APPLY", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 287310851, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.FUEL_LEVEL_LOW", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 287310853, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.NIGHT_MODE", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 287310855, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.TURN_SIGNAL_STATE", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289408008, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.IGNITION_STATE", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289408009, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.ABS_ACTIVE", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 287310858, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.TRACTION_CONTROL_ACTIVE", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 287310859, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_SPEED_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356517120, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_SPEED_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356517120, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_SPEED_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356517120, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_SPEED_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356517120, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_SPEED_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356517120, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_SPEED_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356517120, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_SPEED_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356517120, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_SPEED_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356517120, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_SPEED_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356517120, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356517121, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356517121, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356517121, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356517121, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356517121, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356517121, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356517121, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356517121, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356517121, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_CURRENT_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 358614274, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_CURRENT_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 358614274, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_CURRENT_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 358614274, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_CURRENT_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 358614274, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_CURRENT_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 358614274, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_CURRENT_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 358614274, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_CURRENT_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 358614274, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_CURRENT_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 358614274, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_CURRENT_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 358614274, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 358614275, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 358614275, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 358614275, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 358614275, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 358614275, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 358614275, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 358614275, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 358614275, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 358614275, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 320865540, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 320865540, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 320865540, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 320865540, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 320865540, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 320865540, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 320865540, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 320865540, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 320865540, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_9", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 9, + "offset": 320865540, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AC_ON_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 354419973, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AC_ON_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 354419973, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AC_ON_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 354419973, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AC_ON_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 354419973, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AC_ON_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 354419973, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AC_ON_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 354419973, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AC_ON_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 354419973, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AC_ON_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 354419973, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AC_ON_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 354419973, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_AC_ON_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 354419974, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_AC_ON_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 354419974, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_AC_ON_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 354419974, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_AC_ON_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 354419974, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_AC_ON_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 354419974, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_AC_ON_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 354419974, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_AC_ON_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 354419974, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_AC_ON_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 354419974, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_AC_ON_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 354419974, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_DEFROST_ON_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 354419975, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_DEFROST_ON_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 354419975, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_DEFROST_ON_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 354419975, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_DEFROST_ON_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 354419975, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_DEFROST_ON_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 354419975, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_DEFROST_ON_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 354419975, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_DEFROST_ON_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 354419975, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_DEFROST_ON_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 354419975, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_DEFROST_ON_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 354419975, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_RECIRC_ON_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 354419976, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_RECIRC_ON_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 354419976, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_RECIRC_ON_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 354419976, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_RECIRC_ON_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 354419976, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_RECIRC_ON_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 354419976, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_RECIRC_ON_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 354419976, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_RECIRC_ON_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 354419976, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_RECIRC_ON_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 354419976, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_RECIRC_ON_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 354419976, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DUAL_ON_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 354419977, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DUAL_ON_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 354419977, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DUAL_ON_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 354419977, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DUAL_ON_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 354419977, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DUAL_ON_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 354419977, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DUAL_ON_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 354419977, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DUAL_ON_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 354419977, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DUAL_ON_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 354419977, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DUAL_ON_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 354419977, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_ON_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 354419978, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_ON_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 354419978, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_ON_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 354419978, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_ON_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 354419978, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_ON_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 354419978, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_ON_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 354419978, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_ON_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 354419978, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_ON_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 354419978, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_ON_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 354419978, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356517131, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356517131, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356517131, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356517131, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356517131, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356517131, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356517131, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356517131, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356517131, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SIDE_MIRROR_HEAT_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 339739916, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SIDE_MIRROR_HEAT_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 339739916, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SIDE_MIRROR_HEAT_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 339739916, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_STEERING_WHEEL_HEAT", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289408269, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_DISPLAY_UNITS", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289408270, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ACTUAL_FAN_SPEED_RPM_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356517135, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ACTUAL_FAN_SPEED_RPM_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356517135, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ACTUAL_FAN_SPEED_RPM_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356517135, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ACTUAL_FAN_SPEED_RPM_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356517135, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ACTUAL_FAN_SPEED_RPM_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356517135, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ACTUAL_FAN_SPEED_RPM_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356517135, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ACTUAL_FAN_SPEED_RPM_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356517135, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ACTUAL_FAN_SPEED_RPM_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356517135, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ACTUAL_FAN_SPEED_RPM_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356517135, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_POWER_ON_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 354419984, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_POWER_ON_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 354419984, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_POWER_ON_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 354419984, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_POWER_ON_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 354419984, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_POWER_ON_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 354419984, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_POWER_ON_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 354419984, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_POWER_ON_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 354419984, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_POWER_ON_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 354419984, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_POWER_ON_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 354419984, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_0_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356582673, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_0_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356582673, + "factor": 1, + "length": 1 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_0_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356582673, + "factor": 1, + "length": 2 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_0_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356582673, + "factor": 1, + "length": 3 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_1_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356582673, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_1_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356582673, + "factor": 1, + "length": 1 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_1_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356582673, + "factor": 1, + "length": 2 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_1_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356582673, + "factor": 1, + "length": 3 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_2_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356582673, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_2_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356582673, + "factor": 1, + "length": 1 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_2_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356582673, + "factor": 1, + "length": 2 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_2_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356582673, + "factor": 1, + "length": 3 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_3_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356582673, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_3_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356582673, + "factor": 1, + "length": 1 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_3_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356582673, + "factor": 1, + "length": 2 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_3_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356582673, + "factor": 1, + "length": 3 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_4_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356582673, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_4_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356582673, + "factor": 1, + "length": 1 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_4_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356582673, + "factor": 1, + "length": 2 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_4_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356582673, + "factor": 1, + "length": 3 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_5_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356582673, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_5_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356582673, + "factor": 1, + "length": 1 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_5_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356582673, + "factor": 1, + "length": 2 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_5_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356582673, + "factor": 1, + "length": 3 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_6_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356582673, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_6_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356582673, + "factor": 1, + "length": 1 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_6_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356582673, + "factor": 1, + "length": 2 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_6_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356582673, + "factor": 1, + "length": 3 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_7_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356582673, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_7_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356582673, + "factor": 1, + "length": 1 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_7_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356582673, + "factor": 1, + "length": 2 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_7_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356582673, + "factor": 1, + "length": 3 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_8_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356582673, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_8_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356582673, + "factor": 1, + "length": 1 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_8_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356582673, + "factor": 1, + "length": 2 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_8_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356582673, + "factor": 1, + "length": 3 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_RECIRC_ON_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 354419986, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_RECIRC_ON_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 354419986, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_RECIRC_ON_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 354419986, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_RECIRC_ON_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 354419986, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_RECIRC_ON_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 354419986, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_RECIRC_ON_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 354419986, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_RECIRC_ON_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 354419986, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_RECIRC_ON_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 354419986, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_RECIRC_ON_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 354419986, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_VENTILATION_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356517139, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_VENTILATION_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356517139, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_VENTILATION_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356517139, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_VENTILATION_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356517139, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_VENTILATION_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356517139, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_VENTILATION_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356517139, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_VENTILATION_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356517139, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_VENTILATION_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356517139, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_VENTILATION_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356517139, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 320865556, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 320865556, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 320865556, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 320865556, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 320865556, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 320865556, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 320865556, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 320865556, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 320865556, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_9", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 9, + "offset": 320865556, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DISTANCE_DISPLAY_UNITS", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289408512, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.FUEL_VOLUME_DISPLAY_UNITS", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289408513, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.TIRE_PRESSURE_DISPLAY_UNITS", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289408514, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.EV_BATTERY_DISPLAY_UNITS", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289408515, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 287311364, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.VEHICLE_SPEED_DISPLAY_UNITS", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289408517, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.ENV_OUTSIDE_TEMPERATURE", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 291505923, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.AP_POWER_STATE_REQ_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289475072, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.AP_POWER_STATE_REQ_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289475072, + "factor": 1, + "length": 1 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.AP_POWER_STATE_REPORT_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289475073, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.AP_POWER_STATE_REPORT_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289475073, + "factor": 1, + "length": 1 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.AP_POWER_BOOTUP_REASON", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289409538, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DISPLAY_BRIGHTNESS", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289409539, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_POS_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 373295872, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_POS_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 373295872, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_POS_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 373295872, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_POS_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 373295872, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_POS_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 373295872, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_POS_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 373295872, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_POS_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 373295872, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_POS_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 373295872, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_MOVE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 373295873, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_MOVE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 373295873, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_MOVE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 373295873, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_MOVE_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 373295873, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_MOVE_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 373295873, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_MOVE_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 373295873, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_MOVE_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 373295873, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_MOVE_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 373295873, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_LOCK_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 371198722, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_LOCK_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 371198722, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_LOCK_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 371198722, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_LOCK_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 371198722, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_LOCK_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 371198722, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_LOCK_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 371198722, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_LOCK_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 371198722, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.DOOR_LOCK_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 371198722, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Z_POS_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 339741504, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Z_POS_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 339741504, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Z_POS_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 339741504, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Z_MOVE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 339741505, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Z_MOVE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 339741505, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Z_MOVE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 339741505, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Y_POS_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 339741506, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Y_POS_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 339741506, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Y_POS_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 339741506, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Y_MOVE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 339741507, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Y_MOVE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 339741507, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Y_MOVE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 339741507, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_LOCK", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 287312708, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_FOLD", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 287312709, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SELECT_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518784, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SELECT_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518784, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SELECT_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518784, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SELECT_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518784, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SELECT_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518784, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SELECT_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518784, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SELECT_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518784, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SELECT_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518784, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SELECT_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518784, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SET_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518785, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SET_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518785, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SET_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518785, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SET_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518785, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SET_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518785, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SET_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518785, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SET_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518785, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SET_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518785, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SET_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518785, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_BUCKLED_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 354421634, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_BUCKLED_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 354421634, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_BUCKLED_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 354421634, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_BUCKLED_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 354421634, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_BUCKLED_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 354421634, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_BUCKLED_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 354421634, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_BUCKLED_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 354421634, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_BUCKLED_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 354421634, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_BUCKLED_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 354421634, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_POS_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518787, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_POS_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518787, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_POS_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518787, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_POS_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518787, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_POS_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518787, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_POS_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518787, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_POS_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518787, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_POS_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518787, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_POS_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518787, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_MOVE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518788, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_MOVE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518788, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_MOVE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518788, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_MOVE_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518788, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_MOVE_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518788, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_MOVE_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518788, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_MOVE_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518788, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_MOVE_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518788, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_MOVE_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518788, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_POS_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518789, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_POS_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518789, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_POS_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518789, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_POS_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518789, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_POS_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518789, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_POS_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518789, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_POS_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518789, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_POS_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518789, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_POS_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518789, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_MOVE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518790, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_MOVE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518790, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_MOVE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518790, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_MOVE_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518790, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_MOVE_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518790, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_MOVE_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518790, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_MOVE_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518790, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_MOVE_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518790, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_MOVE_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518790, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_POS_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518791, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_POS_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518791, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_POS_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518791, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_POS_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518791, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_POS_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518791, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_POS_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518791, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_POS_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518791, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_POS_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518791, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_POS_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518791, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_MOVE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518792, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_MOVE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518792, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_MOVE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518792, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_MOVE_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518792, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_MOVE_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518792, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_MOVE_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518792, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_MOVE_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518792, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_MOVE_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518792, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_MOVE_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518792, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_POS_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518793, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_POS_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518793, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_POS_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518793, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_POS_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518793, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_POS_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518793, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_POS_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518793, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_POS_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518793, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_POS_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518793, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_POS_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518793, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_MOVE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518794, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_MOVE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518794, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_MOVE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518794, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_MOVE_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518794, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_MOVE_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518794, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_MOVE_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518794, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_MOVE_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518794, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_MOVE_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518794, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_MOVE_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518794, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_POS_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518795, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_POS_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518795, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_POS_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518795, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_POS_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518795, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_POS_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518795, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_POS_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518795, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_POS_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518795, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_POS_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518795, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_POS_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518795, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_MOVE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518796, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_MOVE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518796, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_MOVE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518796, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_MOVE_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518796, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_MOVE_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518796, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_MOVE_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518796, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_MOVE_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518796, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_MOVE_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518796, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_MOVE_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518796, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_POS_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518797, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_POS_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518797, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_POS_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518797, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_POS_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518797, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_POS_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518797, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_POS_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518797, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_POS_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518797, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_POS_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518797, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_POS_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518797, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_MOVE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518798, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_MOVE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518798, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_MOVE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518798, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_MOVE_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518798, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_MOVE_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518798, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_MOVE_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518798, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_MOVE_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518798, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_MOVE_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518798, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_MOVE_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518798, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_POS_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518799, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_POS_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518799, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_POS_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518799, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_POS_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518799, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_POS_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518799, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_POS_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518799, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_POS_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518799, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_POS_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518799, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_POS_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518799, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_MOVE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518800, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_MOVE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518800, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_MOVE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518800, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_MOVE_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518800, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_MOVE_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518800, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_MOVE_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518800, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_MOVE_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518800, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_MOVE_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518800, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_MOVE_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518800, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_POS_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518801, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_POS_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518801, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_POS_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518801, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_POS_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518801, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_POS_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518801, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_POS_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518801, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_POS_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518801, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_POS_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518801, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_POS_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518801, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_MOVE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518802, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_MOVE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518802, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_MOVE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518802, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_MOVE_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518802, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_MOVE_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518802, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_MOVE_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518802, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_MOVE_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518802, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_MOVE_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518802, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_MOVE_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518802, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_POS_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518803, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_POS_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518803, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_POS_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518803, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_POS_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518803, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_POS_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518803, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_POS_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518803, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_POS_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518803, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_POS_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518803, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_POS_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518803, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_MOVE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518804, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_MOVE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518804, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_MOVE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518804, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_MOVE_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518804, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_MOVE_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518804, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_MOVE_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518804, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_MOVE_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518804, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_MOVE_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518804, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_MOVE_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518804, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_POS", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289409941, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_MOVE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518806, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_MOVE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518806, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_MOVE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518806, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_MOVE_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518806, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_MOVE_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518806, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_MOVE_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518806, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_MOVE_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518806, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_MOVE_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518806, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_MOVE_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518806, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_POS_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518807, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_POS_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518807, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_POS_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518807, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_POS_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518807, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_POS_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518807, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_POS_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518807, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_POS_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518807, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_POS_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518807, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_POS_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518807, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_MOVE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518808, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_MOVE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518808, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_MOVE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518808, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_MOVE_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518808, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_MOVE_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518808, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_MOVE_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518808, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_MOVE_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518808, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_MOVE_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518808, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_MOVE_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518808, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_POS_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518809, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_POS_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518809, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_POS_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518809, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_POS_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518809, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_POS_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518809, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_POS_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518809, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_POS_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518809, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_POS_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518809, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_POS_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518809, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_MOVE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518810, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_MOVE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518810, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_MOVE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518810, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_MOVE_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518810, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_MOVE_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518810, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_MOVE_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518810, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_MOVE_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518810, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_MOVE_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518810, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_MOVE_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518810, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_OCCUPANCY_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356518832, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_OCCUPANCY_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356518832, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_OCCUPANCY_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356518832, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_OCCUPANCY_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356518832, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_OCCUPANCY_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356518832, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_OCCUPANCY_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356518832, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_OCCUPANCY_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356518832, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_OCCUPANCY_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356518832, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.SEAT_OCCUPANCY_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356518832, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 322964416, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 322964416, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 322964416, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 322964416, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 322964416, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 322964416, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 322964416, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 322964416, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 322964416, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_9", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 9, + "offset": 322964416, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 322964417, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 322964417, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 322964417, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 322964417, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 322964417, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 322964417, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 322964417, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 322964417, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 322964417, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_9", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 9, + "offset": 322964417, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 320867268, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 320867268, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 320867268, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 320867268, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 320867268, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 320867268, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 320867268, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 320867268, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 320867268, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_9", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 9, + "offset": 320867268, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HEADLIGHTS_STATE", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289410560, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HIGH_BEAM_LIGHTS_STATE", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289410561, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.FOG_LIGHTS_STATE", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289410562, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HAZARD_LIGHTS_STATE", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289410563, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HEADLIGHTS_SWITCH", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289410576, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HIGH_BEAM_LIGHTS_SWITCH", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289410577, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.FOG_LIGHTS_SWITCH", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289410578, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.HAZARD_LIGHTS_SWITCH", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289410579, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.CABIN_LIGHTS_STATE", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289410817, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.CABIN_LIGHTS_SWITCH", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 289410818, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_STATE_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356519683, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_STATE_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356519683, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_STATE_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356519683, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_STATE_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356519683, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_STATE_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356519683, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_STATE_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356519683, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_STATE_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356519683, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_STATE_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356519683, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_STATE_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356519683, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_SWITCH_0", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": 356519684, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_SWITCH_1", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 1, + "offset": 356519684, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_SWITCH_2", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 2, + "offset": 356519684, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_SWITCH_3", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 3, + "offset": 356519684, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_SWITCH_4", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 4, + "offset": 356519684, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_SWITCH_5", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 5, + "offset": 356519684, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_SWITCH_6", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 6, + "offset": 356519684, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_SWITCH_7", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 7, + "offset": 356519684, + "factor": 1, + "length": 0 + } + }, + { + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_SWITCH_8", + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 8, + "offset": 356519684, + "factor": 1, + "length": 0 + } + } +] diff --git a/tools/android-app/cloud/aaosVhalNodes.json b/tools/android-app/cloud/aaosVhalNodes.json new file mode 100644 index 00000000..d5410224 --- /dev/null +++ b/tools/android-app/cloud/aaosVhalNodes.json @@ -0,0 +1,4264 @@ +[ + { + "branch": { + "fullyQualifiedName": "Vehicle.VHAL", + "description": "Android Automotive OS VHAL" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_MODEL_YEAR", + "description": "INFO_MODEL_YEAR" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_FUEL_CAPACITY", + "description": "INFO_FUEL_CAPACITY" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_FUEL_TYPE_0", + "description": "INFO_FUEL_TYPE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_FUEL_TYPE_1", + "description": "INFO_FUEL_TYPE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_FUEL_TYPE_2", + "description": "INFO_FUEL_TYPE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_FUEL_TYPE_3", + "description": "INFO_FUEL_TYPE_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_EV_BATTERY_CAPACITY", + "description": "INFO_EV_BATTERY_CAPACITY" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_EV_CONNECTOR_TYPE_0", + "description": "INFO_EV_CONNECTOR_TYPE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_EV_CONNECTOR_TYPE_1", + "description": "INFO_EV_CONNECTOR_TYPE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_EV_CONNECTOR_TYPE_2", + "description": "INFO_EV_CONNECTOR_TYPE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_EV_CONNECTOR_TYPE_3", + "description": "INFO_EV_CONNECTOR_TYPE_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_FUEL_DOOR_LOCATION", + "description": "INFO_FUEL_DOOR_LOCATION" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_EV_PORT_LOCATION", + "description": "INFO_EV_PORT_LOCATION" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_DRIVER_SEAT", + "description": "INFO_DRIVER_SEAT" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_EXTERIOR_DIMENSIONS_0", + "description": "INFO_EXTERIOR_DIMENSIONS_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_EXTERIOR_DIMENSIONS_1", + "description": "INFO_EXTERIOR_DIMENSIONS_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_EXTERIOR_DIMENSIONS_2", + "description": "INFO_EXTERIOR_DIMENSIONS_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_EXTERIOR_DIMENSIONS_3", + "description": "INFO_EXTERIOR_DIMENSIONS_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_EXTERIOR_DIMENSIONS_4", + "description": "INFO_EXTERIOR_DIMENSIONS_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_EXTERIOR_DIMENSIONS_5", + "description": "INFO_EXTERIOR_DIMENSIONS_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_EXTERIOR_DIMENSIONS_6", + "description": "INFO_EXTERIOR_DIMENSIONS_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_EXTERIOR_DIMENSIONS_7", + "description": "INFO_EXTERIOR_DIMENSIONS_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_MULTI_EV_PORT_LOCATIONS_0", + "description": "INFO_MULTI_EV_PORT_LOCATIONS_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.INFO_MULTI_EV_PORT_LOCATIONS_1", + "description": "INFO_MULTI_EV_PORT_LOCATIONS_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.PERF_ODOMETER", + "description": "PERF_ODOMETER" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.PERF_VEHICLE_SPEED", + "description": "PERF_VEHICLE_SPEED" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.PERF_VEHICLE_SPEED_DISPLAY", + "description": "PERF_VEHICLE_SPEED_DISPLAY" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.PERF_STEERING_ANGLE", + "description": "PERF_STEERING_ANGLE" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.PERF_REAR_STEERING_ANGLE", + "description": "PERF_REAR_STEERING_ANGLE" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.ENGINE_COOLANT_TEMP", + "description": "ENGINE_COOLANT_TEMP" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.ENGINE_OIL_LEVEL", + "description": "ENGINE_OIL_LEVEL" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.ENGINE_OIL_TEMP", + "description": "ENGINE_OIL_TEMP" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.ENGINE_RPM", + "description": "ENGINE_RPM" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WHEEL_TICK_0", + "description": "WHEEL_TICK_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WHEEL_TICK_1", + "description": "WHEEL_TICK_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WHEEL_TICK_2", + "description": "WHEEL_TICK_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WHEEL_TICK_3", + "description": "WHEEL_TICK_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WHEEL_TICK_4", + "description": "WHEEL_TICK_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.FUEL_LEVEL", + "description": "FUEL_LEVEL" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.FUEL_DOOR_OPEN", + "description": "FUEL_DOOR_OPEN" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.EV_BATTERY_LEVEL", + "description": "EV_BATTERY_LEVEL" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.EV_CHARGE_PORT_OPEN", + "description": "EV_CHARGE_PORT_OPEN" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.EV_CHARGE_PORT_CONNECTED", + "description": "EV_CHARGE_PORT_CONNECTED" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.EV_BATTERY_INSTANTANEOUS_CHARGE_RATE", + "description": "EV_BATTERY_INSTANTANEOUS_CHARGE_RATE" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.RANGE_REMAINING", + "description": "RANGE_REMAINING" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.TIRE_PRESSURE_0", + "description": "TIRE_PRESSURE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.TIRE_PRESSURE_1", + "description": "TIRE_PRESSURE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.TIRE_PRESSURE_2", + "description": "TIRE_PRESSURE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.TIRE_PRESSURE_3", + "description": "TIRE_PRESSURE_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.GEAR_SELECTION", + "description": "GEAR_SELECTION" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.CURRENT_GEAR", + "description": "CURRENT_GEAR" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.PARKING_BRAKE_ON", + "description": "PARKING_BRAKE_ON" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.PARKING_BRAKE_AUTO_APPLY", + "description": "PARKING_BRAKE_AUTO_APPLY" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.FUEL_LEVEL_LOW", + "description": "FUEL_LEVEL_LOW" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.NIGHT_MODE", + "description": "NIGHT_MODE" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.TURN_SIGNAL_STATE", + "description": "TURN_SIGNAL_STATE" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.IGNITION_STATE", + "description": "IGNITION_STATE" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.ABS_ACTIVE", + "description": "ABS_ACTIVE" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.TRACTION_CONTROL_ACTIVE", + "description": "TRACTION_CONTROL_ACTIVE" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_SPEED_0", + "description": "HVAC_FAN_SPEED_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_SPEED_1", + "description": "HVAC_FAN_SPEED_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_SPEED_2", + "description": "HVAC_FAN_SPEED_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_SPEED_3", + "description": "HVAC_FAN_SPEED_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_SPEED_4", + "description": "HVAC_FAN_SPEED_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_SPEED_5", + "description": "HVAC_FAN_SPEED_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_SPEED_6", + "description": "HVAC_FAN_SPEED_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_SPEED_7", + "description": "HVAC_FAN_SPEED_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_SPEED_8", + "description": "HVAC_FAN_SPEED_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_0", + "description": "HVAC_FAN_DIRECTION_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_1", + "description": "HVAC_FAN_DIRECTION_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_2", + "description": "HVAC_FAN_DIRECTION_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_3", + "description": "HVAC_FAN_DIRECTION_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_4", + "description": "HVAC_FAN_DIRECTION_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_5", + "description": "HVAC_FAN_DIRECTION_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_6", + "description": "HVAC_FAN_DIRECTION_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_7", + "description": "HVAC_FAN_DIRECTION_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_8", + "description": "HVAC_FAN_DIRECTION_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_CURRENT_0", + "description": "HVAC_TEMPERATURE_CURRENT_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_CURRENT_1", + "description": "HVAC_TEMPERATURE_CURRENT_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_CURRENT_2", + "description": "HVAC_TEMPERATURE_CURRENT_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_CURRENT_3", + "description": "HVAC_TEMPERATURE_CURRENT_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_CURRENT_4", + "description": "HVAC_TEMPERATURE_CURRENT_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_CURRENT_5", + "description": "HVAC_TEMPERATURE_CURRENT_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_CURRENT_6", + "description": "HVAC_TEMPERATURE_CURRENT_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_CURRENT_7", + "description": "HVAC_TEMPERATURE_CURRENT_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_CURRENT_8", + "description": "HVAC_TEMPERATURE_CURRENT_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_0", + "description": "HVAC_TEMPERATURE_SET_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_1", + "description": "HVAC_TEMPERATURE_SET_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_2", + "description": "HVAC_TEMPERATURE_SET_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_3", + "description": "HVAC_TEMPERATURE_SET_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_4", + "description": "HVAC_TEMPERATURE_SET_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_5", + "description": "HVAC_TEMPERATURE_SET_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_6", + "description": "HVAC_TEMPERATURE_SET_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_7", + "description": "HVAC_TEMPERATURE_SET_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_8", + "description": "HVAC_TEMPERATURE_SET_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_0", + "description": "HVAC_DEFROSTER_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_1", + "description": "HVAC_DEFROSTER_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_2", + "description": "HVAC_DEFROSTER_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_3", + "description": "HVAC_DEFROSTER_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_4", + "description": "HVAC_DEFROSTER_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_5", + "description": "HVAC_DEFROSTER_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_6", + "description": "HVAC_DEFROSTER_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_7", + "description": "HVAC_DEFROSTER_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_8", + "description": "HVAC_DEFROSTER_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DEFROSTER_9", + "description": "HVAC_DEFROSTER_9" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AC_ON_0", + "description": "HVAC_AC_ON_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AC_ON_1", + "description": "HVAC_AC_ON_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AC_ON_2", + "description": "HVAC_AC_ON_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AC_ON_3", + "description": "HVAC_AC_ON_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AC_ON_4", + "description": "HVAC_AC_ON_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AC_ON_5", + "description": "HVAC_AC_ON_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AC_ON_6", + "description": "HVAC_AC_ON_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AC_ON_7", + "description": "HVAC_AC_ON_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AC_ON_8", + "description": "HVAC_AC_ON_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_AC_ON_0", + "description": "HVAC_MAX_AC_ON_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_AC_ON_1", + "description": "HVAC_MAX_AC_ON_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_AC_ON_2", + "description": "HVAC_MAX_AC_ON_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_AC_ON_3", + "description": "HVAC_MAX_AC_ON_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_AC_ON_4", + "description": "HVAC_MAX_AC_ON_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_AC_ON_5", + "description": "HVAC_MAX_AC_ON_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_AC_ON_6", + "description": "HVAC_MAX_AC_ON_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_AC_ON_7", + "description": "HVAC_MAX_AC_ON_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_AC_ON_8", + "description": "HVAC_MAX_AC_ON_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_DEFROST_ON_0", + "description": "HVAC_MAX_DEFROST_ON_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_DEFROST_ON_1", + "description": "HVAC_MAX_DEFROST_ON_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_DEFROST_ON_2", + "description": "HVAC_MAX_DEFROST_ON_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_DEFROST_ON_3", + "description": "HVAC_MAX_DEFROST_ON_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_DEFROST_ON_4", + "description": "HVAC_MAX_DEFROST_ON_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_DEFROST_ON_5", + "description": "HVAC_MAX_DEFROST_ON_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_DEFROST_ON_6", + "description": "HVAC_MAX_DEFROST_ON_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_DEFROST_ON_7", + "description": "HVAC_MAX_DEFROST_ON_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_MAX_DEFROST_ON_8", + "description": "HVAC_MAX_DEFROST_ON_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_RECIRC_ON_0", + "description": "HVAC_RECIRC_ON_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_RECIRC_ON_1", + "description": "HVAC_RECIRC_ON_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_RECIRC_ON_2", + "description": "HVAC_RECIRC_ON_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_RECIRC_ON_3", + "description": "HVAC_RECIRC_ON_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_RECIRC_ON_4", + "description": "HVAC_RECIRC_ON_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_RECIRC_ON_5", + "description": "HVAC_RECIRC_ON_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_RECIRC_ON_6", + "description": "HVAC_RECIRC_ON_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_RECIRC_ON_7", + "description": "HVAC_RECIRC_ON_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_RECIRC_ON_8", + "description": "HVAC_RECIRC_ON_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DUAL_ON_0", + "description": "HVAC_DUAL_ON_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DUAL_ON_1", + "description": "HVAC_DUAL_ON_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DUAL_ON_2", + "description": "HVAC_DUAL_ON_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DUAL_ON_3", + "description": "HVAC_DUAL_ON_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DUAL_ON_4", + "description": "HVAC_DUAL_ON_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DUAL_ON_5", + "description": "HVAC_DUAL_ON_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DUAL_ON_6", + "description": "HVAC_DUAL_ON_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DUAL_ON_7", + "description": "HVAC_DUAL_ON_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_DUAL_ON_8", + "description": "HVAC_DUAL_ON_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_ON_0", + "description": "HVAC_AUTO_ON_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_ON_1", + "description": "HVAC_AUTO_ON_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_ON_2", + "description": "HVAC_AUTO_ON_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_ON_3", + "description": "HVAC_AUTO_ON_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_ON_4", + "description": "HVAC_AUTO_ON_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_ON_5", + "description": "HVAC_AUTO_ON_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_ON_6", + "description": "HVAC_AUTO_ON_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_ON_7", + "description": "HVAC_AUTO_ON_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_ON_8", + "description": "HVAC_AUTO_ON_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_0", + "description": "HVAC_SEAT_TEMPERATURE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_1", + "description": "HVAC_SEAT_TEMPERATURE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_2", + "description": "HVAC_SEAT_TEMPERATURE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_3", + "description": "HVAC_SEAT_TEMPERATURE_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_4", + "description": "HVAC_SEAT_TEMPERATURE_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_5", + "description": "HVAC_SEAT_TEMPERATURE_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_6", + "description": "HVAC_SEAT_TEMPERATURE_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_7", + "description": "HVAC_SEAT_TEMPERATURE_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_8", + "description": "HVAC_SEAT_TEMPERATURE_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SIDE_MIRROR_HEAT_0", + "description": "HVAC_SIDE_MIRROR_HEAT_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SIDE_MIRROR_HEAT_1", + "description": "HVAC_SIDE_MIRROR_HEAT_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SIDE_MIRROR_HEAT_2", + "description": "HVAC_SIDE_MIRROR_HEAT_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_STEERING_WHEEL_HEAT", + "description": "HVAC_STEERING_WHEEL_HEAT" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_TEMPERATURE_DISPLAY_UNITS", + "description": "HVAC_TEMPERATURE_DISPLAY_UNITS" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ACTUAL_FAN_SPEED_RPM_0", + "description": "HVAC_ACTUAL_FAN_SPEED_RPM_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ACTUAL_FAN_SPEED_RPM_1", + "description": "HVAC_ACTUAL_FAN_SPEED_RPM_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ACTUAL_FAN_SPEED_RPM_2", + "description": "HVAC_ACTUAL_FAN_SPEED_RPM_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ACTUAL_FAN_SPEED_RPM_3", + "description": "HVAC_ACTUAL_FAN_SPEED_RPM_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ACTUAL_FAN_SPEED_RPM_4", + "description": "HVAC_ACTUAL_FAN_SPEED_RPM_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ACTUAL_FAN_SPEED_RPM_5", + "description": "HVAC_ACTUAL_FAN_SPEED_RPM_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ACTUAL_FAN_SPEED_RPM_6", + "description": "HVAC_ACTUAL_FAN_SPEED_RPM_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ACTUAL_FAN_SPEED_RPM_7", + "description": "HVAC_ACTUAL_FAN_SPEED_RPM_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ACTUAL_FAN_SPEED_RPM_8", + "description": "HVAC_ACTUAL_FAN_SPEED_RPM_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_POWER_ON_0", + "description": "HVAC_POWER_ON_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_POWER_ON_1", + "description": "HVAC_POWER_ON_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_POWER_ON_2", + "description": "HVAC_POWER_ON_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_POWER_ON_3", + "description": "HVAC_POWER_ON_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_POWER_ON_4", + "description": "HVAC_POWER_ON_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_POWER_ON_5", + "description": "HVAC_POWER_ON_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_POWER_ON_6", + "description": "HVAC_POWER_ON_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_POWER_ON_7", + "description": "HVAC_POWER_ON_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_POWER_ON_8", + "description": "HVAC_POWER_ON_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_0_0", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_0_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_0_1", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_0_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_0_2", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_0_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_0_3", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_0_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_1_0", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_1_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_1_1", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_1_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_1_2", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_1_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_1_3", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_1_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_2_0", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_2_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_2_1", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_2_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_2_2", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_2_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_2_3", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_2_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_3_0", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_3_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_3_1", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_3_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_3_2", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_3_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_3_3", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_3_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_4_0", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_4_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_4_1", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_4_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_4_2", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_4_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_4_3", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_4_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_5_0", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_5_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_5_1", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_5_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_5_2", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_5_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_5_3", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_5_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_6_0", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_6_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_6_1", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_6_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_6_2", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_6_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_6_3", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_6_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_7_0", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_7_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_7_1", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_7_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_7_2", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_7_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_7_3", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_7_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_8_0", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_8_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_8_1", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_8_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_8_2", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_8_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_FAN_DIRECTION_AVAILABLE_8_3", + "description": "HVAC_FAN_DIRECTION_AVAILABLE_8_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_RECIRC_ON_0", + "description": "HVAC_AUTO_RECIRC_ON_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_RECIRC_ON_1", + "description": "HVAC_AUTO_RECIRC_ON_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_RECIRC_ON_2", + "description": "HVAC_AUTO_RECIRC_ON_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_RECIRC_ON_3", + "description": "HVAC_AUTO_RECIRC_ON_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_RECIRC_ON_4", + "description": "HVAC_AUTO_RECIRC_ON_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_RECIRC_ON_5", + "description": "HVAC_AUTO_RECIRC_ON_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_RECIRC_ON_6", + "description": "HVAC_AUTO_RECIRC_ON_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_RECIRC_ON_7", + "description": "HVAC_AUTO_RECIRC_ON_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_AUTO_RECIRC_ON_8", + "description": "HVAC_AUTO_RECIRC_ON_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_VENTILATION_0", + "description": "HVAC_SEAT_VENTILATION_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_VENTILATION_1", + "description": "HVAC_SEAT_VENTILATION_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_VENTILATION_2", + "description": "HVAC_SEAT_VENTILATION_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_VENTILATION_3", + "description": "HVAC_SEAT_VENTILATION_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_VENTILATION_4", + "description": "HVAC_SEAT_VENTILATION_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_VENTILATION_5", + "description": "HVAC_SEAT_VENTILATION_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_VENTILATION_6", + "description": "HVAC_SEAT_VENTILATION_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_VENTILATION_7", + "description": "HVAC_SEAT_VENTILATION_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_SEAT_VENTILATION_8", + "description": "HVAC_SEAT_VENTILATION_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_0", + "description": "HVAC_ELECTRIC_DEFROSTER_ON_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_1", + "description": "HVAC_ELECTRIC_DEFROSTER_ON_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_2", + "description": "HVAC_ELECTRIC_DEFROSTER_ON_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_3", + "description": "HVAC_ELECTRIC_DEFROSTER_ON_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_4", + "description": "HVAC_ELECTRIC_DEFROSTER_ON_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_5", + "description": "HVAC_ELECTRIC_DEFROSTER_ON_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_6", + "description": "HVAC_ELECTRIC_DEFROSTER_ON_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_7", + "description": "HVAC_ELECTRIC_DEFROSTER_ON_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_8", + "description": "HVAC_ELECTRIC_DEFROSTER_ON_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HVAC_ELECTRIC_DEFROSTER_ON_9", + "description": "HVAC_ELECTRIC_DEFROSTER_ON_9" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DISTANCE_DISPLAY_UNITS", + "description": "DISTANCE_DISPLAY_UNITS" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.FUEL_VOLUME_DISPLAY_UNITS", + "description": "FUEL_VOLUME_DISPLAY_UNITS" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.TIRE_PRESSURE_DISPLAY_UNITS", + "description": "TIRE_PRESSURE_DISPLAY_UNITS" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.EV_BATTERY_DISPLAY_UNITS", + "description": "EV_BATTERY_DISPLAY_UNITS" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME", + "description": "FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.VEHICLE_SPEED_DISPLAY_UNITS", + "description": "VEHICLE_SPEED_DISPLAY_UNITS" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.ENV_OUTSIDE_TEMPERATURE", + "description": "ENV_OUTSIDE_TEMPERATURE" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.AP_POWER_STATE_REQ_0", + "description": "AP_POWER_STATE_REQ_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.AP_POWER_STATE_REQ_1", + "description": "AP_POWER_STATE_REQ_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.AP_POWER_STATE_REPORT_0", + "description": "AP_POWER_STATE_REPORT_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.AP_POWER_STATE_REPORT_1", + "description": "AP_POWER_STATE_REPORT_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.AP_POWER_BOOTUP_REASON", + "description": "AP_POWER_BOOTUP_REASON" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DISPLAY_BRIGHTNESS", + "description": "DISPLAY_BRIGHTNESS" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_POS_0", + "description": "DOOR_POS_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_POS_1", + "description": "DOOR_POS_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_POS_2", + "description": "DOOR_POS_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_POS_3", + "description": "DOOR_POS_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_POS_4", + "description": "DOOR_POS_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_POS_5", + "description": "DOOR_POS_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_POS_6", + "description": "DOOR_POS_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_POS_7", + "description": "DOOR_POS_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_MOVE_0", + "description": "DOOR_MOVE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_MOVE_1", + "description": "DOOR_MOVE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_MOVE_2", + "description": "DOOR_MOVE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_MOVE_3", + "description": "DOOR_MOVE_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_MOVE_4", + "description": "DOOR_MOVE_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_MOVE_5", + "description": "DOOR_MOVE_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_MOVE_6", + "description": "DOOR_MOVE_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_MOVE_7", + "description": "DOOR_MOVE_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_LOCK_0", + "description": "DOOR_LOCK_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_LOCK_1", + "description": "DOOR_LOCK_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_LOCK_2", + "description": "DOOR_LOCK_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_LOCK_3", + "description": "DOOR_LOCK_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_LOCK_4", + "description": "DOOR_LOCK_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_LOCK_5", + "description": "DOOR_LOCK_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_LOCK_6", + "description": "DOOR_LOCK_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.DOOR_LOCK_7", + "description": "DOOR_LOCK_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Z_POS_0", + "description": "MIRROR_Z_POS_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Z_POS_1", + "description": "MIRROR_Z_POS_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Z_POS_2", + "description": "MIRROR_Z_POS_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Z_MOVE_0", + "description": "MIRROR_Z_MOVE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Z_MOVE_1", + "description": "MIRROR_Z_MOVE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Z_MOVE_2", + "description": "MIRROR_Z_MOVE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Y_POS_0", + "description": "MIRROR_Y_POS_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Y_POS_1", + "description": "MIRROR_Y_POS_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Y_POS_2", + "description": "MIRROR_Y_POS_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Y_MOVE_0", + "description": "MIRROR_Y_MOVE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Y_MOVE_1", + "description": "MIRROR_Y_MOVE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_Y_MOVE_2", + "description": "MIRROR_Y_MOVE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_LOCK", + "description": "MIRROR_LOCK" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.MIRROR_FOLD", + "description": "MIRROR_FOLD" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SELECT_0", + "description": "SEAT_MEMORY_SELECT_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SELECT_1", + "description": "SEAT_MEMORY_SELECT_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SELECT_2", + "description": "SEAT_MEMORY_SELECT_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SELECT_3", + "description": "SEAT_MEMORY_SELECT_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SELECT_4", + "description": "SEAT_MEMORY_SELECT_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SELECT_5", + "description": "SEAT_MEMORY_SELECT_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SELECT_6", + "description": "SEAT_MEMORY_SELECT_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SELECT_7", + "description": "SEAT_MEMORY_SELECT_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SELECT_8", + "description": "SEAT_MEMORY_SELECT_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SET_0", + "description": "SEAT_MEMORY_SET_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SET_1", + "description": "SEAT_MEMORY_SET_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SET_2", + "description": "SEAT_MEMORY_SET_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SET_3", + "description": "SEAT_MEMORY_SET_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SET_4", + "description": "SEAT_MEMORY_SET_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SET_5", + "description": "SEAT_MEMORY_SET_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SET_6", + "description": "SEAT_MEMORY_SET_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SET_7", + "description": "SEAT_MEMORY_SET_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_MEMORY_SET_8", + "description": "SEAT_MEMORY_SET_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_BUCKLED_0", + "description": "SEAT_BELT_BUCKLED_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_BUCKLED_1", + "description": "SEAT_BELT_BUCKLED_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_BUCKLED_2", + "description": "SEAT_BELT_BUCKLED_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_BUCKLED_3", + "description": "SEAT_BELT_BUCKLED_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_BUCKLED_4", + "description": "SEAT_BELT_BUCKLED_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_BUCKLED_5", + "description": "SEAT_BELT_BUCKLED_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_BUCKLED_6", + "description": "SEAT_BELT_BUCKLED_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_BUCKLED_7", + "description": "SEAT_BELT_BUCKLED_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_BUCKLED_8", + "description": "SEAT_BELT_BUCKLED_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_POS_0", + "description": "SEAT_BELT_HEIGHT_POS_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_POS_1", + "description": "SEAT_BELT_HEIGHT_POS_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_POS_2", + "description": "SEAT_BELT_HEIGHT_POS_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_POS_3", + "description": "SEAT_BELT_HEIGHT_POS_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_POS_4", + "description": "SEAT_BELT_HEIGHT_POS_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_POS_5", + "description": "SEAT_BELT_HEIGHT_POS_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_POS_6", + "description": "SEAT_BELT_HEIGHT_POS_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_POS_7", + "description": "SEAT_BELT_HEIGHT_POS_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_POS_8", + "description": "SEAT_BELT_HEIGHT_POS_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_MOVE_0", + "description": "SEAT_BELT_HEIGHT_MOVE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_MOVE_1", + "description": "SEAT_BELT_HEIGHT_MOVE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_MOVE_2", + "description": "SEAT_BELT_HEIGHT_MOVE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_MOVE_3", + "description": "SEAT_BELT_HEIGHT_MOVE_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_MOVE_4", + "description": "SEAT_BELT_HEIGHT_MOVE_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_MOVE_5", + "description": "SEAT_BELT_HEIGHT_MOVE_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_MOVE_6", + "description": "SEAT_BELT_HEIGHT_MOVE_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_MOVE_7", + "description": "SEAT_BELT_HEIGHT_MOVE_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BELT_HEIGHT_MOVE_8", + "description": "SEAT_BELT_HEIGHT_MOVE_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_POS_0", + "description": "SEAT_FORE_AFT_POS_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_POS_1", + "description": "SEAT_FORE_AFT_POS_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_POS_2", + "description": "SEAT_FORE_AFT_POS_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_POS_3", + "description": "SEAT_FORE_AFT_POS_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_POS_4", + "description": "SEAT_FORE_AFT_POS_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_POS_5", + "description": "SEAT_FORE_AFT_POS_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_POS_6", + "description": "SEAT_FORE_AFT_POS_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_POS_7", + "description": "SEAT_FORE_AFT_POS_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_POS_8", + "description": "SEAT_FORE_AFT_POS_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_MOVE_0", + "description": "SEAT_FORE_AFT_MOVE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_MOVE_1", + "description": "SEAT_FORE_AFT_MOVE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_MOVE_2", + "description": "SEAT_FORE_AFT_MOVE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_MOVE_3", + "description": "SEAT_FORE_AFT_MOVE_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_MOVE_4", + "description": "SEAT_FORE_AFT_MOVE_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_MOVE_5", + "description": "SEAT_FORE_AFT_MOVE_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_MOVE_6", + "description": "SEAT_FORE_AFT_MOVE_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_MOVE_7", + "description": "SEAT_FORE_AFT_MOVE_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_FORE_AFT_MOVE_8", + "description": "SEAT_FORE_AFT_MOVE_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_POS_0", + "description": "SEAT_BACKREST_ANGLE_1_POS_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_POS_1", + "description": "SEAT_BACKREST_ANGLE_1_POS_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_POS_2", + "description": "SEAT_BACKREST_ANGLE_1_POS_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_POS_3", + "description": "SEAT_BACKREST_ANGLE_1_POS_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_POS_4", + "description": "SEAT_BACKREST_ANGLE_1_POS_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_POS_5", + "description": "SEAT_BACKREST_ANGLE_1_POS_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_POS_6", + "description": "SEAT_BACKREST_ANGLE_1_POS_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_POS_7", + "description": "SEAT_BACKREST_ANGLE_1_POS_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_POS_8", + "description": "SEAT_BACKREST_ANGLE_1_POS_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_MOVE_0", + "description": "SEAT_BACKREST_ANGLE_1_MOVE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_MOVE_1", + "description": "SEAT_BACKREST_ANGLE_1_MOVE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_MOVE_2", + "description": "SEAT_BACKREST_ANGLE_1_MOVE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_MOVE_3", + "description": "SEAT_BACKREST_ANGLE_1_MOVE_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_MOVE_4", + "description": "SEAT_BACKREST_ANGLE_1_MOVE_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_MOVE_5", + "description": "SEAT_BACKREST_ANGLE_1_MOVE_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_MOVE_6", + "description": "SEAT_BACKREST_ANGLE_1_MOVE_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_MOVE_7", + "description": "SEAT_BACKREST_ANGLE_1_MOVE_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_1_MOVE_8", + "description": "SEAT_BACKREST_ANGLE_1_MOVE_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_POS_0", + "description": "SEAT_BACKREST_ANGLE_2_POS_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_POS_1", + "description": "SEAT_BACKREST_ANGLE_2_POS_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_POS_2", + "description": "SEAT_BACKREST_ANGLE_2_POS_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_POS_3", + "description": "SEAT_BACKREST_ANGLE_2_POS_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_POS_4", + "description": "SEAT_BACKREST_ANGLE_2_POS_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_POS_5", + "description": "SEAT_BACKREST_ANGLE_2_POS_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_POS_6", + "description": "SEAT_BACKREST_ANGLE_2_POS_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_POS_7", + "description": "SEAT_BACKREST_ANGLE_2_POS_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_POS_8", + "description": "SEAT_BACKREST_ANGLE_2_POS_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_MOVE_0", + "description": "SEAT_BACKREST_ANGLE_2_MOVE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_MOVE_1", + "description": "SEAT_BACKREST_ANGLE_2_MOVE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_MOVE_2", + "description": "SEAT_BACKREST_ANGLE_2_MOVE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_MOVE_3", + "description": "SEAT_BACKREST_ANGLE_2_MOVE_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_MOVE_4", + "description": "SEAT_BACKREST_ANGLE_2_MOVE_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_MOVE_5", + "description": "SEAT_BACKREST_ANGLE_2_MOVE_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_MOVE_6", + "description": "SEAT_BACKREST_ANGLE_2_MOVE_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_MOVE_7", + "description": "SEAT_BACKREST_ANGLE_2_MOVE_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_BACKREST_ANGLE_2_MOVE_8", + "description": "SEAT_BACKREST_ANGLE_2_MOVE_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_POS_0", + "description": "SEAT_HEIGHT_POS_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_POS_1", + "description": "SEAT_HEIGHT_POS_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_POS_2", + "description": "SEAT_HEIGHT_POS_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_POS_3", + "description": "SEAT_HEIGHT_POS_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_POS_4", + "description": "SEAT_HEIGHT_POS_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_POS_5", + "description": "SEAT_HEIGHT_POS_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_POS_6", + "description": "SEAT_HEIGHT_POS_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_POS_7", + "description": "SEAT_HEIGHT_POS_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_POS_8", + "description": "SEAT_HEIGHT_POS_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_MOVE_0", + "description": "SEAT_HEIGHT_MOVE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_MOVE_1", + "description": "SEAT_HEIGHT_MOVE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_MOVE_2", + "description": "SEAT_HEIGHT_MOVE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_MOVE_3", + "description": "SEAT_HEIGHT_MOVE_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_MOVE_4", + "description": "SEAT_HEIGHT_MOVE_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_MOVE_5", + "description": "SEAT_HEIGHT_MOVE_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_MOVE_6", + "description": "SEAT_HEIGHT_MOVE_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_MOVE_7", + "description": "SEAT_HEIGHT_MOVE_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEIGHT_MOVE_8", + "description": "SEAT_HEIGHT_MOVE_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_POS_0", + "description": "SEAT_DEPTH_POS_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_POS_1", + "description": "SEAT_DEPTH_POS_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_POS_2", + "description": "SEAT_DEPTH_POS_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_POS_3", + "description": "SEAT_DEPTH_POS_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_POS_4", + "description": "SEAT_DEPTH_POS_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_POS_5", + "description": "SEAT_DEPTH_POS_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_POS_6", + "description": "SEAT_DEPTH_POS_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_POS_7", + "description": "SEAT_DEPTH_POS_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_POS_8", + "description": "SEAT_DEPTH_POS_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_MOVE_0", + "description": "SEAT_DEPTH_MOVE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_MOVE_1", + "description": "SEAT_DEPTH_MOVE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_MOVE_2", + "description": "SEAT_DEPTH_MOVE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_MOVE_3", + "description": "SEAT_DEPTH_MOVE_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_MOVE_4", + "description": "SEAT_DEPTH_MOVE_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_MOVE_5", + "description": "SEAT_DEPTH_MOVE_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_MOVE_6", + "description": "SEAT_DEPTH_MOVE_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_MOVE_7", + "description": "SEAT_DEPTH_MOVE_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_DEPTH_MOVE_8", + "description": "SEAT_DEPTH_MOVE_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_POS_0", + "description": "SEAT_TILT_POS_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_POS_1", + "description": "SEAT_TILT_POS_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_POS_2", + "description": "SEAT_TILT_POS_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_POS_3", + "description": "SEAT_TILT_POS_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_POS_4", + "description": "SEAT_TILT_POS_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_POS_5", + "description": "SEAT_TILT_POS_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_POS_6", + "description": "SEAT_TILT_POS_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_POS_7", + "description": "SEAT_TILT_POS_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_POS_8", + "description": "SEAT_TILT_POS_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_MOVE_0", + "description": "SEAT_TILT_MOVE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_MOVE_1", + "description": "SEAT_TILT_MOVE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_MOVE_2", + "description": "SEAT_TILT_MOVE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_MOVE_3", + "description": "SEAT_TILT_MOVE_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_MOVE_4", + "description": "SEAT_TILT_MOVE_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_MOVE_5", + "description": "SEAT_TILT_MOVE_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_MOVE_6", + "description": "SEAT_TILT_MOVE_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_MOVE_7", + "description": "SEAT_TILT_MOVE_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_TILT_MOVE_8", + "description": "SEAT_TILT_MOVE_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_POS_0", + "description": "SEAT_LUMBAR_FORE_AFT_POS_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_POS_1", + "description": "SEAT_LUMBAR_FORE_AFT_POS_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_POS_2", + "description": "SEAT_LUMBAR_FORE_AFT_POS_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_POS_3", + "description": "SEAT_LUMBAR_FORE_AFT_POS_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_POS_4", + "description": "SEAT_LUMBAR_FORE_AFT_POS_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_POS_5", + "description": "SEAT_LUMBAR_FORE_AFT_POS_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_POS_6", + "description": "SEAT_LUMBAR_FORE_AFT_POS_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_POS_7", + "description": "SEAT_LUMBAR_FORE_AFT_POS_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_POS_8", + "description": "SEAT_LUMBAR_FORE_AFT_POS_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_MOVE_0", + "description": "SEAT_LUMBAR_FORE_AFT_MOVE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_MOVE_1", + "description": "SEAT_LUMBAR_FORE_AFT_MOVE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_MOVE_2", + "description": "SEAT_LUMBAR_FORE_AFT_MOVE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_MOVE_3", + "description": "SEAT_LUMBAR_FORE_AFT_MOVE_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_MOVE_4", + "description": "SEAT_LUMBAR_FORE_AFT_MOVE_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_MOVE_5", + "description": "SEAT_LUMBAR_FORE_AFT_MOVE_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_MOVE_6", + "description": "SEAT_LUMBAR_FORE_AFT_MOVE_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_MOVE_7", + "description": "SEAT_LUMBAR_FORE_AFT_MOVE_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_FORE_AFT_MOVE_8", + "description": "SEAT_LUMBAR_FORE_AFT_MOVE_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_POS_0", + "description": "SEAT_LUMBAR_SIDE_SUPPORT_POS_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_POS_1", + "description": "SEAT_LUMBAR_SIDE_SUPPORT_POS_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_POS_2", + "description": "SEAT_LUMBAR_SIDE_SUPPORT_POS_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_POS_3", + "description": "SEAT_LUMBAR_SIDE_SUPPORT_POS_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_POS_4", + "description": "SEAT_LUMBAR_SIDE_SUPPORT_POS_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_POS_5", + "description": "SEAT_LUMBAR_SIDE_SUPPORT_POS_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_POS_6", + "description": "SEAT_LUMBAR_SIDE_SUPPORT_POS_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_POS_7", + "description": "SEAT_LUMBAR_SIDE_SUPPORT_POS_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_POS_8", + "description": "SEAT_LUMBAR_SIDE_SUPPORT_POS_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_MOVE_0", + "description": "SEAT_LUMBAR_SIDE_SUPPORT_MOVE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_MOVE_1", + "description": "SEAT_LUMBAR_SIDE_SUPPORT_MOVE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_MOVE_2", + "description": "SEAT_LUMBAR_SIDE_SUPPORT_MOVE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_MOVE_3", + "description": "SEAT_LUMBAR_SIDE_SUPPORT_MOVE_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_MOVE_4", + "description": "SEAT_LUMBAR_SIDE_SUPPORT_MOVE_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_MOVE_5", + "description": "SEAT_LUMBAR_SIDE_SUPPORT_MOVE_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_MOVE_6", + "description": "SEAT_LUMBAR_SIDE_SUPPORT_MOVE_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_MOVE_7", + "description": "SEAT_LUMBAR_SIDE_SUPPORT_MOVE_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_LUMBAR_SIDE_SUPPORT_MOVE_8", + "description": "SEAT_LUMBAR_SIDE_SUPPORT_MOVE_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_POS", + "description": "SEAT_HEADREST_HEIGHT_POS" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_MOVE_0", + "description": "SEAT_HEADREST_HEIGHT_MOVE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_MOVE_1", + "description": "SEAT_HEADREST_HEIGHT_MOVE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_MOVE_2", + "description": "SEAT_HEADREST_HEIGHT_MOVE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_MOVE_3", + "description": "SEAT_HEADREST_HEIGHT_MOVE_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_MOVE_4", + "description": "SEAT_HEADREST_HEIGHT_MOVE_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_MOVE_5", + "description": "SEAT_HEADREST_HEIGHT_MOVE_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_MOVE_6", + "description": "SEAT_HEADREST_HEIGHT_MOVE_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_MOVE_7", + "description": "SEAT_HEADREST_HEIGHT_MOVE_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_HEIGHT_MOVE_8", + "description": "SEAT_HEADREST_HEIGHT_MOVE_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_POS_0", + "description": "SEAT_HEADREST_ANGLE_POS_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_POS_1", + "description": "SEAT_HEADREST_ANGLE_POS_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_POS_2", + "description": "SEAT_HEADREST_ANGLE_POS_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_POS_3", + "description": "SEAT_HEADREST_ANGLE_POS_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_POS_4", + "description": "SEAT_HEADREST_ANGLE_POS_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_POS_5", + "description": "SEAT_HEADREST_ANGLE_POS_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_POS_6", + "description": "SEAT_HEADREST_ANGLE_POS_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_POS_7", + "description": "SEAT_HEADREST_ANGLE_POS_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_POS_8", + "description": "SEAT_HEADREST_ANGLE_POS_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_MOVE_0", + "description": "SEAT_HEADREST_ANGLE_MOVE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_MOVE_1", + "description": "SEAT_HEADREST_ANGLE_MOVE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_MOVE_2", + "description": "SEAT_HEADREST_ANGLE_MOVE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_MOVE_3", + "description": "SEAT_HEADREST_ANGLE_MOVE_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_MOVE_4", + "description": "SEAT_HEADREST_ANGLE_MOVE_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_MOVE_5", + "description": "SEAT_HEADREST_ANGLE_MOVE_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_MOVE_6", + "description": "SEAT_HEADREST_ANGLE_MOVE_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_MOVE_7", + "description": "SEAT_HEADREST_ANGLE_MOVE_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_ANGLE_MOVE_8", + "description": "SEAT_HEADREST_ANGLE_MOVE_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_POS_0", + "description": "SEAT_HEADREST_FORE_AFT_POS_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_POS_1", + "description": "SEAT_HEADREST_FORE_AFT_POS_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_POS_2", + "description": "SEAT_HEADREST_FORE_AFT_POS_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_POS_3", + "description": "SEAT_HEADREST_FORE_AFT_POS_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_POS_4", + "description": "SEAT_HEADREST_FORE_AFT_POS_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_POS_5", + "description": "SEAT_HEADREST_FORE_AFT_POS_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_POS_6", + "description": "SEAT_HEADREST_FORE_AFT_POS_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_POS_7", + "description": "SEAT_HEADREST_FORE_AFT_POS_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_POS_8", + "description": "SEAT_HEADREST_FORE_AFT_POS_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_MOVE_0", + "description": "SEAT_HEADREST_FORE_AFT_MOVE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_MOVE_1", + "description": "SEAT_HEADREST_FORE_AFT_MOVE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_MOVE_2", + "description": "SEAT_HEADREST_FORE_AFT_MOVE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_MOVE_3", + "description": "SEAT_HEADREST_FORE_AFT_MOVE_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_MOVE_4", + "description": "SEAT_HEADREST_FORE_AFT_MOVE_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_MOVE_5", + "description": "SEAT_HEADREST_FORE_AFT_MOVE_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_MOVE_6", + "description": "SEAT_HEADREST_FORE_AFT_MOVE_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_MOVE_7", + "description": "SEAT_HEADREST_FORE_AFT_MOVE_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_HEADREST_FORE_AFT_MOVE_8", + "description": "SEAT_HEADREST_FORE_AFT_MOVE_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_OCCUPANCY_0", + "description": "SEAT_OCCUPANCY_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_OCCUPANCY_1", + "description": "SEAT_OCCUPANCY_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_OCCUPANCY_2", + "description": "SEAT_OCCUPANCY_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_OCCUPANCY_3", + "description": "SEAT_OCCUPANCY_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_OCCUPANCY_4", + "description": "SEAT_OCCUPANCY_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_OCCUPANCY_5", + "description": "SEAT_OCCUPANCY_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_OCCUPANCY_6", + "description": "SEAT_OCCUPANCY_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_OCCUPANCY_7", + "description": "SEAT_OCCUPANCY_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.SEAT_OCCUPANCY_8", + "description": "SEAT_OCCUPANCY_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_0", + "description": "WINDOW_POS_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_1", + "description": "WINDOW_POS_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_2", + "description": "WINDOW_POS_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_3", + "description": "WINDOW_POS_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_4", + "description": "WINDOW_POS_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_5", + "description": "WINDOW_POS_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_6", + "description": "WINDOW_POS_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_7", + "description": "WINDOW_POS_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_8", + "description": "WINDOW_POS_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_POS_9", + "description": "WINDOW_POS_9" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_0", + "description": "WINDOW_MOVE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_1", + "description": "WINDOW_MOVE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_2", + "description": "WINDOW_MOVE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_3", + "description": "WINDOW_MOVE_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_4", + "description": "WINDOW_MOVE_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_5", + "description": "WINDOW_MOVE_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_6", + "description": "WINDOW_MOVE_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_7", + "description": "WINDOW_MOVE_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_8", + "description": "WINDOW_MOVE_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_MOVE_9", + "description": "WINDOW_MOVE_9" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_0", + "description": "WINDOW_LOCK_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_1", + "description": "WINDOW_LOCK_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_2", + "description": "WINDOW_LOCK_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_3", + "description": "WINDOW_LOCK_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_4", + "description": "WINDOW_LOCK_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_5", + "description": "WINDOW_LOCK_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_6", + "description": "WINDOW_LOCK_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_7", + "description": "WINDOW_LOCK_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_8", + "description": "WINDOW_LOCK_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.WINDOW_LOCK_9", + "description": "WINDOW_LOCK_9" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HEADLIGHTS_STATE", + "description": "HEADLIGHTS_STATE" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HIGH_BEAM_LIGHTS_STATE", + "description": "HIGH_BEAM_LIGHTS_STATE" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.FOG_LIGHTS_STATE", + "description": "FOG_LIGHTS_STATE" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HAZARD_LIGHTS_STATE", + "description": "HAZARD_LIGHTS_STATE" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HEADLIGHTS_SWITCH", + "description": "HEADLIGHTS_SWITCH" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HIGH_BEAM_LIGHTS_SWITCH", + "description": "HIGH_BEAM_LIGHTS_SWITCH" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.FOG_LIGHTS_SWITCH", + "description": "FOG_LIGHTS_SWITCH" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.HAZARD_LIGHTS_SWITCH", + "description": "HAZARD_LIGHTS_SWITCH" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.CABIN_LIGHTS_STATE", + "description": "CABIN_LIGHTS_STATE" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.CABIN_LIGHTS_SWITCH", + "description": "CABIN_LIGHTS_SWITCH" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_STATE_0", + "description": "READING_LIGHTS_STATE_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_STATE_1", + "description": "READING_LIGHTS_STATE_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_STATE_2", + "description": "READING_LIGHTS_STATE_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_STATE_3", + "description": "READING_LIGHTS_STATE_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_STATE_4", + "description": "READING_LIGHTS_STATE_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_STATE_5", + "description": "READING_LIGHTS_STATE_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_STATE_6", + "description": "READING_LIGHTS_STATE_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_STATE_7", + "description": "READING_LIGHTS_STATE_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_STATE_8", + "description": "READING_LIGHTS_STATE_8" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_SWITCH_0", + "description": "READING_LIGHTS_SWITCH_0" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_SWITCH_1", + "description": "READING_LIGHTS_SWITCH_1" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_SWITCH_2", + "description": "READING_LIGHTS_SWITCH_2" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_SWITCH_3", + "description": "READING_LIGHTS_SWITCH_3" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_SWITCH_4", + "description": "READING_LIGHTS_SWITCH_4" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_SWITCH_5", + "description": "READING_LIGHTS_SWITCH_5" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_SWITCH_6", + "description": "READING_LIGHTS_SWITCH_6" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_SWITCH_7", + "description": "READING_LIGHTS_SWITCH_7" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.VHAL.READING_LIGHTS_SWITCH_8", + "description": "READING_LIGHTS_SWITCH_8" + } + } +] diff --git a/tools/android-app/cloud/campaign-android.json b/tools/android-app/cloud/campaign-android.json index ee9a7de2..88604f9a 100644 --- a/tools/android-app/cloud/campaign-android.json +++ b/tools/android-app/cloud/campaign-android.json @@ -88,6 +88,192 @@ }, { "name": "Vehicle.OBD.Odometer" + }, + { + "name": "Vehicle.VHAL.PERF_ODOMETER" + }, + { + "name": "Vehicle.VHAL.PERF_VEHICLE_SPEED" + }, + { + "name": "Vehicle.VHAL.PERF_STEERING_ANGLE" + }, + { + "name": "Vehicle.VHAL.ENGINE_COOLANT_TEMP" + }, + { + "name": "Vehicle.VHAL.ENGINE_OIL_LEVEL" + }, + { + "name": "Vehicle.VHAL.ENGINE_OIL_TEMP" + }, + { + "name": "Vehicle.VHAL.ENGINE_RPM" + }, + { + "name": "Vehicle.VHAL.FUEL_LEVEL" + }, + { + "name": "Vehicle.VHAL.EV_BATTERY_LEVEL" + }, + { + "name": "Vehicle.VHAL.EV_CHARGE_PORT_CONNECTED" + }, + { + "name": "Vehicle.VHAL.EV_BATTERY_INSTANTANEOUS_CHARGE_RATE" + }, + { + "name": "Vehicle.VHAL.RANGE_REMAINING" + }, + { + "name": "Vehicle.VHAL.TIRE_PRESSURE_0" + }, + { + "name": "Vehicle.VHAL.TIRE_PRESSURE_1" + }, + { + "name": "Vehicle.VHAL.TIRE_PRESSURE_2" + }, + { + "name": "Vehicle.VHAL.TIRE_PRESSURE_3" + }, + { + "name": "Vehicle.VHAL.GEAR_SELECTION" + }, + { + "name": "Vehicle.VHAL.CURRENT_GEAR" + }, + { + "name": "Vehicle.VHAL.PARKING_BRAKE_ON" + }, + { + "name": "Vehicle.VHAL.FUEL_LEVEL_LOW" + }, + { + "name": "Vehicle.VHAL.TURN_SIGNAL_STATE" + }, + { + "name": "Vehicle.VHAL.IGNITION_STATE" + }, + { + "name": "Vehicle.VHAL.ABS_ACTIVE" + }, + { + "name": "Vehicle.VHAL.TRACTION_CONTROL_ACTIVE" + }, + { + "name": "Vehicle.VHAL.HVAC_FAN_SPEED_0" + }, + { + "name": "Vehicle.VHAL.HVAC_FAN_DIRECTION_0" + }, + { + "name": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_0" + }, + { + "name": "Vehicle.VHAL.HVAC_TEMPERATURE_SET_1" + }, + { + "name": "Vehicle.VHAL.HVAC_AC_ON_0" + }, + { + "name": "Vehicle.VHAL.HVAC_RECIRC_ON_0" + }, + { + "name": "Vehicle.VHAL.HVAC_DUAL_ON_0" + }, + { + "name": "Vehicle.VHAL.HVAC_AUTO_ON_0" + }, + { + "name": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_0" + }, + { + "name": "Vehicle.VHAL.HVAC_SEAT_TEMPERATURE_1" + }, + { + "name": "Vehicle.VHAL.HVAC_SIDE_MIRROR_HEAT_0" + }, + { + "name": "Vehicle.VHAL.HVAC_STEERING_WHEEL_HEAT" + }, + { + "name": "Vehicle.VHAL.HVAC_POWER_ON_0" + }, + { + "name": "Vehicle.VHAL.ENV_OUTSIDE_TEMPERATURE" + }, + { + "name": "Vehicle.VHAL.DOOR_POS_0" + }, + { + "name": "Vehicle.VHAL.DOOR_POS_1" + }, + { + "name": "Vehicle.VHAL.DOOR_POS_2" + }, + { + "name": "Vehicle.VHAL.DOOR_POS_3" + }, + { + "name": "Vehicle.VHAL.DOOR_POS_4" + }, + { + "name": "Vehicle.VHAL.DOOR_LOCK_0" + }, + { + "name": "Vehicle.VHAL.DOOR_LOCK_1" + }, + { + "name": "Vehicle.VHAL.DOOR_LOCK_2" + }, + { + "name": "Vehicle.VHAL.DOOR_LOCK_3" + }, + { + "name": "Vehicle.VHAL.SEAT_OCCUPANCY_0" + }, + { + "name": "Vehicle.VHAL.SEAT_OCCUPANCY_1" + }, + { + "name": "Vehicle.VHAL.WINDOW_POS_0" + }, + { + "name": "Vehicle.VHAL.WINDOW_POS_1" + }, + { + "name": "Vehicle.VHAL.WINDOW_POS_2" + }, + { + "name": "Vehicle.VHAL.WINDOW_POS_3" + }, + { + "name": "Vehicle.VHAL.WINDOW_POS_4" + }, + { + "name": "Vehicle.VHAL.HEADLIGHTS_STATE" + }, + { + "name": "Vehicle.VHAL.HIGH_BEAM_LIGHTS_STATE" + }, + { + "name": "Vehicle.VHAL.FOG_LIGHTS_STATE" + }, + { + "name": "Vehicle.VHAL.HAZARD_LIGHTS_STATE" + }, + { + "name": "Vehicle.VHAL.HEADLIGHTS_SWITCH" + }, + { + "name": "Vehicle.VHAL.HIGH_BEAM_LIGHTS_SWITCH" + }, + { + "name": "Vehicle.VHAL.FOG_LIGHTS_SWITCH" + }, + { + "name": "Vehicle.VHAL.HAZARD_LIGHTS_SWITCH" } ] } diff --git a/tools/android-app/cloud/gen-aaos-vhal-info.py b/tools/android-app/cloud/gen-aaos-vhal-info.py new file mode 100644 index 00000000..22fc6c17 --- /dev/null +++ b/tools/android-app/cloud/gen-aaos-vhal-info.py @@ -0,0 +1,113 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +import json +import re + +# Before running this script to generate the output files, firstly create a file called +# aaos-vhal-types.h with the content from: +# https://cs.android.com/android/platform/superproject/main/+/main:prebuilts/vndk/v30/x86/include/generated-headers/hardware/interfaces/automotive/vehicle/2.0/android.hardware.automotive.vehicle@2.0_genc++_headers/gen/android/hardware/automotive/vehicle/2.0/types.h +# The output in aaos-vhal-props-table.txt can then be copied to +# `tools/android-app/app/src/main/java/com/aws/iotfleetwise/AaosVehicleProperties.java` + +with open("aaos-vhal-types.h") as fp: + text = fp.read() + +result_sizes = { + "INFO_FUEL_TYPE": 4, + "INFO_EV_CONNECTOR_TYPE": 4, + "INFO_EXTERIOR_DIMENSIONS": 8, + "INFO_MULTI_EV_PORT_LOCATIONS": 2, + "WHEEL_TICK": 5, + "HVAC_FAN_DIRECTION_AVAILABLE": 4, + "AP_POWER_STATE_REQ": 2, + "AP_POWER_STATE_REPORT": 2, + "CLUSTER_DISPLAY_STATE": 9, +} + +area_sizes = { + "SEAT": 9, + "WHEEL": 4, + "WINDOW": 10, + "DOOR": 8, + "MIRROR": 3, +} + +ignore = [ + "HW_ROTARY_INPUT", + "HW_KEY_INPUT", + "HW_CUSTOM_INPUT", + "SUPPORT_CUSTOMIZE_VENDOR_PERMISSION", + "EVS_SERVICE_REQUEST", + "CLUSTER_SWITCH_UI", + "CLUSTER_REQUEST_DISPLAY", +] + +decoders = [] +nodes = [ + {"branch": {"fullyQualifiedName": "Vehicle.VHAL", "description": "Android Automotive OS VHAL"}} +] +fqns = "" +matches = re.findall( + r" ([A-Z0-9_]+) = ([0-9]+) \/\*.+VehiclePropertyType:" + r"([A-Z0-9_]+).+VehicleArea:([A-Z]+).+\*\/", + text, +) +for match in matches: + prop_name = match[0] + prop_id = int(match[1]) + prop_type = match[2] + prop_area = match[3] + if prop_name == "INFO_DRIVER_SEAT": # Special case for INFO_DRIVER_SEAT + prop_area = "GLOBAL" + if ( + prop_type not in ["BOOLEAN", "INT32", "FLOAT", "INT32_VEC", "INT64_VEC"] + or prop_name in ignore + ): + print(f"Ignoring {prop_name}: {prop_id}, {prop_type}") + continue + if "VEC" in prop_type and prop_name not in result_sizes: + print(f"Error: no size defined for {prop_name}") + exit(-1) + for area_index in [-1] if prop_area not in area_sizes else range(area_sizes[prop_area]): + for result_index in ( + [-1] if prop_name not in result_sizes else range(result_sizes[prop_name]) + ): + full_name = ( + prop_name + + ("" if area_index == -1 else f"_{area_index}") + + ("" if result_index == -1 else f"_{result_index}") + ) + fqn = "Vehicle.VHAL." + full_name + fqns += fqn + "\n" + decoders.append( + { + "fullyQualifiedName": fqn, + "type": "CAN_SIGNAL", + "interfaceId": "AAOS-VHAL-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": True, + "isSigned": True, + "startBit": (0 if area_index == -1 else area_index), + "offset": prop_id, + "factor": 1, + "length": (0 if result_index == -1 else result_index), + }, + } + ) + nodes.append( + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": fqn, + "description": full_name, + } + } + ) +with open("aaosVhalDecoders.json", "w") as fp: + json.dump(decoders, fp, indent=2) +with open("aaosVhalNodes.json", "w") as fp: + json.dump(nodes, fp, indent=2) +with open("aaos-vhal-fqns.txt", "w") as fp: + fp.write(fqns) diff --git a/tools/android-app/cloud/network-interfaces.json b/tools/android-app/cloud/network-interfaces.json index 729cd9eb..5bb1fa64 100644 --- a/tools/android-app/cloud/network-interfaces.json +++ b/tools/android-app/cloud/network-interfaces.json @@ -18,5 +18,14 @@ "protocolName": "CAN", "protocolVersion": "2.0B" } + }, + { + "interfaceId": "AAOS-VHAL-CAN", + "type": "CAN_INTERFACE", + "canInterface": { + "name": "can0", + "protocolName": "CAN", + "protocolVersion": "2.0B" + } } ] diff --git a/tools/android-app/cloud/provision.sh b/tools/android-app/cloud/provision.sh index 1295e11d..50253c2c 100755 --- a/tools/android-app/cloud/provision.sh +++ b/tools/android-app/cloud/provision.sh @@ -4,6 +4,7 @@ set -euo pipefail +S3_QR_CODE="false" S3_BUCKET="" S3_KEY_PREFIX="" S3_PRESIGNED_URL_EXPIRY=86400 # One day @@ -17,6 +18,9 @@ VEHICLE_NAME="fwdemo-android-${TIMESTAMP}" parse_args() { while [ "$#" -gt 0 ]; do case $1 in + --s3-qr-code) + S3_QR_CODE="true" + ;; --s3-bucket) S3_BUCKET=$2 shift @@ -43,6 +47,7 @@ parse_args() { ;; --help) echo "Usage: $0 [OPTION]" + echo " --s3-qr-code Store credentials in S3 and generate QR code with pre-signed provisioning URL" echo " --s3-bucket Existing S3 bucket name" echo " --s3-key-prefix S3 bucket prefix" echo " --s3-presigned-url-expiry S3 presigned URL expiry, default: ${S3_PRESIGNED_URL_EXPIRY}" @@ -62,21 +67,23 @@ parse_args() { parse_args "$@" -if [ "${S3_BUCKET}" == "" ]; then - echo -n "Enter name of existing S3 bucket: " - read S3_BUCKET +if ${S3_QR_CODE}; then if [ "${S3_BUCKET}" == "" ]; then - echo "Error: S3 bucket name required" - exit -1 + echo -n "Enter name of existing S3 bucket: " + read S3_BUCKET + if [ "${S3_BUCKET}" == "" ]; then + echo "Error: S3 bucket name required" + exit -1 + fi fi -fi -echo "Getting S3 bucket region..." -S3_BUCKET_REGION=`aws s3api get-bucket-location --bucket ${S3_BUCKET} | jq -r .LocationConstraint` -if [ -z "${S3_BUCKET_REGION}" ] || [ "${S3_BUCKET_REGION}" == "null" ]; then - S3_BUCKET_REGION="us-east-1" + echo "Getting S3 bucket region..." + S3_BUCKET_REGION=`aws s3api get-bucket-location --bucket ${S3_BUCKET} | jq -r .LocationConstraint` + if [ -z "${S3_BUCKET_REGION}" ] || [ "${S3_BUCKET_REGION}" == "null" ]; then + S3_BUCKET_REGION="us-east-1" + fi + echo ${S3_BUCKET_REGION} fi -echo ${S3_BUCKET_REGION} mkdir -p config ../../provision.sh \ @@ -99,21 +106,27 @@ echo {} | jq ".vehicle_name=\"${VEHICLE_NAME}\"" \ | jq ".mqtt_topic_prefix=\"${TOPIC_PREFIX}\"" \ > config/creds.json -S3_URL="s3://${S3_BUCKET}/${S3_KEY_PREFIX}${VEHICLE_NAME}-creds.json" -echo "Uploading credentials to S3..." -aws s3 cp --region ${REGION} config/creds.json ${S3_URL} -echo "Creating S3 pre-signed URL..." -S3_PRESIGNED_URL=`aws s3 presign --region ${S3_BUCKET_REGION} --expires-in ${S3_PRESIGNED_URL_EXPIRY} ${S3_URL}` -PROVISIONING_LINK="https://fleetwise-app.automotive.iot.aws.dev/config#url=`echo ${S3_PRESIGNED_URL} | jq -s -R -r @uri`" -QR_CODE_FILENAME="config/provisioning-qr-code.png" -segno --scale 5 --output ${QR_CODE_FILENAME} "${PROVISIONING_LINK}" +if ${S3_QR_CODE}; then + S3_URL="s3://${S3_BUCKET}/${S3_KEY_PREFIX}${VEHICLE_NAME}-creds.json" + echo "Uploading credentials to S3..." + aws s3 cp --region ${REGION} config/creds.json ${S3_URL} + echo "Creating S3 pre-signed URL..." + S3_PRESIGNED_URL=`aws s3 presign --region ${S3_BUCKET_REGION} --expires-in ${S3_PRESIGNED_URL_EXPIRY} ${S3_URL}` + PROVISIONING_LINK="https://fleetwise-app.automotive.iot.aws.dev/config#url=`echo ${S3_PRESIGNED_URL} | jq -s -R -r @uri`" -echo -echo "You can now download the provisioning QR code." -echo "----------------------------------" -echo "| Provisioning QR code filename: |" -echo "----------------------------------" -echo `realpath ${QR_CODE_FILENAME}` + QR_CODE_FILENAME="config/provisioning-qr-code.png" + segno --scale 5 --output ${QR_CODE_FILENAME} "${PROVISIONING_LINK}" -echo -echo "Optional: After you have downloaded and scanned the QR code, you can delete the credentials for security by running the following command: aws s3 rm ${S3_URL}" + echo + echo "You can now download the provisioning QR code." + echo "----------------------------------" + echo "| Provisioning QR code filename: |" + echo "----------------------------------" + echo `realpath ${QR_CODE_FILENAME}` + + echo + echo "Optional: After you have downloaded and scanned the QR code, you can delete the credentials for security by running the following command: aws s3 rm ${S3_URL}" +else + echo + echo "Finished!" +fi diff --git a/tools/android-app/cloud/setup-iotfleetwise.sh b/tools/android-app/cloud/setup-iotfleetwise.sh index 1edcf7dd..9f45a3bb 100755 --- a/tools/android-app/cloud/setup-iotfleetwise.sh +++ b/tools/android-app/cloud/setup-iotfleetwise.sh @@ -205,6 +205,7 @@ aws iotfleetwise delete-vehicle \ VEHICLE_NODE=`cat ../../cloud/vehicle-node.json` OBD_NODES=`cat ../../cloud/obd-nodes.json` DBC_NODES=`cat externalGpsNodes.json` +AAOS_NODES=`cat aaosVhalNodes.json` echo "Checking for existing signal catalog..." SIGNAL_CATALOG_LIST=`aws iotfleetwise list-signal-catalogs \ @@ -267,9 +268,28 @@ else echo "Signals exist and are in use, continuing" fi +echo "Updating AAOS signals in signal catalog..." +NODE_COUNT=`echo $AAOS_NODES | jq length` +for ((i=0; i<${NODE_COUNT}; i+=500)); do + NODES_SUBSET=`echo $AAOS_NODES | jq .[$i:$(($i+500))]` + if UPDATE_SIGNAL_CATALOG_STATUS=`aws iotfleetwise update-signal-catalog \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --name ${SIGNAL_CATALOG_NAME} \ + --description "AAOS signals" \ + --nodes-to-update "${NODES_SUBSET}" 2>&1`; then + echo ${UPDATE_SIGNAL_CATALOG_STATUS} | jq -r .arn + elif ! echo ${UPDATE_SIGNAL_CATALOG_STATUS} | grep -q "InvalidSignalsException"; then + echo ${UPDATE_SIGNAL_CATALOG_STATUS} >&2 + exit -1 + else + echo "Signals exist and are in use, continuing" + fi +done + echo "Creating model manifest..." # Make a list of all node names: NODE_LIST=`( echo ${DBC_NODES} | jq -r .[].sensor.fullyQualifiedName | grep Vehicle\\. ; \ + echo ${AAOS_NODES} | jq -r .[].sensor.fullyQualifiedName | grep Vehicle\\. ; \ echo ${OBD_NODES} | jq -r .[].sensor.fullyQualifiedName | grep Vehicle\\. ) | jq -Rn [inputs]` aws iotfleetwise create-model-manifest \ ${ENDPOINT_URL_OPTION} --region ${REGION} \ @@ -295,12 +315,24 @@ DECODER_MANIFEST_ARN=`aws iotfleetwise create-decoder-manifest \ --signal-decoders "${OBD_SIGNAL_DECODERS}" | jq -r .arn` echo ${DECODER_MANIFEST_ARN} +echo "Updating decoder manifest with external GPS decoders..." SIGNAL_DECODERS=`cat externalGpsDecoders.json` aws iotfleetwise update-decoder-manifest \ ${ENDPOINT_URL_OPTION} --region ${REGION} \ --name ${NAME}-decoder-manifest \ --signal-decoders-to-add "${SIGNAL_DECODERS}" | jq -r .arn +echo "Updating decoder manifest with AAOS VHAL decoders..." +SIGNAL_DECODERS=`cat aaosVhalDecoders.json` +DECODER_COUNT=`echo $SIGNAL_DECODERS | jq length` +for ((i=0; i<${DECODER_COUNT}; i+=200)); do + DECODER_SUBSET=`echo $SIGNAL_DECODERS | jq .[$i:$(($i+200))]` + aws iotfleetwise update-decoder-manifest \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --name ${NAME}-decoder-manifest \ + --signal-decoders-to-add "${DECODER_SUBSET}" | jq -r .arn +done + echo "Activating decoder manifest..." aws iotfleetwise update-decoder-manifest \ ${ENDPOINT_URL_OPTION} --region ${REGION} \ @@ -355,6 +387,9 @@ aws iotfleetwise update-campaign \ --name ${NAME}-campaign \ --action APPROVE | jq -r .arn -echo "=========" -echo "Finished!" -echo "=========" +echo +echo Finished, you can run the following SQL query to retrieve the collected data: +echo "--------------------------------" +echo "| Amazon Timestream SQL query: |" +echo "--------------------------------" +echo "SELECT * FROM \"${TIMESTREAM_DB_NAME}\".\"${TIMESTREAM_TABLE_NAME}\" WHERE vehicleName='${VEHICLE_NAME}' ORDER BY time DESC LIMIT 1000" diff --git a/tools/android-app/settings.gradle b/tools/android-app/settings.gradle index d62ae53b..9a6f086c 100644 --- a/tools/android-app/settings.gradle +++ b/tools/android-app/settings.gradle @@ -12,5 +12,5 @@ dependencyResolutionManagement { mavenCentral() } } -rootProject.name = "AWS IoT FleetWise Edge" +rootProject.name = "FWE" include ':app' diff --git a/tools/build-fwe-cross-android.sh b/tools/build-fwe-cross-android.sh index 7b2b403f..c1047a6a 100755 --- a/tools/build-fwe-cross-android.sh +++ b/tools/build-fwe-cross-android.sh @@ -8,16 +8,24 @@ SCRIPT_DIR=$(dirname $(realpath "$0")) source ${SCRIPT_DIR}/install-deps-versions.sh NATIVE_PREFIX="/usr/local/`gcc -dumpmachine`" +ARCHS="x86_64:x86_64-linux-android \ + armeabi-v7a:armv7a-linux-androideabi \ + arm64-v8a:aarch64-linux-android" parse_args() { while [ "$#" -gt 0 ]; do case $1 in + --archs) + ARCHS=$2 + shift + ;; --native-prefix) NATIVE_PREFIX="$2" shift ;; --help) echo "Usage: $0 [OPTION]" + echo " --archs Space separated list of archs in the format :" echo " --native-prefix Native install prefix, default ${NATIVE_PREFIX}" exit 0 ;; @@ -35,12 +43,13 @@ build() { TARGET_ARCH="$1" HOST_PLATFORM="$2" - mkdir ${TARGET_ARCH} && cd ${TARGET_ARCH} + mkdir -p ${TARGET_ARCH} && cd ${TARGET_ARCH} LDFLAGS=-L/usr/local/${HOST_PLATFORM}/lib cmake \ -DFWE_STATIC_LINK=On \ -DFWE_STRIP_SYMBOLS=On \ -DFWE_FEATURE_CUSTOM_DATA_SOURCE=On \ -DFWE_FEATURE_EXTERNAL_GPS=On \ + -DFWE_FEATURE_AAOS_VHAL=On \ -DFWE_BUILD_EXECUTABLE=Off \ -DFWE_BUILD_ANDROID_SHARED_LIBRARY=On \ -DCMAKE_BUILD_TYPE=Release \ @@ -56,5 +65,8 @@ build() { cd .. } -build "armeabi-v7a" "armv7a-linux-androideabi" -build "arm64-v8a" "aarch64-linux-android" +for ARCH in ${ARCHS}; do + TARGET_ARCH=`echo $ARCH | cut -d ':' -f1` + HOST_PLATFORM=`echo $ARCH | cut -d ':' -f2` + build ${TARGET_ARCH} ${HOST_PLATFORM} +done diff --git a/tools/cansim/canigen.py b/tools/cansim/canigen.py index 9cc52f74..c92c9a7d 100644 --- a/tools/cansim/canigen.py +++ b/tools/cansim/canigen.py @@ -80,6 +80,7 @@ def __init__( f" setting it to default of {default_cycle_time_ms} ms" ) thread = Thread( + name=f"CanigenSignals-{msg.frame_id}", target=self._sig_thread, args=( msg.name, @@ -90,7 +91,9 @@ def __init__( self._threads.append(thread) if obd_config_filename is not None: for ecu in self._obd_config["ecus"]: - thread = Thread(target=self._obd_thread, args=(ecu,)) + thread = Thread( + name=f"CanigenObd-{ecu['tx_id']}", target=self._obd_thread, args=(ecu,) + ) thread.start() self._threads.append(thread) @@ -131,6 +134,7 @@ def _write_frame(self, msg, data): ) def _sig_thread(self, msg_name, cycle_time): + send_time = time.monotonic() while not self._stop: msg = self._db.get_message_by_name(msg_name) vals = {} @@ -151,7 +155,10 @@ def _sig_thread(self, msg_name, cycle_time): data=data, ) self._can_bus.send(frame) - time.sleep(cycle_time / 1000.0) + send_time += cycle_time / 1000 + sleep_time = send_time - time.monotonic() + if sleep_time >= 0: + time.sleep(sleep_time) def _get_supported_pids(self, num_range, ecu): supported = False diff --git a/tools/cfn-templates/README.md b/tools/cfn-templates/README.md deleted file mode 100644 index d6e5149e..00000000 --- a/tools/cfn-templates/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# AWS CloudFormation Templates for IoT FleetWise - -These AWS CloudFormation template files can be used to compile and run the IoT FleetWise Edge Agent. -See the [Edge Agent Developer](../../docs/dev-guide/edge-agent-dev-guide.md) Guide for more -information. diff --git a/tools/cfn-templates/fwdemo.yml b/tools/cfn-templates/fwdemo.yml index 68b95ce9..cd2ca1be 100644 --- a/tools/cfn-templates/fwdemo.yml +++ b/tools/cfn-templates/fwdemo.yml @@ -165,7 +165,7 @@ Resources: ./aws/install rm awscliv2.zip - # Download agent: + # Download FWE: cd /home/ubuntu if echo ${BinaryUrl} | grep -q 's3://'; then sudo -u ubuntu aws s3 cp ${BinaryUrl} aws-iot-fleetwise-edge.tar.gz @@ -421,39 +421,39 @@ Mappings: # Ubuntu 20.04 arm64 AMIs AMIRegionMap: ap-northeast-1: - AMIID: ami-0836b08b6e50975e0 + AMIID: ami-01444f83954203c6f ap-northeast-2: - AMIID: ami-09a9b3cefb0428387 + AMIID: ami-0ac62099928d25fec ap-northeast-3: - AMIID: ami-0e48a9cf92bd86508 + AMIID: ami-0efdceaebc778c5f3 ap-south-1: - AMIID: ami-0a20a058ee035d49c + AMIID: ami-0df6182e39efe7c4d ap-southeast-1: - AMIID: ami-04a46a6d4dbeb5fe0 + AMIID: ami-01d87e25d3c65ec37 ap-southeast-2: - AMIID: ami-0c9be6cb7d01a9066 + AMIID: ami-0641fc20c25fdd380 ca-central-1: - AMIID: ami-0c4c276bcaf3911b1 + AMIID: ami-0a3e942fe4813672b eu-central-1: - AMIID: ami-048c3444604e23b5c + AMIID: ami-0d85ad3aa712d96af eu-north-1: - AMIID: ami-04a8d27b5fe65dd51 + AMIID: ami-0ff124a3d7381bfec eu-west-1: - AMIID: ami-0ea366b1f105cb0aa + AMIID: ami-09c59b011574e4c96 eu-west-2: - AMIID: ami-06b427ade4da0da55 + AMIID: ami-03e26d11b665ac7be eu-west-3: - AMIID: ami-06cc80fe7e768f974 + AMIID: ami-00771c0ac817397bd sa-east-1: - AMIID: ami-081f3905bf0562cdd + AMIID: ami-08dc4b989f93eafb9 us-east-1: - AMIID: ami-0620aa8714211d0af + AMIID: ami-097d5b19d4f1a7d1b us-east-2: - AMIID: ami-0bc02c3c09aaee8ea + AMIID: ami-0071e4b30f26879e2 us-west-1: - AMIID: ami-038e08160883bc1f9 + AMIID: ami-054cf1a291b64095f us-west-2: - AMIID: ami-0c4dfe348469b0ae5 + AMIID: ami-0dca369228f3b2ce7 FleetSizeEc2InstanceTypeMap: "1": InstanceType: m6g.xlarge diff --git a/tools/cfn-templates/fwdev.yml b/tools/cfn-templates/fwdev.yml index f8589c0b..42127ab3 100644 --- a/tools/cfn-templates/fwdev.yml +++ b/tools/cfn-templates/fwdev.yml @@ -163,39 +163,39 @@ Mappings: # Ubuntu 20.04 arm64 AMIs AMIRegionMap: ap-northeast-1: - AMIID: ami-0836b08b6e50975e0 + AMIID: ami-01444f83954203c6f ap-northeast-2: - AMIID: ami-09a9b3cefb0428387 + AMIID: ami-0ac62099928d25fec ap-northeast-3: - AMIID: ami-0e48a9cf92bd86508 + AMIID: ami-0efdceaebc778c5f3 ap-south-1: - AMIID: ami-0a20a058ee035d49c + AMIID: ami-0df6182e39efe7c4d ap-southeast-1: - AMIID: ami-04a46a6d4dbeb5fe0 + AMIID: ami-01d87e25d3c65ec37 ap-southeast-2: - AMIID: ami-0c9be6cb7d01a9066 + AMIID: ami-0641fc20c25fdd380 ca-central-1: - AMIID: ami-0c4c276bcaf3911b1 + AMIID: ami-0a3e942fe4813672b eu-central-1: - AMIID: ami-048c3444604e23b5c + AMIID: ami-0d85ad3aa712d96af eu-north-1: - AMIID: ami-04a8d27b5fe65dd51 + AMIID: ami-0ff124a3d7381bfec eu-west-1: - AMIID: ami-0ea366b1f105cb0aa + AMIID: ami-09c59b011574e4c96 eu-west-2: - AMIID: ami-06b427ade4da0da55 + AMIID: ami-03e26d11b665ac7be eu-west-3: - AMIID: ami-06cc80fe7e768f974 + AMIID: ami-00771c0ac817397bd sa-east-1: - AMIID: ami-081f3905bf0562cdd + AMIID: ami-08dc4b989f93eafb9 us-east-1: - AMIID: ami-0620aa8714211d0af + AMIID: ami-097d5b19d4f1a7d1b us-east-2: - AMIID: ami-0bc02c3c09aaee8ea + AMIID: ami-0071e4b30f26879e2 us-west-1: - AMIID: ami-038e08160883bc1f9 + AMIID: ami-054cf1a291b64095f us-west-2: - AMIID: ami-0c4dfe348469b0ae5 + AMIID: ami-0dca369228f3b2ce7 Outputs: Ec2InstanceId: Description: "EC2 instance ID" diff --git a/tools/cfn-templates/fwremoteprofiler.yml b/tools/cfn-templates/fwremoteprofiler.yml index 70de49ac..f117ad35 100644 --- a/tools/cfn-templates/fwremoteprofiler.yml +++ b/tools/cfn-templates/fwremoteprofiler.yml @@ -93,7 +93,7 @@ Resources: MetricUnit: "${metric10.unit}" MetricValue: "${metric10.value}" RoleArn: !GetAtt RemoteProfilerCloudwatchRole.Arn - Description: Used by the Remote Profiler of the AWS IoT Fleetwise Edge Client see METRICS.md + Description: Used by the Remote Profiler of FWE, see METRICS.md Sql: SELECT * FROM 'aws-iot-fleetwise-metrics-upload' RemoteProfilerLogRule: Type: AWS::IoT::TopicRule @@ -104,7 +104,7 @@ Resources: - CloudwatchLogs: LogGroupName: AWSIotFleetWiseEdge RoleArn: !GetAtt RemoteProfilerCloudwatchRole.Arn - Description: Used by the Remote Profiler of the AWS IoT Fleetwise Edge Client see METRICS.md + Description: Used by the Remote Profiler of FWE, see METRICS.md Sql: SELECT * FROM 'aws-iot-fleetwise-logging-upload' RemoteProfilerLogGroup: Type: AWS::Logs::LogGroup diff --git a/tools/cloud/.gitignore b/tools/cloud/.gitignore index 862d8106..e8c33ccc 100644 --- a/tools/cloud/.gitignore +++ b/tools/cloud/.gitignore @@ -2,3 +2,4 @@ *.html part-* s3-bucket-policy.json +demo.env diff --git a/tools/cloud/clean-up.sh b/tools/cloud/clean-up.sh index c233a15e..751174d8 100755 --- a/tools/cloud/clean-up.sh +++ b/tools/cloud/clean-up.sh @@ -8,7 +8,7 @@ ENDPOINT_URL="" ENDPOINT_URL_OPTION="" REGION="us-east-1" VEHICLE_NAME="" -TIMESTAMP="" +DISAMBIGUATOR="" ACCOUNT_ID=`aws sts get-caller-identity --query "Account" --output text` SERVICE_ROLE="IoTFleetWiseServiceRole" SERVICE_ROLE_POLICY_ARN="arn:aws:iam::${ACCOUNT_ID}:policy/" @@ -26,8 +26,8 @@ parse_args() { FLEET_SIZE=$2 shift ;; - --timestamp) - TIMESTAMP=$2 + --disambiguator) + DISAMBIGUATOR=$2 shift ;; --endpoint-url) @@ -40,13 +40,13 @@ parse_args() { ;; --help) echo "Usage: $0 [OPTION]" - echo " --vehicle-name Vehicle name" - echo " --fleet-size Size of fleet, default: ${FLEET_SIZE}. When greater than 1," - echo " the instance number will be appended to each" - echo " Vehicle name after a '-', e.g. fwdemo-42" - echo " --timestamp Timestamp of demo.sh script" - echo " --endpoint-url The endpoint URL used for AWS CLI calls" - echo " --region The region used for AWS CLI calls, default: ${REGION}" + echo " --vehicle-name Vehicle name" + echo " --fleet-size Size of fleet, default: ${FLEET_SIZE}. When greater than 1," + echo " the instance number will be appended to each" + echo " Vehicle name after a '-', e.g. fwdemo-42" + echo " --disambiguator The unique string used by the demo.sh script to avoid resource name conflicts" + echo " --endpoint-url The endpoint URL used for AWS CLI calls" + echo " --region The region used for AWS CLI calls, default: ${REGION}" exit 0 ;; esac @@ -60,8 +60,8 @@ parse_args() { echo "Error: Vehicle name not provided" exit -1 fi - if [ "${TIMESTAMP}" == "" ]; then - echo "Error: Timestamp not provided" + if [ "${DISAMBIGUATOR}" == "" ]; then + echo "Error: Disambiguator not provided" exit -1 fi } @@ -72,16 +72,23 @@ echo "=======================================" parse_args "$@" -NAME="${VEHICLE_NAME}-${TIMESTAMP}" -SERVICE_ROLE="${SERVICE_ROLE}-${REGION}-${TIMESTAMP}" +if [ ${FLEET_SIZE} -gt 1 ]; then + VEHICLES=( $(seq -f "${VEHICLE_NAME}-%g" 0 $((${FLEET_SIZE}-1))) ) +else + VEHICLES=( ${VEHICLE_NAME} ) +fi + +NAME="${VEHICLE_NAME}-${DISAMBIGUATOR}" +SERVICE_ROLE="${SERVICE_ROLE}-${REGION}-${DISAMBIGUATOR}" SERVICE_ROLE_POLICY_ARN="${SERVICE_ROLE_POLICY_ARN}${SERVICE_ROLE}-policy" echo -n "Date: " date --rfc-3339=seconds -echo "Timestamp: ${TIMESTAMP}" +echo "Disambiguator: ${DISAMBIGUATOR}" echo "Vehicle name: ${VEHICLE_NAME}" echo "Fleet size: ${FLEET_SIZE}" +echo "Vehicles: ${VEHICLES[@]}" # $1 is the campaign name suspend_and_delete_campaign(){ @@ -101,51 +108,38 @@ suspend_and_delete_campaign ${NAME}-campaign suspend_and_delete_campaign ${NAME}-campaign-s3-json suspend_and_delete_campaign ${NAME}-campaign-s3-parquet -if ((FLEET_SIZE==1)); then - echo "Disassociating vehicle ${VEHICLE_NAME}..." - aws iotfleetwise disassociate-vehicle-fleet \ - ${ENDPOINT_URL_OPTION} --region ${REGION} \ - --fleet-id ${NAME}-fleet \ - --vehicle-name "${VEHICLE_NAME}" 2> /dev/null || true -else - echo "Disassociating vehicle ${VEHICLE_NAME}-0..$((FLEET_SIZE-1))..." - for ((i=0; i<${FLEET_SIZE}; i+=${BATCH_SIZE})); do - for ((j=0; j<${BATCH_SIZE} && i+j<${FLEET_SIZE}; j++)); do - # This output group is run in a background process. Note that stderr is redirected to stream 3 and back, - # to print stderr from the output group, but not info about the background process. - { \ - aws iotfleetwise disassociate-vehicle-fleet \ - ${ENDPOINT_URL_OPTION} --region ${REGION} \ - --fleet-id ${NAME}-fleet \ - --vehicle-name "${VEHICLE_NAME}-$((i+j))" 2> /dev/null || true \ - 2>&3 &} 3>&2 2>/dev/null - done - # Wait for all background processes to finish - wait +for ((i=0; i<${FLEET_SIZE}; i+=${BATCH_SIZE})); do + for ((j=0; j<${BATCH_SIZE} && i+j<${FLEET_SIZE}; j++)); do + vehicle=${VEHICLES[$((i+j))]} + echo "Disassociating vehicle ${vehicle}..." + # This output group is run in a background process. Note that stderr is redirected to stream 3 and back, + # to print stderr from the output group, but not info about the background process. + { \ + aws iotfleetwise disassociate-vehicle-fleet \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --fleet-id ${NAME}-fleet \ + --vehicle-name "${vehicle}" 2> /dev/null || true \ + 2>&3 &} 3>&2 2>/dev/null done -fi - -if ((FLEET_SIZE==1)); then - echo "Deleting vehicle ${VEHICLE_NAME}..." - aws iotfleetwise delete-vehicle \ - ${ENDPOINT_URL_OPTION} --region ${REGION} \ - --vehicle-name "${VEHICLE_NAME}" 2> /dev/null || true -else - echo "Deleting vehicle ${VEHICLE_NAME}-0..$((FLEET_SIZE-1))..." - for ((i=0; i<${FLEET_SIZE}; i+=${BATCH_SIZE})); do - for ((j=0; j<${BATCH_SIZE} && i+j<${FLEET_SIZE}; j++)); do - # This output group is run in a background process. Note that stderr is redirected to stream 3 and back, - # to print stderr from the output group, but not info about the background process. - { \ - aws iotfleetwise delete-vehicle \ - ${ENDPOINT_URL_OPTION} --region ${REGION} \ - --vehicle-name "${VEHICLE_NAME}-$((i+j))" 2> /dev/null || true \ - 2>&3 &} 3>&2 2>/dev/null - done - # Wait for all background processes to finish - wait + # Wait for all background processes to finish + wait +done + +for ((i=0; i<${FLEET_SIZE}; i+=${BATCH_SIZE})); do + for ((j=0; j<${BATCH_SIZE} && i+j<${FLEET_SIZE}; j++)); do + vehicle=${VEHICLES[$((i+j))]} + echo "Deleting vehicle ${vehicle}..." + # This output group is run in a background process. Note that stderr is redirected to stream 3 and back, + # to print stderr from the output group, but not info about the background process. + { \ + aws iotfleetwise delete-vehicle \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --vehicle-name "${vehicle}" 2> /dev/null || true \ + 2>&3 &} 3>&2 2>/dev/null done -fi + # Wait for all background processes to finish + wait +done echo "Deleting fleet..." aws iotfleetwise delete-fleet \ diff --git a/tools/cloud/demo.sh b/tools/cloud/demo.sh index 70733582..7147c3aa 100755 --- a/tools/cloud/demo.sh +++ b/tools/cloud/demo.sh @@ -4,19 +4,23 @@ set -euo pipefail +ACCOUNT_ID=`aws sts get-caller-identity --query "Account" --output text` +UUID="$(cat /proc/sys/kernel/random/uuid)" +# A short string to avoid resource name conflicts. Since most resources names have a small +# characters limit, using a full uuid would not be possible. As those names just need to be unique +# in the same account, truncating the uuid should be enough. +DISAMBIGUATOR="${UUID:0:8}" +S3_SUFFIX="${ACCOUNT_ID}-${DISAMBIGUATOR}" ENDPOINT_URL="" ENDPOINT_URL_OPTION="" REGION="us-east-1" -TIMESTAMP=`date +%s` -ACCOUNT_ID=`aws sts get-caller-identity --query "Account" --output text` DEFAULT_VEHICLE_NAME="fwdemo" VEHICLE_NAME="" -TIMESTREAM_DB_NAME="IoTFleetWiseDB-${TIMESTAMP}" +TIMESTREAM_DB_NAME="IoTFleetWiseDB-${DISAMBIGUATOR}" TIMESTREAM_TABLE_NAME="VehicleDataTable" SERVICE_ROLE="IoTFleetWiseServiceRole" SERVICE_ROLE_POLICY_ARN="" SERVICE_PRINCIPAL="iotfleetwise.amazonaws.com" -RANDOM_HASH="$(cat /proc/sys/kernel/random/uuid)" BUCKET_NAME="" SKIP_S3_POLICY=true CAMPAIGN_FILE="" @@ -31,6 +35,7 @@ HEALTH_CHECK_RETRIES=360 # About 30mins MAX_ATTEMPTS_ON_REGISTRATION_FAILURE=5 FORCE_REGISTRATION=false MIN_CLI_VERSION="aws-cli/2.11.24" +CREATED_SIGNAL_CATALOG_NAME="" parse_args() { while [ "$#" -gt 0 ]; do @@ -81,7 +86,7 @@ parse_args() { echo " Vehicle name after a '-', e.g. ${DEFAULT_VEHICLE_NAME}-42" echo " --campaign-file Campaign JSON file, default: ${DEFAULT_CAMPAIGN_FILE}" echo " --dbc-file DBC file, default: ${DEFAULT_DBC_FILE}" - echo " --bucket-name S3 bucket name, default: iot-fleetwise-demo-" + echo " --bucket-name S3 bucket name, if not specified a new bucket will be created" echo " --clean-up Delete created resources" echo " --enable-s3-upload Create campaigns to upload data to S3" echo " --endpoint-url The endpoint URL used for AWS CLI calls" @@ -129,19 +134,26 @@ if [ "${CAMPAIGN_FILE}" == "" ]; then CAMPAIGN_FILE=${DEFAULT_CAMPAIGN_FILE} fi -if [ "${BUCKET_NAME}" == "" ]; then - BUCKET_NAME="iot-fleetwise-demo-${RANDOM_HASH}" +if [ ${S3_UPLOAD} == true ] && [ "${BUCKET_NAME}" == "" ]; then + BUCKET_NAME="iot-fleetwise-demo-${S3_SUFFIX}" SKIP_S3_POLICY=false fi -NAME="${VEHICLE_NAME}-${TIMESTAMP}" -SERVICE_ROLE="${SERVICE_ROLE}-${REGION}-${TIMESTAMP}" +if [ ${FLEET_SIZE} -gt 1 ]; then + VEHICLES=( $(seq -f "${VEHICLE_NAME}-%g" 0 $((${FLEET_SIZE}-1))) ) +else + VEHICLES=( ${VEHICLE_NAME} ) +fi + +NAME="${VEHICLE_NAME}-${DISAMBIGUATOR}" +SERVICE_ROLE="${SERVICE_ROLE}-${REGION}-${DISAMBIGUATOR}" echo -n "Date: " date +%Y-%m-%dT%H:%M:%S%z -echo "Timestamp: ${TIMESTAMP}" +echo "Disambiguator: ${DISAMBIGUATOR}" echo "Vehicle name: ${VEHICLE_NAME}" echo "Fleet Size: ${FLEET_SIZE}" +echo "Vehicles: ${VEHICLES[@]}" # AWS CLI v1.x has a double base64 encoding issue echo "Checking AWS CLI version..." @@ -153,16 +165,28 @@ if [[ "${CLI_VERSION}" < "${MIN_CLI_VERSION}" ]]; then exit -1 fi -error_handler() { +save_variables() { + # Export some variables to a .env file so that they can be referenced by other scripts + echo " +TIMESTREAM_DB_NAME=${TIMESTREAM_DB_NAME} +TIMESTREAM_TABLE_NAME=${TIMESTREAM_TABLE_NAME} +BUCKET_NAME=${BUCKET_NAME} +" > demo.env +} + +cleanup() { + save_variables if [ ${CLEAN_UP} == true ]; then ./clean-up.sh \ --vehicle-name ${VEHICLE_NAME} \ --fleet-size ${FLEET_SIZE} \ - --timestamp ${TIMESTAMP} \ + --disambiguator ${DISAMBIGUATOR} \ ${ENDPOINT_URL_OPTION} \ - --region ${REGION} \ - --service-role ${SERVICE_ROLE} \ - --service-role-policy-arn ${SERVICE_ROLE_POLICY_ARN} + --region ${REGION} + if [ ! -z "${CREATED_SIGNAL_CATALOG_NAME}" ]; then + echo "Deleting signal catalog: ${CREATED_SIGNAL_CATALOG_NAME}" + aws iotfleetwise delete-signal-catalog --name ${CREATED_SIGNAL_CATALOG_NAME} ${ENDPOINT_URL_OPTION} --region=${REGION} + fi fi } @@ -182,7 +206,6 @@ approve_campaign() { echo "Waiting for campaign to become ready for approval..." while true; do sleep 5 - SIGNAL_CATALOG_COUNT=`echo ${SIGNAL_CATALOG_LIST} | jq '.summaries|length'` CAMPAIGN_STATUS=`aws iotfleetwise get-campaign \ ${ENDPOINT_URL_OPTION} --region ${REGION} \ --name $1` @@ -199,7 +222,7 @@ approve_campaign() { --action APPROVE | jq -r .arn } -trap error_handler ERR +trap cleanup EXIT echo "Getting AWS account ID..." echo ${ACCOUNT_ID} @@ -242,7 +265,7 @@ while [ "${ACCOUNT_STATUS}" != "REGISTRATION_SUCCESS" ]; do fi done -echo "Creating Timestream database..." >&2 +echo "Creating Timestream database..." aws timestream-write create-database \ --region ${REGION} \ --database-name ${TIMESTREAM_DB_NAME} | jq -r .Database.Arn @@ -323,27 +346,21 @@ aws iam attach-role-policy \ --policy-arn ${SERVICE_ROLE_POLICY_ARN} \ --role-name "${SERVICE_ROLE}" -if ((FLEET_SIZE==1)); then - echo "Deleting vehicle ${VEHICLE_NAME} if it already exists..." - aws iotfleetwise delete-vehicle \ - ${ENDPOINT_URL_OPTION} --region ${REGION} \ - --vehicle-name "${VEHICLE_NAME}" -else - echo "Deleting vehicle ${VEHICLE_NAME}-0..$((FLEET_SIZE-1)) if it already exists..." - for ((i=0; i<${FLEET_SIZE}; i+=${BATCH_SIZE})); do - for ((j=0; j<${BATCH_SIZE} && i+j<${FLEET_SIZE}; j++)); do - # This output group is run in a background process. Note that stderr is redirected to stream 3 and back, - # to print stderr from the output group, but not info about the background process. - { \ - aws iotfleetwise delete-vehicle \ - ${ENDPOINT_URL_OPTION} --region ${REGION} \ - --vehicle-name "${VEHICLE_NAME}-$((i+j))" \ - 2>&3 &} 3>&2 2>/dev/null - done - # Wait for all background processes to finish - wait +for ((i=0; i<${FLEET_SIZE}; i+=${BATCH_SIZE})); do + for ((j=0; j<${BATCH_SIZE} && i+j<${FLEET_SIZE}; j++)); do + vehicle=${VEHICLES[$((i+j))]} + echo "Deleting vehicle ${vehicle} if it already exists..." + # This output group is run in a background process. Note that stderr is redirected to stream 3 and back, + # to print stderr from the output group, but not info about the background process. + { \ + aws iotfleetwise delete-vehicle \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --vehicle-name "${vehicle}" \ + 2>&3 &} 3>&2 2>/dev/null done -fi + # Wait for all background processes to finish + wait +done if [ ${S3_UPLOAD} == true ]; then echo "S3 upload is enabled" @@ -381,6 +398,8 @@ if [ ${S3_UPLOAD} == true ]; then } EOF aws s3api put-bucket-policy --bucket $BUCKET_NAME --policy file://s3-bucket-policy.json + else + echo "Skipping S3 bucket policy. Since bucket already existed it needs to be manually configured." fi fi @@ -392,19 +411,13 @@ else DBC_NODES=`python3 dbc-to-nodes.py ${DBC_FILE}` fi -echo "Checking for existing signal catalog..." -SIGNAL_CATALOG_LIST=`aws iotfleetwise list-signal-catalogs \ - ${ENDPOINT_URL_OPTION} --region ${REGION}` -SIGNAL_CATALOG_COUNT=`echo ${SIGNAL_CATALOG_LIST} | jq '.summaries|length'` -# Currently only one signal catalog is supported by the service -if [ ${SIGNAL_CATALOG_COUNT} == 0 ]; then - echo "No existing signal catalog" - echo "Creating signal catalog with Vehicle node..." - SIGNAL_CATALOG_ARN=`aws iotfleetwise create-signal-catalog \ +if SIGNAL_CATALOG_ARN=`aws iotfleetwise create-signal-catalog \ ${ENDPOINT_URL_OPTION} --region ${REGION} \ --name ${NAME}-signal-catalog \ - --nodes "${VEHICLE_NODE}" | jq -r .arn` - echo ${SIGNAL_CATALOG_ARN} + --nodes "${VEHICLE_NODE}" 2>/dev/null | jq -r .arn`; then + + echo "Created new signal catalog: ${SIGNAL_CATALOG_ARN}" + CREATED_SIGNAL_CATALOG_NAME="${NAME}-signal-catalog" echo "Adding OBD signals to signal catalog..." aws iotfleetwise update-signal-catalog \ @@ -434,9 +447,12 @@ if [ ${SIGNAL_CATALOG_COUNT} == 0 ]; then }} ]' | jq -r .arn else + echo "Checking for existing signal catalogs..." + SIGNAL_CATALOG_LIST=`aws iotfleetwise list-signal-catalogs \ + ${ENDPOINT_URL_OPTION} --region ${REGION}` SIGNAL_CATALOG_NAME=`echo ${SIGNAL_CATALOG_LIST} | jq -r .summaries[0].name` SIGNAL_CATALOG_ARN=`echo ${SIGNAL_CATALOG_LIST} | jq -r .summaries[0].arn` - echo ${SIGNAL_CATALOG_ARN} + echo "Reusing existing signal catalog: ${SIGNAL_CATALOG_ARN}" echo "Updating Vehicle node in signal catalog..." if UPDATE_SIGNAL_CATALOG_STATUS=`aws iotfleetwise update-signal-catalog \ @@ -564,35 +580,25 @@ aws iotfleetwise update-decoder-manifest \ --name ${NAME}-decoder-manifest \ --status ACTIVE | jq -r .arn -if ((FLEET_SIZE==1)); then - echo "Creating vehicle ${VEHICLE_NAME}..." - aws iotfleetwise create-vehicle \ - ${ENDPOINT_URL_OPTION} --region ${REGION} \ - --decoder-manifest-arn ${DECODER_MANIFEST_ARN} \ - --association-behavior ValidateIotThingExists \ - --model-manifest-arn ${MODEL_MANIFEST_ARN} \ - --attributes '{"Vehicle.Color":"Red"}' \ - --vehicle-name "${VEHICLE_NAME}" | jq -r .arn -else - echo "Creating vehicle ${VEHICLE_NAME}-0..$((FLEET_SIZE-1))..." - for ((i=0; i<${FLEET_SIZE}; i+=${BATCH_SIZE})); do - for ((j=0; j<${BATCH_SIZE} && i+j<${FLEET_SIZE}; j++)); do - # This output group is run in a background process. Note that stderr is redirected to stream 3 and back, - # to print stderr from the output group, but not info about the background process. - { \ - aws iotfleetwise create-vehicle \ - ${ENDPOINT_URL_OPTION} --region ${REGION} \ - --decoder-manifest-arn ${DECODER_MANIFEST_ARN} \ - --association-behavior ValidateIotThingExists \ - --model-manifest-arn ${MODEL_MANIFEST_ARN} \ - --attributes '{"Vehicle.Color":"Red"}' \ - --vehicle-name "${VEHICLE_NAME}-$((i+j))" >/dev/null \ - 2>&3 &} 3>&2 2>/dev/null - done - # Wait for all background processes to finish - wait +for ((i=0; i<${FLEET_SIZE}; i+=${BATCH_SIZE})); do + for ((j=0; j<${BATCH_SIZE} && i+j<${FLEET_SIZE}; j++)); do + echo "Creating vehicle ${vehicle}..." + vehicle=${VEHICLES[$((i+j))]} + # This output group is run in a background process. Note that stderr is redirected to stream 3 and back, + # to print stderr from the output group, but not info about the background process. + { \ + aws iotfleetwise create-vehicle \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --decoder-manifest-arn ${DECODER_MANIFEST_ARN} \ + --association-behavior ValidateIotThingExists \ + --model-manifest-arn ${MODEL_MANIFEST_ARN} \ + --attributes '{"Vehicle.Color":"Red"}' \ + --vehicle-name "${vehicle}" >/dev/null \ + 2>&3 &} 3>&2 2>/dev/null done -fi + # Wait for all background processes to finish + wait +done echo "Creating fleet..." FLEET_ARN=`aws iotfleetwise create-fleet \ @@ -602,29 +608,22 @@ FLEET_ARN=`aws iotfleetwise create-fleet \ --signal-catalog-arn ${SIGNAL_CATALOG_ARN} | jq -r .arn` echo ${FLEET_ARN} -if ((FLEET_SIZE==1)); then - echo "Associating vehicle ${VEHICLE_NAME}..." - aws iotfleetwise associate-vehicle-fleet \ - ${ENDPOINT_URL_OPTION} --region ${REGION} \ - --fleet-id ${NAME}-fleet \ - --vehicle-name "${VEHICLE_NAME}" -else - echo "Associating vehicle ${VEHICLE_NAME}-0..$((FLEET_SIZE-1))..." - for ((i=0; i<${FLEET_SIZE}; i+=${BATCH_SIZE})); do - for ((j=0; j<${BATCH_SIZE} && i+j<${FLEET_SIZE}; j++)); do - # This output group is run in a background process. Note that stderr is redirected to stream 3 and back, - # to print stderr from the output group, but not info about the background process. - { \ - aws iotfleetwise associate-vehicle-fleet \ - ${ENDPOINT_URL_OPTION} --region ${REGION} \ - --fleet-id ${NAME}-fleet \ - --vehicle-name "${VEHICLE_NAME}-$((i+j))" \ - 2>&3 &} 3>&2 2>/dev/null - done - # Wait for all background processes to finish - wait +for ((i=0; i<${FLEET_SIZE}; i+=${BATCH_SIZE})); do + for ((j=0; j<${BATCH_SIZE} && i+j<${FLEET_SIZE}; j++)); do + vehicle=${VEHICLES[$((i+j))]} + echo "Associating vehicle ${vehicle}..." + # This output group is run in a background process. Note that stderr is redirected to stream 3 and back, + # to print stderr from the output group, but not info about the background process. + { \ + aws iotfleetwise associate-vehicle-fleet \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --fleet-id ${NAME}-fleet \ + --vehicle-name "${vehicle}" \ + 2>&3 &} 3>&2 2>/dev/null done -fi + # Wait for all background processes to finish + wait +done echo "Creating campaign from ${CAMPAIGN_FILE}..." CAMPAIGN=`cat ${CAMPAIGN_FILE} \ @@ -645,7 +644,7 @@ if [ ${S3_UPLOAD} == true ]; then | jq .targetArn=\"${FLEET_ARN}\"` aws iotfleetwise create-campaign \ ${ENDPOINT_URL_OPTION} --region ${REGION} \ - --cli-input-json "${CAMPAIGN}" --data-destination-configs "[{\"s3Config\":{\"bucketArn\":\"arn:aws:s3:::${BUCKET_NAME}\",\"prefix\":\"${NAME}-campaign-s3-${RANDOM_HASH}\",\"dataFormat\":\"JSON\",\"storageCompressionFormat\":\"NONE\"}}]"| jq -r .arn + --cli-input-json "${CAMPAIGN}" --data-destination-configs "[{\"s3Config\":{\"bucketArn\":\"arn:aws:s3:::${BUCKET_NAME}\",\"prefix\":\"${NAME}-campaign-s3-${S3_SUFFIX}\",\"dataFormat\":\"JSON\",\"storageCompressionFormat\":\"NONE\"}}]"| jq -r .arn approve_campaign ${NAME}-campaign-s3-json @@ -656,7 +655,7 @@ if [ ${S3_UPLOAD} == true ]; then | jq .targetArn=\"${FLEET_ARN}\"` aws iotfleetwise create-campaign \ ${ENDPOINT_URL_OPTION} --region ${REGION} \ - --cli-input-json "${CAMPAIGN}" --data-destination-configs "[{\"s3Config\":{\"bucketArn\":\"arn:aws:s3:::${BUCKET_NAME}\",\"prefix\":\"${NAME}-campaign-s3-${RANDOM_HASH}\",\"dataFormat\":\"PARQUET\",\"storageCompressionFormat\":\"NONE\"}}]"| jq -r .arn + --cli-input-json "${CAMPAIGN}" --data-destination-configs "[{\"s3Config\":{\"bucketArn\":\"arn:aws:s3:::${BUCKET_NAME}\",\"prefix\":\"${NAME}-campaign-s3-${S3_SUFFIX}\",\"dataFormat\":\"PARQUET\",\"storageCompressionFormat\":\"NONE\"}}]"| jq -r .arn approve_campaign ${NAME}-campaign-s3-parquet fi @@ -704,23 +703,19 @@ check_vehicle_healthy() { fi } -if ((FLEET_SIZE==1)); then - echo "Waiting until status of vehicle ${VEHICLE_NAME} is healthy..." - check_vehicle_healthy "${VEHICLE_NAME}" -else - echo "Waiting until status of vehicle ${VEHICLE_NAME}-0..$((FLEET_SIZE-1)) is healthy..." - for ((i=0; i<${FLEET_SIZE}; i+=${BATCH_SIZE})); do - for ((j=0; j<${BATCH_SIZE} && i+j<${FLEET_SIZE}; j++)); do - # This output group is run in a background process. Note that stderr is redirected to stream 3 and back, - # to print stderr from the output group, but not info about the background process. - { \ - check_vehicle_healthy "${VEHICLE_NAME}-$((i+j))" \ - 2>&3 &} 3>&2 2>/dev/null - done - # Wait for all background processes to finish - wait +for ((i=0; i<${FLEET_SIZE}; i+=${BATCH_SIZE})); do + for ((j=0; j<${BATCH_SIZE} && i+j<${FLEET_SIZE}; j++)); do + vehicle=${VEHICLES[$((i+j))]} + echo "Waiting until status of vehicle ${vehicle} is healthy..." + # This output group is run in a background process. Note that stderr is redirected to stream 3 and back, + # to print stderr from the output group, but not info about the background process. + { \ + check_vehicle_healthy "${vehicle}" \ + 2>&3 &} 3>&2 2>/dev/null done -fi + # Wait for all background processes to finish + wait +done DELAY=30 echo "Waiting ${DELAY} seconds for data to be collected..." @@ -729,24 +724,32 @@ sleep ${DELAY} echo "The DB Name is ${TIMESTREAM_DB_NAME}" echo "The DB Table is ${TIMESTREAM_TABLE_NAME}" -echo "Querying Timestream..." -aws timestream-query query \ - --region ${REGION} \ - --query-string "SELECT * FROM \"${TIMESTREAM_DB_NAME}\".\"${TIMESTREAM_TABLE_NAME}\" \ - WHERE vehicleName = '${VEHICLE_NAME}`if ((FLEET_SIZE>1)); then echo "-0"; fi`' \ - AND time between ago(1m) and now() ORDER BY time ASC" \ - > ${NAME}-timestream-result.json +for vehicle in ${VEHICLES[@]}; do + echo "Querying Timestream for vehicle ${vehicle}..." + aws timestream-query query \ + --region ${REGION} \ + --query-string "SELECT * FROM \"${TIMESTREAM_DB_NAME}\".\"${TIMESTREAM_TABLE_NAME}\" \ + WHERE vehicleName = '${vehicle}' \ + AND time between ago(1m) and now() ORDER BY time ASC" \ + > ${vehicle}-timestream-result.json +done if [ "${DBC_FILE}" == "" ]; then - echo "Converting to HTML..." - OUTPUT_FILE_HTML="${NAME}.html" - python3 timestream-to-html.py ${NAME}-timestream-result.json ${OUTPUT_FILE_HTML} + OUTPUT_FILES=() + for vehicle in ${VEHICLES[@]}; do + echo "Converting to HTML..." + OUTPUT_FILE_HTML="${vehicle}.html" + OUTPUT_FILES+=(${OUTPUT_FILE_HTML}) + python3 timestream-to-html.py ${vehicle}-timestream-result.json ${OUTPUT_FILE_HTML} + done echo "You can now view the collected data." echo "----------------------------------" echo "| Collected data in HTML format: |" echo "----------------------------------" - echo `pwd`/${OUTPUT_FILE_HTML} + for file in ${OUTPUT_FILES[@]}; do + echo $(pwd)/${file} + done fi if [ ${S3_UPLOAD} == true ]; then @@ -757,7 +760,7 @@ if [ ${S3_UPLOAD} == true ]; then if [ "${DBC_FILE}" == "" ]; then echo "Converting data from S3 to HTML..." OUTPUT_FILE_HTML="${NAME}.html" - python3 s3-to-html.py --bucket ${BUCKET_NAME} --prefix ${NAME}-campaign-s3-${RANDOM_HASH} --json-output-filename ${NAME}-s3-json-result.html --parquet-output-filename ${NAME}-s3-parquet-result.html + python3 s3-to-html.py --bucket ${BUCKET_NAME} --prefix ${NAME}-campaign-s3-${S3_SUFFIX} --json-output-filename ${NAME}-s3-json-result.html --parquet-output-filename ${NAME}-s3-parquet-result.html echo "You can now view the collected data." echo "-------------------------------------" @@ -766,12 +769,3 @@ if [ ${S3_UPLOAD} == true ]; then echo `pwd`/${NAME}-s3-json-result.html "and" `pwd`/${NAME}-s3-parquet-result.html fi fi - -if [ ${CLEAN_UP} == true ]; then - ./clean-up.sh \ - --vehicle-name ${VEHICLE_NAME} \ - --fleet-size ${FLEET_SIZE} \ - --timestamp ${TIMESTAMP} \ - ${ENDPOINT_URL_OPTION} \ - --region ${REGION} -fi diff --git a/tools/cloud/obd-nodes.json b/tools/cloud/obd-nodes.json index af691344..85be079f 100644 --- a/tools/cloud/obd-nodes.json +++ b/tools/cloud/obd-nodes.json @@ -488,28 +488,28 @@ "sensor": { "dataType": "DOUBLE", "fullyQualifiedName": "Vehicle.OBD.MaxEquivalenceRatio", - "description": "Maximum value for Fuel–Air equivalence ratio," + "description": "Maximum value for Fuel-Air equivalence ratio," } }, { "sensor": { "dataType": "DOUBLE", "fullyQualifiedName": "Vehicle.OBD.MaxOxygenSensorVoltage", - "description": "Maximum value for Fuel–Air equivalence ratio," + "description": "Maximum value for Fuel-Air equivalence ratio," } }, { "sensor": { "dataType": "DOUBLE", "fullyQualifiedName": "Vehicle.OBD.MaxOxygenSensorCurrent", - "description": "Maximum value for Fuel–Air equivalence ratio," + "description": "Maximum value for Fuel-Air equivalence ratio," } }, { "sensor": { "dataType": "DOUBLE", "fullyQualifiedName": "Vehicle.OBD.MaxIntakeMAP", - "description": "Maximum value for Fuel–Air equivalence ratio," + "description": "Maximum value for Fuel-Air equivalence ratio," } }, { diff --git a/tools/configure-fwe.sh b/tools/configure-fwe.sh index 19405663..88fc10c0 100755 --- a/tools/configure-fwe.sh +++ b/tools/configure-fwe.sh @@ -29,7 +29,7 @@ if [ -z "${ENDPOINT_URL+x}" ]; then ENDPOINT_URL="" fi if [ -z "${CAN_BUS0+x}" ]; then - CAN_BUS0="vcan0" + CAN_BUS0="" fi if [ -z "${LOG_LEVEL+x}" ]; then LOG_LEVEL="Info" @@ -43,6 +43,9 @@ fi if [ -z "${TOPIC_PREFIX+x}" ]; then TOPIC_PREFIX="\$aws/iotfleetwise/" fi +if [ -z "${CONNECTION_TYPE+x}" ]; then + CONNECTION_TYPE="iotCore" +fi parse_args() { while [ "$#" -gt 0 ]; do @@ -55,6 +58,10 @@ parse_args() { OUTPUT_CONFIG_FILE=$2 shift ;; + --connection-type) + CONNECTION_TYPE=$2 + shift + ;; --vehicle-name) VEHICLE_NAME=$2 shift @@ -101,19 +108,20 @@ parse_args() { ;; --help) echo "Usage: $0 [OPTION]" - echo " --input-config-file Input JSON config file" - echo " --output-config-file Output JSON config file" - echo " --vehicle-name Vehicle name" - echo " --endpoint-url IoT Core MQTT endpoint URL" - echo " --can-bus0 CAN bus 0, default: ${CAN_BUS0}" - echo " --certificate Certificate" - echo " --certificate-file Certificate file, default: ${CERTIFICATE_FILE}" - echo " --private-key Private key" - echo " --private-key-file Private key file, default: ${PRIVATE_KEY_FILE}" - echo " --persistency-path Persistency path, default: ${PERSISTENCY_PATH}" - echo " --topic-prefix IoT MQTT topic prefix, default: ${TOPIC_PREFIX}" - echo " --log-level Log level. Either: Off, Error, Warning, Info, Trace. Default: ${LOG_LEVEL}" - echo " --log-color Whether logs should be colored. Either: Auto, Yes, No. Default: ${LOG_COLOR}" + echo " --input-config-file Input JSON config file" + echo " --output-config-file Output JSON config file" + echo " --connection-type Connectivity connection type, default: ${CONNECTION_TYPE}" + echo " --vehicle-name Vehicle name" + echo " --endpoint-url IoT Core MQTT endpoint URL" + echo " --can-bus0 CAN bus 0, e.g. vcan0" + echo " --certificate Certificate" + echo " --certificate-file Certificate file, default: ${CERTIFICATE_FILE}" + echo " --private-key Private key" + echo " --private-key-file Private key file, default: ${PRIVATE_KEY_FILE}" + echo " --persistency-path Persistency path, default: ${PERSISTENCY_PATH}" + echo " --topic-prefix IoT MQTT topic prefix, default: ${TOPIC_PREFIX}" + echo " --log-level Log level. Either: Off, Error, Warning, Info, Trace. Default: ${LOG_LEVEL}" + echo " --log-color Whether logs should be colored. Either: Auto, Yes, No. Default: ${LOG_COLOR}" exit 0 ;; esac @@ -132,9 +140,11 @@ parse_args() { echo "Error: No Vehicle name specified" exit -1 fi - if [ "${ENDPOINT_URL}" == "" ]; then - echo "Error: No endpoint URL specified" - exit -1 + if [ "${CONNECTION_TYPE}" == "iotCore" ]; then + if [ "${ENDPOINT_URL}" == "" ]; then + echo "Error: No endpoint URL specified" + exit -1 + fi fi } @@ -147,27 +157,46 @@ if [ "`jq '.networkInterfaces[0].canInterface' ${INPUT_CONFIG_FILE}`" == "null" fi # Create the config file: -jq ".staticConfig.mqttConnection.endpointUrl=\"${ENDPOINT_URL}\"" ${INPUT_CONFIG_FILE} \ - | jq ".staticConfig.mqttConnection.clientId=\"${VEHICLE_NAME}\"" \ +OUTPUT_CONFIG=` \ + jq ".staticConfig.mqttConnection.clientId=\"${VEHICLE_NAME}\"" ${INPUT_CONFIG_FILE} \ | jq ".staticConfig.mqttConnection.collectionSchemeListTopic=\"${TOPIC_PREFIX}vehicles/${VEHICLE_NAME}/collection_schemes\"" \ | jq ".staticConfig.mqttConnection.decoderManifestTopic=\"${TOPIC_PREFIX}vehicles/${VEHICLE_NAME}/decoder_manifests\"" \ | jq ".staticConfig.mqttConnection.canDataTopic=\"${TOPIC_PREFIX}vehicles/${VEHICLE_NAME}/signals\"" \ | jq ".staticConfig.mqttConnection.checkinTopic=\"${TOPIC_PREFIX}vehicles/${VEHICLE_NAME}/checkins\"" \ - | if [[ $CERTIFICATE ]]; \ - then jq "del(.staticConfig.mqttConnection.certificateFilename)" | jq ".staticConfig.mqttConnection.certificate=\"${CERTIFICATE}\""; \ - else jq ".staticConfig.mqttConnection.certificateFilename=\"${CERTIFICATE_FILE}\""; \ - fi \ - | if [[ $PRIVATE_KEY ]]; \ - then jq "del(.staticConfig.mqttConnection.privateKeyFilename)" | jq ".staticConfig.mqttConnection.privateKey=\"${PRIVATE_KEY}\""; \ - else jq ".staticConfig.mqttConnection.privateKeyFilename=\"${PRIVATE_KEY_FILE}\""; \ - fi \ | jq ".staticConfig.internalParameters.systemWideLogLevel=\"${LOG_LEVEL}\"" \ | jq ".staticConfig.internalParameters.logColor=\"${LOG_COLOR}\"" \ | jq ".staticConfig.persistency.persistencyPath=\"${PERSISTENCY_PATH}\"" \ - | jq ".networkInterfaces[0].canInterface.interfaceName=\"${CAN_BUS0}\"" \ - | jq ".networkInterfaces[1].obdInterface.interfaceName=\"${CAN_BUS0}\"" \ - | jq ".networkInterfaces[1].obdInterface.pidRequestIntervalSeconds=5" \ - | jq ".networkInterfaces[1].obdInterface.dtcRequestIntervalSeconds=5" \ - | jq ".networkInterfaces[1].interfaceId=\"0\"" \ | jq ".staticConfig.publishToCloudParameters.collectionSchemeManagementCheckinIntervalMs=5000" \ - > ${OUTPUT_CONFIG_FILE} + | jq ".staticConfig.mqttConnection.connectionType=\"${CONNECTION_TYPE}\""` + +if [ "$CONNECTION_TYPE" == "iotCore" ]; then + OUTPUT_CONFIG=`echo "${OUTPUT_CONFIG}" \ + | jq ".staticConfig.mqttConnection.endpointUrl=\"${ENDPOINT_URL}\"" \ + | if [[ $CERTIFICATE ]]; \ + then jq "del(.staticConfig.mqttConnection.certificateFilename)" | jq ".staticConfig.mqttConnection.certificate=\"${CERTIFICATE}\""; \ + else jq ".staticConfig.mqttConnection.certificateFilename=\"${CERTIFICATE_FILE}\""; \ + fi \ + | if [[ $PRIVATE_KEY ]]; \ + then jq "del(.staticConfig.mqttConnection.privateKeyFilename)" | jq ".staticConfig.mqttConnection.privateKey=\"${PRIVATE_KEY}\""; \ + else jq ".staticConfig.mqttConnection.privateKeyFilename=\"${PRIVATE_KEY_FILE}\""; \ + fi` +elif [ "$CONNECTION_TYPE" == "iotGreengrassV2" ]; then + OUTPUT_CONFIG=`echo "${OUTPUT_CONFIG}" \ + | jq "del(.staticConfig.mqttConnection.endpointUrl)" \ + | jq "del(.staticConfig.mqttConnection.certificateFilename)" \ + | jq "del(.staticConfig.mqttConnection.privateKeyFilename)"` +else + echo "Error: Unknown connection type ${CONNECTION_TYPE}" +fi + +if [ "${CAN_BUS0}" != "" ]; then + OUTPUT_CONFIG=`echo "${OUTPUT_CONFIG}" | jq ".networkInterfaces[0].canInterface.interfaceName=\"${CAN_BUS0}\"" \ + | jq ".networkInterfaces[1].obdInterface.interfaceName=\"${CAN_BUS0}\"" \ + | jq ".networkInterfaces[1].obdInterface.pidRequestIntervalSeconds=5" \ + | jq ".networkInterfaces[1].obdInterface.dtcRequestIntervalSeconds=5" \ + | jq ".networkInterfaces[1].interfaceId=\"0\""` +else + OUTPUT_CONFIG=`echo "${OUTPUT_CONFIG}" | jq ".networkInterfaces=[]"` +fi + +echo "${OUTPUT_CONFIG}" > ${OUTPUT_CONFIG_FILE} diff --git a/tools/container/README.md b/tools/container/README.md index e18635b5..94ddf58b 100644 --- a/tools/container/README.md +++ b/tools/container/README.md @@ -1,7 +1,7 @@ -# Containerized Edge Agent +# Containerized FWE -A containerized version of the edge agent is built using Github Actions, and is available from AWS -ECR Public Gallery: https://gallery.ecr.aws/aws-iot-fleetwise-edge/aws-iot-fleetwise-edge +A containerized version of FWE is built using Github Actions, and is available from AWS ECR Public +Gallery: https://gallery.ecr.aws/aws-iot-fleetwise-edge/aws-iot-fleetwise-edge ## Running the Container diff --git a/tools/deploy/fwe@.service b/tools/deploy/fwe@.service index 83e543b9..d7a42f38 100644 --- a/tools/deploy/fwe@.service +++ b/tools/deploy/fwe@.service @@ -1,5 +1,5 @@ [Unit] -Description=AWS IoT FleetWise Edge #%i +Description=FWE #%i After=network-online.target setup-socketcan.service Wants=network-online.target setup-socketcan.service diff --git a/tools/deploy/start-and-enable-fwe.sh b/tools/deploy/start-and-enable-fwe.sh index 865c4d96..dc18d52d 100755 --- a/tools/deploy/start-and-enable-fwe.sh +++ b/tools/deploy/start-and-enable-fwe.sh @@ -2,14 +2,32 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 +set -e + # Start and enable the FWE service at startup: systemctl daemon-reload FLEET_SIZE=`ls -1 /etc/aws-iot-fleetwise/*.json | wc -l` -LAST_SERVICE=`expr ${FLEET_SIZE} - 1` +LAST_SERVICE=0 # expr returns error code when the expression result is 0, so default to 0 +if [ "${FLEET_SIZE}" != "1" ]; then + LAST_SERVICE=`expr ${FLEET_SIZE} - 1` +fi SERVICES="" for i in `seq 0 ${LAST_SERVICE}`; do - SERVICES="${SERVICES} fwe@$i" + SERVICES="${SERVICES} fwe@$i" done echo "Starting and enabling${SERVICES}" systemctl enable ${SERVICES} +START_TIME=$(date -u '+%Y-%m-%d %H:%M:%S') systemctl start ${SERVICES} + +SUCCESS_MESSAGE="Started successfully" +for SERVICE_NAME in ${SERVICES}; do + # sed will quit as soon as it finds the first match and this will make `journalctl -f` quit too + if timeout 5s bash -c "journalctl -u ${SERVICE_NAME} -f --no-tail --since \"${START_TIME}\" | sed '/${SUCCESS_MESSAGE}/ q' > /dev/null"; then + echo "${SERVICE_NAME} started successfully" + else + echo "${SERVICE_NAME} didn't start correctly. Please check the logs below:" + journalctl -u ${SERVICE_NAME} --since "${START_TIME}" + exit 1 + fi +done diff --git a/tools/greengrassV2/.gitignore b/tools/greengrassV2/.gitignore new file mode 100644 index 00000000..d4f588ed --- /dev/null +++ b/tools/greengrassV2/.gitignore @@ -0,0 +1 @@ +artifacts/ diff --git a/tools/greengrassV2/README.md b/tools/greengrassV2/README.md new file mode 100644 index 00000000..8707908a --- /dev/null +++ b/tools/greengrassV2/README.md @@ -0,0 +1,75 @@ +# FWE with AWS IoT Greengrass v2 IPC + +This project is an integration of the Reference Implementation for AWS IoT FleetWise ("FWE") with +the AWS IoT Greengrass v2 IPC client. It enables FWE to be deployed as a Greengrass v2 Component and +allows it to connect to AWS IoT Core via the Greengrass IPC mechanism. Authentication with IoT Core +is handled by Greengrass, simplifying the configuration of IoT FleetWise. + +## Compile the FWE with Greengrass feature + +enable the compile flag: -DFWE_FEATURE_GREENGRASSV2=On + +``` +mkdir build && cd build && cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DFWE_STATIC_LINK=On \ + -DBUILD_TESTING=Off \ + -DFWE_FEATURE_GREENGRASSV2=On \ + .. \ + && make install -j`nproc` && cd .. +``` + +## Install IoT Greengrass core in local machine + +Install IoT Greengrass v2 core on the development machine, by following these instructions: +https://docs.aws.amazon.com/greengrass/v2/developerguide/getting-started.html#install-greengrass-v2 + +## Configure the FWE as the deployment of GG + +Run the following command to create the configuration file for IoT FleetWise. Replace +with the thing name that you used above to register the IoT Greengrass device: + +``` +mkdir -p tools/greengrassV2/artifacts/com.amazon.aws.IoTFleetWise/1.0.0/ +./tools/configure-fwe.sh \ + --input-config-file ./configuration/static-config.json \ + --output-config-file tools/greengrassV2/artifacts/com.amazon.aws.IoTFleetWise/1.0.0/config.json \ + --vehicle-name +``` + +## Deployment + +### Cloud Deployment + +Deploy FWE via Greengrass deployment console. By creating a component using the example recipe like +[recipes/com.amazon.aws.IoTFleetWise-2.0.0.json](recipes/com.amazon.aws.IoTFleetWise-2.0.0.json). + +### Local Deployment + +Create the persistency path for FWE: + +``` +mkdir -p /var/aws-iot-fleetwise +``` + +Make a local deployment of IoT FleetWise as a Greengrass Component by running the following: + +``` +cp build/lib/aws-iot-fleetwise-edge/src/executionmanagement/aws-iot-fleetwise-edge \ + tools/greengrassV2/artifacts/com.amazon.aws.IoTFleetWise/1.0.0/ +sudo /greengrass/v2/bin/greengrass-cli deployment create \ + --recipeDir tools/greengrassV2/recipes \ + --artifactDir tools/greengrassV2/artifacts \ + --merge "com.amazon.aws.IoTFleetWise=1.0.0" +``` + +View the logs of running component as follows: + +``` +sudo tail -f /greengrass/v2/logs/com.amazon.aws.IoTFleetWise.log +``` + +## Testing + +To test IoT FleetWise you can now follow the instructions in the Edge Agent Developer Guide to run +the cloud demo script. diff --git a/tools/greengrassV2/recipes/com.amazon.aws.IoTFleetWise-1.0.0.json b/tools/greengrassV2/recipes/com.amazon.aws.IoTFleetWise-1.0.0.json new file mode 100644 index 00000000..6aa5fa42 --- /dev/null +++ b/tools/greengrassV2/recipes/com.amazon.aws.IoTFleetWise-1.0.0.json @@ -0,0 +1,31 @@ +{ + "RecipeFormatVersion": "2020-01-25", + "ComponentName": "com.amazon.aws.IoTFleetWise", + "ComponentVersion": "1.0.0", + "ComponentDescription": "Reference Implementation for AWS IoT FleetWise", + "ComponentPublisher": "Amazon", + "ComponentConfiguration": { + "DefaultConfiguration": { + "Message": "world", + "accessControl": { + "aws.greengrass.ipc.mqttproxy": { + "com.amazon.aws.IoTFleetWise:mqttproxy:1": { + "policyDescription": "Allows access to publish/subscribe to all topics.", + "operations": ["aws.greengrass#PublishToIoTCore", "aws.greengrass#SubscribeToIoTCore"], + "resources": ["*"] + } + } + } + } + }, + "Manifests": [ + { + "Platform": { + "os": "linux" + }, + "Lifecycle": { + "Run": "{artifacts:path}/aws-iot-fleetwise-edge {artifacts:path}/config.json" + } + } + ] +} diff --git a/tools/greengrassV2/recipes/com.amazon.aws.IoTFleetWise-2.0.0.json b/tools/greengrassV2/recipes/com.amazon.aws.IoTFleetWise-2.0.0.json new file mode 100644 index 00000000..25b25def --- /dev/null +++ b/tools/greengrassV2/recipes/com.amazon.aws.IoTFleetWise-2.0.0.json @@ -0,0 +1,48 @@ +{ + "RecipeFormatVersion": "2020-01-25", + "ComponentName": "com.amazon.aws.IoTFleetWise", + "ComponentVersion": "2.0.0", + "ComponentDescription": "Reference Implementation for AWS IoT FleetWise", + "ComponentPublisher": "Amazon", + "ComponentConfiguration": { + "DefaultConfiguration": { + "Message": "world", + "accessControl": { + "aws.greengrass.ipc.mqttproxy": { + "com.amazon.aws.IoTFleetWise:mqttproxy:1": { + "policyDescription": "Allows access to publish/subscribe to all topics.", + "operations": ["aws.greengrass#PublishToIoTCore", "aws.greengrass#SubscribeToIoTCore"], + "resources": ["*"] + } + } + } + } + }, + "Manifests": [ + { + "Platform": { + "os": "linux" + }, + "Lifecycle": { + "Run": "{artifacts:path}/aws-iot-fleetwise-edge {artifacts:path}/config.json", + "RequiresPrivilege": "true" + }, + "Artifacts": [ + { + "Uri": "s3://URI-TO-FWE-BINARY/com.amazon.aws.IoTFleetWise/2.0.0/aws-iot-fleetwise-edge", + "Permission": { + "Read": "ALL", + "Execute": "ALL" + } + }, + { + "Uri": "s3://URI-TO-FWE-STATIC-CONFIG/com.amazon.aws.IoTFleetWise/2.0.0/config.json", + "Permission": { + "Read": "ALL", + "Execute": "ALL" + } + } + ] + } + ] +} diff --git a/tools/install-cansim.sh b/tools/install-cansim.sh index 0aad1147..e64cadcd 100755 --- a/tools/install-cansim.sh +++ b/tools/install-cansim.sh @@ -43,3 +43,16 @@ cp tools/cansim/cansim@.service /lib/systemd/system/ systemctl daemon-reload systemctl enable `seq -f "cansim@%.0f" 0 $((BUS_COUNT-1))` systemctl start `seq -f "cansim@%.0f" 0 $((BUS_COUNT-1))` + +# Check that the simulators started correctly and are generating data +for SEQUENCE in `seq 0 $((BUS_COUNT-1))`; do + DEVICE=vcan${SEQUENCE} + echo Checking if device ${DEVICE} is receiving data; + NUM_MESSAGES=$(candump -n 10 -T 5000 ${DEVICE} | wc -l) + echo "Received ${NUM_MESSAGES} messages"; + if [ ${NUM_MESSAGES} -ne 10 ]; then + echo "Received fewer messages than expected. Most likely the CAN simulator #${SEQUENCE} didn't start correctly. See the log below:"; + journalctl -u cansim@${SEQUENCE}.service | tail -n100 + exit 1 + fi +done diff --git a/tools/install-deps-cross-android.sh b/tools/install-deps-cross-android.sh index eb84b281..4a3be38a 100755 --- a/tools/install-deps-cross-android.sh +++ b/tools/install-deps-cross-android.sh @@ -9,10 +9,17 @@ source ${SCRIPT_DIR}/install-deps-versions.sh USE_CACHE="true" SDK_PREFIX="/usr/local/android_sdk" +ARCHS="x86_64:x86_64-linux-android:android-x86_64 \ + armeabi-v7a:armv7a-linux-androideabi:android-arm \ + arm64-v8a:aarch64-linux-android:android-arm64" parse_args() { while [ "$#" -gt 0 ]; do case $1 in + --archs) + ARCHS=$2 + shift + ;; --native-prefix) NATIVE_PREFIX="$2" USE_CACHE="false" @@ -20,6 +27,7 @@ parse_args() { ;; --help) echo "Usage: $0 [OPTION]" + echo " --archs Space separated list of archs in the format ::" echo " --native-prefix Native install prefix" exit 0 ;; @@ -223,7 +231,20 @@ install_deps() { rm -rf deps-cross-android } -if ! ${USE_CACHE} || [ ! -d /usr/local/aarch64-linux-android ] || [ ! -d /usr/local/armv7a-linux-androideabi ] || [ ! -d ${NATIVE_PREFIX} ]; then - install_deps "armeabi-v7a" "armv7a-linux-androideabi" "android-arm" - install_deps "arm64-v8a" "aarch64-linux-android" "android-arm64" +ARCH_NOT_INSTALLED="false" +for ARCH in ${ARCHS}; do + HOST_PLATFORM=`echo $ARCH | cut -d ':' -f2` + if [ ! -d /usr/local/${HOST_PLATFORM} ]; then + ARCH_NOT_INSTALLED="true" + break + fi +done + +if ! ${USE_CACHE} || ${ARCH_NOT_INSTALLED} || [ ! -d ${NATIVE_PREFIX} ]; then + for ARCH in ${ARCHS}; do + TARGET_ARCH=`echo $ARCH | cut -d ':' -f1` + HOST_PLATFORM=`echo $ARCH | cut -d ':' -f2` + SSL_TARGET=`echo $ARCH | cut -d ':' -f3` + install_deps ${TARGET_ARCH} ${HOST_PLATFORM} ${SSL_TARGET} + done fi diff --git a/tools/install-deps-cross-arm64.sh b/tools/install-deps-cross-arm64.sh index 4aa4a994..24e58f71 100755 --- a/tools/install-deps-cross-arm64.sh +++ b/tools/install-deps-cross-arm64.sh @@ -7,14 +7,14 @@ set -euo pipefail SCRIPT_DIR=$(dirname $(realpath "$0")) source ${SCRIPT_DIR}/install-deps-versions.sh -WITH_CAMERA_SUPPORT="false" USE_CACHE="true" +WITH_GREENGRASSV2_SUPPORT="false" parse_args() { while [ "$#" -gt 0 ]; do case $1 in - --with-camera-support) - WITH_CAMERA_SUPPORT="true" + --with-greengrassv2-support) + WITH_GREENGRASSV2_SUPPORT="true" ;; --native-prefix) NATIVE_PREFIX="$2" @@ -23,8 +23,8 @@ parse_args() { ;; --help) echo "Usage: $0 [OPTION]" - echo " --with-camera-support Install dependencies for camera support" - echo " --native-prefix Native install prefix" + echo " --with-greengrassv2-support Install dependencies for Greengrass V2" + echo " --native-prefix Native install prefix" exit 0 ;; esac @@ -61,13 +61,6 @@ apt install -y \ wget \ zlib1g-dev:arm64 -if ${WITH_CAMERA_SUPPORT}; then - apt install -y \ - default-jre \ - libasio-dev \ - qemu-user-binfmt -fi - if [ ! -f /usr/include/linux/can/isotp.h ]; then git clone https://github.com/hartkopp/can-isotp.git cd can-isotp @@ -142,71 +135,21 @@ if ! ${USE_CACHE} || [ ! -d /usr/local/aarch64-linux-gnu ] || [ ! -d ${NATIVE_PR make install -j`nproc` cd ../.. - # AWS IoT FleetWise Edge camera support requires Fast-DDS and its dependencies: - if ${WITH_CAMERA_SUPPORT}; then - git clone -b ${VERSION_TINYXML2} https://github.com/leethomason/tinyxml2.git - cd tinyxml2 - mkdir build && cd build - cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -DBUILD_STATIC_LIBS=ON \ - -DBUILD_TESTS=OFF \ - -DCMAKE_POSITION_INDEPENDENT_CODE=On \ - -DCMAKE_TOOLCHAIN_FILE=/usr/local/aarch64-linux-gnu/lib/cmake/arm64-toolchain.cmake \ - -DCMAKE_INSTALL_PREFIX=/usr/local/aarch64-linux-gnu \ - .. - make install -j`nproc` - cd ../.. - - git clone -b ${VERSION_FOONATHAN_MEMORY_VENDOR} https://github.com/eProsima/foonathan_memory_vendor.git - cd foonathan_memory_vendor - mkdir build && cd build - cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -Dextra_cmake_args="-DCMAKE_CROSSCOMPILING_EMULATOR=qemu-aarch64" \ - -DCMAKE_TOOLCHAIN_FILE=/usr/local/aarch64-linux-gnu/lib/cmake/arm64-toolchain.cmake \ - -DCMAKE_INSTALL_PREFIX=/usr/local/aarch64-linux-gnu \ - .. - make install -j`nproc` - cd ../.. - - git clone -b ${VERSION_FAST_CDR} https://github.com/eProsima/Fast-CDR.git - cd Fast-CDR + if ${WITH_GREENGRASSV2_SUPPORT}; then + git clone -b ${VERSION_AWS_IOT_DEVICE_SDK_CPP_V2} --recursive https://github.com/aws/aws-iot-device-sdk-cpp-v2.git + cd aws-iot-device-sdk-cpp-v2 mkdir build && cd build cmake \ - -DCMAKE_BUILD_TYPE=Release \ -DBUILD_SHARED_LIBS=OFF \ - -DCMAKE_POSITION_INDEPENDENT_CODE=On \ + -DBUILD_DEPS=ON \ + -DBUILD_TESTING=OFF \ + -DUSE_OPENSSL=ON \ + -DBUILD_ONLY='greengrass_ipc' \ -DCMAKE_TOOLCHAIN_FILE=/usr/local/aarch64-linux-gnu/lib/cmake/arm64-toolchain.cmake \ -DCMAKE_INSTALL_PREFIX=/usr/local/aarch64-linux-gnu \ .. make install -j`nproc` cd ../.. - - git clone -b ${VERSION_FAST_DDS} https://github.com/eProsima/Fast-DDS.git - cd Fast-DDS - mkdir build && cd build - cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -DCOMPILE_TOOLS=OFF \ - -DCMAKE_POSITION_INDEPENDENT_CODE=On \ - -DCMAKE_CXX_FLAGS="-DUSE_FOONATHAN_NODE_SIZES=1" \ - -DCMAKE_TOOLCHAIN_FILE=/usr/local/aarch64-linux-gnu/lib/cmake/arm64-toolchain.cmake \ - -DCMAKE_INSTALL_PREFIX=/usr/local/aarch64-linux-gnu \ - .. - make install -j`nproc` - cd ../.. - - git clone -b ${VERSION_FAST_DDS_GEN} --recursive https://github.com/eProsima/Fast-DDS-Gen.git - cd Fast-DDS-Gen - ./gradlew assemble - mkdir -p ${NATIVE_PREFIX}/share/fastddsgen/java - cp share/fastddsgen/java/fastddsgen.jar ${NATIVE_PREFIX}/share/fastddsgen/java - cp scripts/fastddsgen ${NATIVE_PREFIX}/bin - cd .. fi cd .. diff --git a/tools/install-deps-cross-armhf.sh b/tools/install-deps-cross-armhf.sh index 76b83e7b..af07b531 100755 --- a/tools/install-deps-cross-armhf.sh +++ b/tools/install-deps-cross-armhf.sh @@ -7,14 +7,14 @@ set -euo pipefail SCRIPT_DIR=$(dirname $(realpath "$0")) source ${SCRIPT_DIR}/install-deps-versions.sh -WITH_CAMERA_SUPPORT="false" USE_CACHE="true" +WITH_GREENGRASSV2_SUPPORT="false" parse_args() { while [ "$#" -gt 0 ]; do case $1 in - --with-camera-support) - WITH_CAMERA_SUPPORT="true" + --with-greengrassv2-support) + WITH_GREENGRASSV2_SUPPORT="true" ;; --native-prefix) NATIVE_PREFIX="$2" @@ -23,8 +23,8 @@ parse_args() { ;; --help) echo "Usage: $0 [OPTION]" - echo " --with-camera-support Install dependencies for camera support" - echo " --native-prefix Native install prefix" + echo " --with-greengrassv2-support Install dependencies for Greengrass V2" + echo " --native-prefix Native install prefix" exit 0 ;; esac @@ -61,13 +61,6 @@ apt install -y \ wget \ zlib1g-dev:armhf -if ${WITH_CAMERA_SUPPORT}; then - apt install -y \ - default-jre \ - libasio-dev \ - qemu-user-binfmt -fi - if [ ! -f /usr/include/linux/can/isotp.h ]; then git clone https://github.com/hartkopp/can-isotp.git cd can-isotp @@ -142,71 +135,21 @@ if ! ${USE_CACHE} || [ ! -d /usr/local/arm-linux-gnueabihf ] || [ ! -d ${NATIVE_ make install -j`nproc` cd ../.. - # AWS IoT FleetWise Edge camera support requires Fast-DDS and its dependencies: - if ${WITH_CAMERA_SUPPORT}; then - git clone -b ${VERSION_TINYXML2} https://github.com/leethomason/tinyxml2.git - cd tinyxml2 - mkdir build && cd build - cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -DBUILD_STATIC_LIBS=ON \ - -DBUILD_TESTS=OFF \ - -DCMAKE_POSITION_INDEPENDENT_CODE=On \ - -DCMAKE_TOOLCHAIN_FILE=/usr/local/arm-linux-gnueabihf/lib/cmake/armhf-toolchain.cmake \ - -DCMAKE_INSTALL_PREFIX=/usr/local/arm-linux-gnueabihf \ - .. - make install -j`nproc` - cd ../.. - - git clone -b ${VERSION_FOONATHAN_MEMORY_VENDOR} https://github.com/eProsima/foonathan_memory_vendor.git - cd foonathan_memory_vendor - mkdir build && cd build - cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -Dextra_cmake_args="-DCMAKE_CROSSCOMPILING_EMULATOR=qemu-arm" \ - -DCMAKE_TOOLCHAIN_FILE=/usr/local/arm-linux-gnueabihf/lib/cmake/armhf-toolchain.cmake \ - -DCMAKE_INSTALL_PREFIX=/usr/local/arm-linux-gnueabihf \ - .. - make install -j`nproc` - cd ../.. - - git clone -b ${VERSION_FAST_CDR} https://github.com/eProsima/Fast-CDR.git - cd Fast-CDR + if ${WITH_GREENGRASSV2_SUPPORT}; then + git clone -b ${VERSION_AWS_IOT_DEVICE_SDK_CPP_V2} --recursive https://github.com/aws/aws-iot-device-sdk-cpp-v2.git + cd aws-iot-device-sdk-cpp-v2 mkdir build && cd build cmake \ - -DCMAKE_BUILD_TYPE=Release \ -DBUILD_SHARED_LIBS=OFF \ - -DCMAKE_POSITION_INDEPENDENT_CODE=On \ + -DBUILD_DEPS=ON \ + -DBUILD_TESTING=OFF \ + -DUSE_OPENSSL=ON \ + -DBUILD_ONLY='greengrass_ipc' \ -DCMAKE_TOOLCHAIN_FILE=/usr/local/arm-linux-gnueabihf/lib/cmake/armhf-toolchain.cmake \ -DCMAKE_INSTALL_PREFIX=/usr/local/arm-linux-gnueabihf \ .. make install -j`nproc` cd ../.. - - git clone -b ${VERSION_FAST_DDS} https://github.com/eProsima/Fast-DDS.git - cd Fast-DDS - mkdir build && cd build - cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -DCOMPILE_TOOLS=OFF \ - -DCMAKE_POSITION_INDEPENDENT_CODE=On \ - -DCMAKE_CXX_FLAGS="-DUSE_FOONATHAN_NODE_SIZES=1" \ - -DCMAKE_TOOLCHAIN_FILE=/usr/local/arm-linux-gnueabihf/lib/cmake/armhf-toolchain.cmake \ - -DCMAKE_INSTALL_PREFIX=/usr/local/arm-linux-gnueabihf \ - .. - make install -j`nproc` - cd ../.. - - git clone -b ${VERSION_FAST_DDS_GEN} --recursive https://github.com/eProsima/Fast-DDS-Gen.git - cd Fast-DDS-Gen - ./gradlew assemble - mkdir -p ${NATIVE_PREFIX}/share/fastddsgen/java - cp share/fastddsgen/java/fastddsgen.jar ${NATIVE_PREFIX}/share/fastddsgen/java - cp scripts/fastddsgen ${NATIVE_PREFIX}/bin - cd .. fi cd .. diff --git a/tools/install-deps-native.sh b/tools/install-deps-native.sh index d918ef56..47fde284 100755 --- a/tools/install-deps-native.sh +++ b/tools/install-deps-native.sh @@ -7,15 +7,15 @@ set -euo pipefail SCRIPT_DIR=$(dirname $(realpath "$0")) source ${SCRIPT_DIR}/install-deps-versions.sh -WITH_CAMERA_SUPPORT="false" USE_CACHE="true" INSTALL_BUILD_TIME_DEPS="true" +WITH_GREENGRASSV2_SUPPORT="false" parse_args() { while [ "$#" -gt 0 ]; do case $1 in - --with-camera-support) - WITH_CAMERA_SUPPORT="true" + --with-greengrassv2-support) + WITH_GREENGRASSV2_SUPPORT="true" ;; --prefix) PREFIX="$2" @@ -27,9 +27,9 @@ parse_args() { ;; --help) echo "Usage: $0 [OPTION]" - echo " --with-camera-support Install dependencies for camera support" - echo " --runtime-only Install only runtime dependencies" - echo " --prefix Install prefix" + echo " --with-greengrassv2-support Install dependencies for Greengrass V2" + echo " --runtime-only Install only runtime dependencies" + echo " --prefix Install prefix" exit 0 ;; esac @@ -62,12 +62,6 @@ if ${INSTALL_BUILD_TIME_DEPS}; then update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-10 1000 fi -if ${INSTALL_BUILD_TIME_DEPS} && ${WITH_CAMERA_SUPPORT}; then - apt install -y \ - default-jre \ - libasio-dev -fi - if ${INSTALL_BUILD_TIME_DEPS} && [ ! -f /usr/include/linux/can/isotp.h ]; then git clone https://github.com/hartkopp/can-isotp.git cd can-isotp @@ -132,6 +126,21 @@ if ${INSTALL_BUILD_TIME_DEPS} && ( ! ${USE_CACHE} || [ ! -d ${PREFIX} ] ); then make install -j`nproc` cd ../.. + if ${WITH_GREENGRASSV2_SUPPORT}; then + git clone -b ${VERSION_AWS_IOT_DEVICE_SDK_CPP_V2} --recursive https://github.com/aws/aws-iot-device-sdk-cpp-v2.git + cd aws-iot-device-sdk-cpp-v2 + mkdir build && cd build + cmake \ + -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_DEPS=ON \ + -DBUILD_TESTING=OFF \ + -DUSE_OPENSSL=ON \ + -DBUILD_ONLY='greengrass_ipc' \ + .. + make install -j`nproc` + cd ../.. + fi + git clone -b ${VERSION_GOOGLE_TEST} https://github.com/google/googletest.git cd googletest mkdir build && cd build @@ -154,66 +163,6 @@ if ${INSTALL_BUILD_TIME_DEPS} && ( ! ${USE_CACHE} || [ ! -d ${PREFIX} ] ); then make install -j`nproc` cd ../.. - # AWS IoT FleetWise Edge camera support requires Fast-DDS and its dependencies: - if ${WITH_CAMERA_SUPPORT}; then - git clone -b ${VERSION_TINYXML2} https://github.com/leethomason/tinyxml2.git - cd tinyxml2 - mkdir build && cd build - cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -DBUILD_STATIC_LIBS=ON \ - -DBUILD_TESTS=OFF \ - -DCMAKE_POSITION_INDEPENDENT_CODE=On \ - -DCMAKE_INSTALL_PREFIX=${PREFIX} \ - .. - make install -j`nproc` - cd ../.. - - git clone -b ${VERSION_FOONATHAN_MEMORY_VENDOR} https://github.com/eProsima/foonathan_memory_vendor.git - cd foonathan_memory_vendor - mkdir build && cd build - cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -DCMAKE_INSTALL_PREFIX=${PREFIX} \ - .. - make install -j`nproc` - cd ../.. - - git clone -b ${VERSION_FAST_CDR} https://github.com/eProsima/Fast-CDR.git - cd Fast-CDR - mkdir build && cd build - cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -DCMAKE_INSTALL_PREFIX=${PREFIX} \ - .. - make install -j`nproc` - cd ../.. - - git clone -b ${VERSION_FAST_DDS} https://github.com/eProsima/Fast-DDS.git - cd Fast-DDS - mkdir build && cd build - cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -DCOMPILE_TOOLS=OFF \ - -DCMAKE_CXX_FLAGS="-DUSE_FOONATHAN_NODE_SIZES=1" \ - -DCMAKE_INSTALL_PREFIX=${PREFIX} \ - .. - make install -j`nproc` - cd ../.. - - git clone -b ${VERSION_FAST_DDS_GEN} --recursive https://github.com/eProsima/Fast-DDS-Gen.git - cd Fast-DDS-Gen - ./gradlew assemble - mkdir -p ${PREFIX}/share/fastddsgen/java - cp share/fastddsgen/java/fastddsgen.jar ${PREFIX}/share/fastddsgen/java - cp scripts/fastddsgen ${PREFIX}/bin - cd .. - fi - cd .. rm -rf deps-native fi diff --git a/tools/install-deps-versions.sh b/tools/install-deps-versions.sh index 853f7ce1..2bec9ce6 100755 --- a/tools/install-deps-versions.sh +++ b/tools/install-deps-versions.sh @@ -4,12 +4,9 @@ export VERSION_PROTOBUF="3.21.12" export VERSION_PROTOBUF_RELEASE="v21.12" export VERSION_CURL="7.86.0" export VERSION_CURL_RELEASE="curl-7_86_0" -export VERSION_AWS_SDK_CPP="1.11.94" -export VERSION_TINYXML2="6.0.0" -export VERSION_FOONATHAN_MEMORY_VENDOR="v1.1.0" -export VERSION_FAST_CDR="v1.0.21" -export VERSION_FAST_DDS="v2.3.4" -export VERSION_FAST_DDS_GEN="v2.0.1" +export VERSION_AWS_SDK_CPP="1.11.111" +# MAKE SURE THE CRT VERSON IN AWS_SDK_CPP IS THE SAME AS IN AWS_IOT_SDK_CPP +export VERSION_AWS_IOT_DEVICE_SDK_CPP_V2="v1.22.0" export VERSION_GOOGLE_TEST="release-1.10.0" export VERSION_GOOGLE_BENCHMARK="v1.6.1" export VERSION_ANDROID_CMDLINE_TOOLS="9123335" diff --git a/tools/provision.sh b/tools/provision.sh index 034f5598..8c88bae8 100755 --- a/tools/provision.sh +++ b/tools/provision.sh @@ -2,7 +2,7 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -set -eo pipefail +set -euo pipefail ENDPOINT_URL="" ENDPOINT_URL_OPTION="" @@ -57,15 +57,15 @@ parse_args() { ;; --help) echo "Usage: $0 [OPTION]" - echo " --vehicle-name Vehicle name" - echo " --certificate-pem-outfile Certificate output file, default: ${CERT_OUT_FILE}" - echo " --private-key-outfile Private key output file, default: ${PRIVATE_KEY_OUT_FILE}" - echo " --endpoint-url-outfile Endpoint URL for MQTT connections output file" - echo " --vehicle-name-outfile Vehicle name output file" - echo " --thing-policy-outfile Thing policy output file" - echo " --endpoint-url The endpoint URL used for AWS CLI calls" - echo " --region The region used for AWS CLI calls, default: ${REGION}" - echo " --only-clean-up Clean up resources created by previous runs of this script" + echo " --vehicle-name Vehicle name" + echo " --certificate-pem-outfile Certificate output file, default: ${CERT_OUT_FILE}" + echo " --private-key-outfile Private key output file, default: ${PRIVATE_KEY_OUT_FILE}" + echo " --endpoint-url-outfile Endpoint URL for MQTT connections output file" + echo " --vehicle-name-outfile Vehicle name output file" + echo " --thing-policy-outfile Thing policy output file" + echo " --endpoint-url The endpoint URL used for AWS CLI calls" + echo " --region The region used for AWS CLI calls, default: ${REGION}" + echo " --only-clean-up Clean up resources created by previous runs of this script" exit 0 ;; esac @@ -194,7 +194,7 @@ aws iot attach-thing-principal \ --thing-name ${VEHICLE_NAME} \ --principal ${CERT_ARN} -echo "Getting endpoint..." +echo "Getting MQTT endpoint..." ENDPOINT=`aws iot describe-endpoint \ ${ENDPOINT_URL_OPTION} \ --region ${REGION} \