diff --git a/.github/template/fwe-build/action.yml b/.github/template/fwe-build/action.yml index b642ec35..39714f90 100644 --- a/.github/template/fwe-build/action.yml +++ b/.github/template/fwe-build/action.yml @@ -10,6 +10,8 @@ inputs: required: true cache-paths: required: true + dist-files: + required: true runs: using: "composite" @@ -40,9 +42,9 @@ runs: shell: bash run: | ./tools/build-fwe-${{ inputs.build-arch }}.sh - ./tools/build-dist.sh build/src/executionmanagement/aws-iot-fleetwise-edge + ./tools/build-dist.sh ${{ inputs.dist-files }} # If the output file changes, make sure to update the upload-asset job below - mv build/aws-iot-fleetwise-edge.tar.gz aws-iot-fleetwise-edge-${{ inputs.upload-arch }}.tar.gz + mv build/aws-iot-fleetwise-edge.tar.gz build/aws-iot-fleetwise-edge-${{ inputs.upload-arch }}.tar.gz - name: unit-test shell: bash @@ -57,6 +59,7 @@ runs: name: build-${{ inputs.upload-arch }} path: | build/src/executionmanagement/aws-iot-fleetwise-edge + build/aws-iot-fleetwise-edge-${{ inputs.upload-arch }}.tar.gz build/Testing/Temporary/ build/**/report-*.xml @@ -65,4 +68,4 @@ runs: shell: bash run: | RELEASE_VERSION="${GITHUB_REF/refs\/tags\//}" - gh release upload ${RELEASE_VERSION} aws-iot-fleetwise-edge-${{ inputs.upload-arch }}.tar.gz + gh release upload ${RELEASE_VERSION} build/aws-iot-fleetwise-edge-${{ inputs.upload-arch }}.tar.gz diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cdb5b689..fb5f562f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,7 @@ jobs: - run: sudo apt-get install expect - run: pip install pre-commit - run: unbuffer pre-commit run --all-files + build-amd64: runs-on: ubuntu-20.04 steps: @@ -37,6 +38,7 @@ jobs: build-arch: "native" upload-arch: "amd64" cache-paths: /usr/local/x86_64-linux-gnu + dist-files: build/src/executionmanagement/aws-iot-fleetwise-edge:. build-arm64: runs-on: ubuntu-20.04 @@ -47,6 +49,7 @@ jobs: build-arch: "cross-arm64" upload-arch: "arm64" cache-paths: /usr/local/aarch64-linux-gnu:/usr/local/x86_64-linux-gnu + dist-files: build/src/executionmanagement/aws-iot-fleetwise-edge:. build-armhf: runs-on: ubuntu-20.04 @@ -57,6 +60,66 @@ jobs: build-arch: "cross-armhf" upload-arch: "armhf" cache-paths: /usr/local/arm-linux-gnueabihf:/usr/local/x86_64-linux-gnu + dist-files: build/src/executionmanagement/aws-iot-fleetwise-edge:. + + build-android: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - uses: "./.github/template/fwe-build" + with: + build-arch: "cross-android" + upload-arch: "android" + cache-paths: /usr/local/aarch64-linux-android:/usr/local/armv7a-linux-androideabi:/usr/local/x86_64-linux-gnu + dist-files: + 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 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 \ + https://fleetwise-app.automotive.iot.aws.dev/ic_launcher.webp + fi + cd tools/android-app + 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' + 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' + run: | + source tools/install-deps-versions.sh + SIGNING_INFO=`aws secretsmanager get-secret-value --region us-east-1 --secret-id AndroidAppKeyStore | jq -r .SecretString` + echo "${SIGNING_INFO}" | jq -r .KeyStore | base64 --decode > ~/android-signing.jks + KEYSTORE_PASSWORD=`echo "${SIGNING_INFO}" | jq -r .KeyStorePassword` + cd tools/android-app + /usr/local/android_sdk/build-tools/${VERSION_ANDROID_BUILD_TOOLS}/zipalign -v -p 4 \ + app/build/outputs/apk/release/app-release-unsigned.apk \ + app/build/outputs/apk/release/app-release-unsigned-aligned.apk + /usr/local/android_sdk/build-tools/${VERSION_ANDROID_BUILD_TOOLS}/apksigner sign \ + --ks ~/android-signing.jks \ + --ks-pass "pass:${KEYSTORE_PASSWORD}" \ + --out app/build/outputs/apk/release/aws-iot-fleetwise-edge.apk \ + 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' + uses: actions/upload-artifact@v3 + with: + name: build-android-app + path: | + tools/android-app/app/build/outputs/apk/release/aws-iot-fleetwise-edge.apk + - name: upload-asset + if: github.repository == 'aws/aws-iot-fleetwise-edge' && github.ref_type == 'tag' + run: | + RELEASE_VERSION="${GITHUB_REF/refs\/tags\//}" + gh release upload ${RELEASE_VERSION} tools/android-app/app/build/outputs/apk/release/aws-iot-fleetwise-edge.apk build-docker: runs-on: ubuntu-20.04 @@ -76,7 +139,7 @@ jobs: find linux -name aws-iot-fleetwise-edge -exec chmod +x {} \; - uses: docker/setup-qemu-action@v2 - uses: docker/setup-buildx-action@v2 - - uses: aws-actions/configure-aws-credentials@v1-node16 + - uses: aws-actions/configure-aws-credentials@v2 if: github.ref_type == 'tag' with: role-to-assume: ${{ secrets.PUBLIC_ECR_PUSH_ROLE }} diff --git a/CHANGELOG.md b/CHANGELOG.md index d21beb49..90f2c95a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Change Log +## v1.0.6 (2023-06-12) + +Features: + +- Add Android support, including shared library and demonstration app. + +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. +- 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. +- Add support for in-process ingestion of external GPS, CAN and OBD PID values when FWE is compiled + as a shared library. +- Fix compiler warnings for armhf build. +- Update Protobuf to v3.21.12, AWS C++ SDK to v1.11.94. + ## v1.0.5 (2023-05-11) Bugfixes: diff --git a/CMakeLists.txt b/CMakeLists.txt index b30f85d6..9bd6a5fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10.2) -project(iotfleetwise VERSION 1.0.5) +project(iotfleetwise VERSION 1.0.6) # AWS IoT FleetWise Edge uses C++14 for compatibility reasons with # Automotive middlewares ( Adaptive AUTOSAR, ROS2) @@ -21,15 +21,23 @@ option(FWE_CODE_COVERAGE "Enables code coverage" OFF) option(FWE_VALGRIND "Enable valgrind tests" OFF) option(FWE_BUILD_DOC "Build documentation" ON) option(FWE_STRIP_SYMBOLS "Strips symbols from output binaries" OFF) -option(FWE_FEATURE_CAMERA "Enable Camera Data Collection feature" OFF) option(FWE_TEST_CLANG_TIDY "Add clang-tidy test" ON) option(FWE_TEST_FAKETIME "Enable tests that use the faketime library" OFF) +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_EXAMPLE_IWAVEGPS "Include the IWave GPS example for a custom data source" OFF) -if(FWE_EXAMPLE_IWAVEGPS) - add_compile_options("-DFWE_EXAMPLE_IWAVEGPS") +option(FWE_FEATURE_CAMERA "Enable Camera Data Collection feature" 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_BUILD_ANDROID_SHARED_LIBRARY "Build the android shared library" OFF) +if(FWE_FEATURE_IWAVE_GPS) + add_compile_options("-DFWE_FEATURE_IWAVE_GPS") +endif() +if(FWE_FEATURE_EXTERNAL_GPS) + add_compile_options("-DFWE_FEATURE_EXTERNAL_GPS") endif() # Define the default build type diff --git a/README.md b/README.md index 6029c4b8..22648caa 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # 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) or the +> [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 @@ -10,7 +11,7 @@ Agent Reference Implementation for AWS IoT FleetWise (the “Reference Implement 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 +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 @@ -70,7 +71,7 @@ where they can deploy their Edge Agent binary. Possible options include (if pres and [Renesas R-Car S4](https://www.renesas.com/jp/en/products/automotive-products/automotive-system-chips-socs/rtp8a779f0askb0sp2s-r-car-s4-reference-boardspider) 2. Vehicle Head-Unit -3. Vehicle’s High Performance Computer +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 @@ -109,13 +110,13 @@ See [SECURITY](./SECURITY.md) for more information Edge Agent Reference Implementation for AWS IoT FleetWise depends on the following open source libraries. Refer to the corresponding links for more information. -- [AWS SDK for C++: v1.11.46](https://github.com/aws/aws-sdk-cpp) +- [AWS SDK for C++: v1.11.94](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) - [GoogleTest: v1.10.0](https://github.com/google/googletest) - [Google Benchmark: v1.6.1](https://github.com/google/benchmark) -- [Protobuf: v3.21.7](https://github.com/protocolbuffers/protobuf) +- [Protobuf: v3.21.12](https://github.com/protocolbuffers/protobuf) - [Boost: v1.71.1](https://github.com/boostorg/boost) - [JsonCpp: v1.9.5](https://github.com/open-source-parsers/jsoncpp) - [Snappy: v1.1.8](https://github.com/google/snappy) diff --git a/THIRD-PARTY-LICENSES b/THIRD-PARTY-LICENSES index 60b0d6ed..96f0703c 100644 --- a/THIRD-PARTY-LICENSES +++ b/THIRD-PARTY-LICENSES @@ -1,6 +1,6 @@ AWS IoT FleetWise Edge includes the following third-party software/licensing: -** AWS SDK for C++: v1.11.46 - https://github.com/aws/aws-sdk-cpp +** 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 @@ -213,7 +213,7 @@ AWS IoT FleetWise Edge includes the following third-party software/licensing: ---------------- ** GoogleTest: v1.10.0 - https://github.com/google/googletest -** Protobuf: v3.21.7 - https://github.com/protocolbuffers/protobuf +** Protobuf: v3.21.12 - https://github.com/protocolbuffers/protobuf Copyright 2008, Google Inc. All rights reserved. diff --git a/cmake/compiler_gcc.cmake b/cmake/compiler_gcc.cmake index dd2c998c..00888731 100644 --- a/cmake/compiler_gcc.cmake +++ b/cmake/compiler_gcc.cmake @@ -1,4 +1,3 @@ -# -Werror will be eventually added when target toolchain is decided add_compile_options(-Wconversion -Wall -Wextra -pedantic -ffunction-sections -fdata-sections) link_libraries( -Wl,--gc-sections # Remove all unreferenced sections @@ -16,6 +15,12 @@ if(FWE_CODE_COVERAGE) link_libraries("-lgcov" "--coverage") endif() +# Only add -Werror when explicitly asked to avoid compilation errors when someone tries a compiler +# different from the ones we use on CI. +if(FWE_WERROR) + add_compile_options(-Werror) +endif() + # optimization -O3 is added by default by CMake when build type is RELEASE add_compile_options( $<$:-O0> # Debug: Optimize for debugging 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 5bc303f2..afde39f6 100644 --- a/docs/dev-guide/edge-agent-dev-guide-nxp-s32g.md +++ b/docs/dev-guide/edge-agent-dev-guide-nxp-s32g.md @@ -65,16 +65,16 @@ required by AWS IoT FleetWise Edge Agent, an updated version of the `canutils` p ## Flash the SD-Card Image 1. Download and install Balena Etcher _on your local machine_: https://www.balena.io/etcher/ -1. Run Balena Etcher, choose ‘Select image’ and select the compressed SD-card image +1. Run Balena Etcher, choose 'Select image' and select the compressed SD-card image `fsl-image-ubuntu-s32g274ardb2.sdcard.gz` -1. Insert the SD-card supplied with the S32G-VNP-RDB2 into your local machine’s SD-card reader -1. Choose ‘Select target’ and select the SD-card -1. Choose ‘Flash!’ +1. Insert the SD-card supplied with the S32G-VNP-RDB2 into your local machine's SD-card reader +1. Choose 'Select target' and select the SD-card +1. Choose 'Flash!' ## Specify Initial Board Configuration -1. Insert the SD-card into the S32G-VNP-RDB2’s SD-card slot. -1. Connect the S32G-VNP-RDB2’s power supply. +1. Insert the SD-card into the S32G-VNP-RDB2's SD-card slot. +1. Connect the S32G-VNP-RDB2's power supply. 1. Connect an Ethernet cable from port P3A of the S32G-VNP-RDB2 to the internet router. ![](./images/s32g_golden_box.png) @@ -82,11 +82,11 @@ required by AWS IoT FleetWise Edge Agent, an updated version of the `canutils` p 1. Connect your local machine to the internet router, for example via WiFi or via Ethernet. 1. Connect to the S32G-VNP-RDB2 via SSH, entering password `bluebox`: `ssh bluebox@ubuntu-s32g274ardb2` - 1. If you can’t connect using the hostname `ubuntu-s32g274ardb2`, you will need to connect to the + 1. If you can't connect using the hostname `ubuntu-s32g274ardb2`, you will need to connect to the administration webpage of the internet router to obtain the IP address assigned to the S32G-VNP-RDB2. Use this IP address in place of `ubuntu-s32g274ardb2` used throughout this guide. -1. Once connected via SSH, check the board’s internet connection by running: `ping amazon.com`. +1. Once connected via SSH, check the board's internet connection by running: `ping amazon.com`. There should be 0% packet loss. ## Provision AWS IoT Credentials @@ -156,7 +156,7 @@ mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ ## Collect OBD Data -1. Run the following _on the development machine_ to deploy a ‘heartbeat’ campaign that periodically +1. Run the following _on the development machine_ to deploy a 'heartbeat' campaign that periodically collects OBD data: ```bash 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 6fd3c41f..7b624c54 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 @@ -62,7 +62,7 @@ on the Ubuntu variant of the Renesas Linux BSP version 5.10.41. ## Specify Initial Board Configuration -1. Insert the micro SD-card into the R-Car S4 Spider board’s micro SD-card slot. +1. Insert the micro SD-card into the R-Car S4 Spider board's micro SD-card slot. 1. Connect an Ethernet cable. 1. Connect develop machine to R-Car S4 Spider board USB port. @@ -95,7 +95,7 @@ on the Ubuntu variant of the Renesas Linux BSP version 5.10.41. 1. Connect to the R-Car S4 Spider board via SSH, entering password `rcar`: `ssh rcar@` -1. Once connected via SSH, check the board’s internet connection by running: `ping amazon.com`. +1. Once connected via SSH, check the board's internet connection by running: `ping amazon.com`. There should be 0% packet loss. ## Provision AWS IoT Credentials @@ -168,7 +168,7 @@ mkdir -p ~/aws-iot-fleetwise-deploy && cd ~/aws-iot-fleetwise-deploy \ ## Collect OBD Data -1. Run the following _on the development machine_ to deploy a ‘heartbeat’ campaign that periodically +1. Run the following _on the development machine_ to deploy a 'heartbeat' campaign that periodically collects OBD data: ```bash diff --git a/docs/dev-guide/edge-agent-dev-guide.md b/docs/dev-guide/edge-agent-dev-guide.md index ae02ce64..e7d0b1c1 100644 --- a/docs/dev-guide/edge-agent-dev-guide.md +++ b/docs/dev-guide/edge-agent-dev-guide.md @@ -19,7 +19,7 @@ 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 +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 @@ -80,10 +80,10 @@ instance. 1. (Optional) You can increase the number of simulated vehicles by updating the `FleetSize` parameter. You can also specify the region IoT Things are created in by updating the `IoTCoreRegion` parameter. -1. Select the checkbox next to _‘I acknowledge that AWS CloudFormation might create IAM resources - with custom names.’_ +1. Select the checkbox next to _'I acknowledge that AWS CloudFormation might create IAM resources + with custom names.'_ 1. Choose **Create stack**. -1. Wait until the status of the Stack is ‘CREATE_COMPLETE’, this will take approximately 10 minutes. +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 @@ -269,8 +269,8 @@ launch an AWS EC2 Graviton (arm64) instance. Pricing for EC2 can be found, 1. Do not include the file suffix `.pem`. 1. If you do not have an SSH key pair, you will need to create one and download the corresponding `.pem` file. Be sure to update the file permissions: `chmod 400 ` -1. **Select the checkbox** next to _‘I acknowledge that AWS CloudFormation might create IAM - resources with custom names.’_ +1. **Select the checkbox** next to _'I acknowledge that AWS CloudFormation might create IAM + resources with custom names.'_ 1. Choose **Create stack**. 1. Wait until the status of the Stack is **CREATE_COMPLETE**; this can take up to five minutes. 1. Select the **Outputs** tab, copy the EC2 IP address, and connect via SSH from your local machine @@ -347,7 +347,7 @@ 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 + 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 @@ -470,14 +470,14 @@ collect data from it. ![](./images/collected_data_plot.png) -1. Run the following _on the development machine_ to deploy a ‘heartbeat’ campaign that collects OBD +1. Run the following _on the development machine_ to deploy a 'heartbeat' campaign that collects OBD data from the vehicle. Repeat the process above to view the collected data. ```bash ./demo.sh --vehicle-name fwdemo-ec2 --campaign-file campaign-obd-heartbeat.json ``` - Similarly, if you chose to deploy the ‘heartbeat’ campaign that collects OBD data from an AWS IoT + Similarly, if you chose to deploy the 'heartbeat' campaign that collects OBD data from an AWS IoT thing created in in Europe (Frankfurt), you must configure `--region`: ```bash @@ -492,7 +492,7 @@ collect data from it. ./demo.sh --vehicle-name fwdemo-ec2 --dbc-file --campaign-file ``` - Similarly, if you chose to deploy the ‘heartbeat’ campaign that collects OBD data from an AWS IoT + Similarly, if you chose to deploy the 'heartbeat' campaign that collects OBD data from an AWS IoT thing created in in Europe (Frankfurt), you must configure `--region`: ```bash @@ -540,9 +540,6 @@ Agent software. - [Best practices and recommendation](#best-practices-and-recommendation) - [Supported platforms](#supported-platforms) - [Disclaimer](#disclaimer) -- [Appendix](#appendix) - - [Decoder manifest example](#decoder-manifest-example) - - [Campaign example](#campaign-example) ## Terminology @@ -594,7 +591,7 @@ AWS IoT FleetWise enables you to create campaigns that can be deployed to a flee 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 +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 @@ -722,7 +719,7 @@ 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 Off-board 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** @@ -807,164 +804,15 @@ The device software sends two artifacts with the Cloud services: 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. - -```protobuf -syntax = "proto3"; -message Checkin { - /* - * List of document ARNs the Edge Agent software currently - * has enacted including collection schemes (both idle and active) - * and decoder manifest. - */ - repeated string document_arns = 1; - /* - * Timestamp of when check in was generated in milliseconds since - * the Unix epoch - */ - uint64 timestamp_ms_epoch = 2; -} -``` +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: - -```protobuf -syntax = "proto3"; -message VehicleData { - - /* - * Amazon Resource Name of the campaign that triggered the collection of the - * data in this message - */ - string campaign_arn = 1; - - /* - * Amazon Resource Name of the decoder manifest used to decode the signals - * in this message - */ - string decoder_arn = 2; - - /* - * A unique ID that FWE generates each time a Scheme condition is triggered. - */ - uint32 collection_event_id = 3; - - /* - * The absolute timestamp in milliseconds since Unix Epoch of when - * the event happened. - */ - uint64 collection_event_time_ms_epoch = 4; - - /* - * Captured signals, which includes both OBD PIDs and signals decoded - * from normal CAN traffic - */ - repeated CapturedSignal captured_signals = 5; - - /* - * Captured DTC Information if specified by the collection Scheme - */ - DtcData dtc_data = 6; - - /* - * Captured raw CAN frames if specified by the Scheme - */ - repeated CanFrame can_frames = 7; - - /* - * Captured Geohash which reflect which geohash tile the vehicle is currently - * located. - */ - Geohash geohash = 8; -} - -/* - * Represents all signals for CAN and OBDII signals - */ -message CapturedSignal { - - /* - * Milliseconds relative to the event_time_ms_epoch of when the signal - * arrived on the bus - */ - sint64 relative_time_ms = 1; - - /* - * The signal id of the physical value of a signal that was captured. This maps - * directly to signal IDs provided in the collection schemes and decoder - * manifest. Signals can come from normal CAN traffic or OBD-II PIDs. In the - * case of OBD-II PIDs, Signal ID will only map to one of those signals, as per - * the decoder manifest. - */ - uint32 signal_id = 2; - - /* - * Datatypes of physical signal values. This can be extended to add other - * dataypes. - */ - oneof SignalValue { - - /* - * A double value of a decoded physical value. For Alpha, PID data will be - * calculated using a PID formula and directly cast to a double. - */ - double double_value = 3; - } -} - -/* - * A raw CAN2.0 A or B frame - */ -message CanFrame { - - /* - * Milliseconds relative to the event_time_ms_epoch. Can be negative or positive. - */ - sint64 relative_time_ms = 1; - - /* - * CAN Message Arbitration ID - */ - uint32 message_id = 2; - - /* - * VSS node_id of the CAN node as per the decoder manifest. - */ - uint32 node_id = 3; - - /* - * Array of bytes from the CAN Frame - */ - bytes byte_values = 4; -} - -message DtcData { - - /* - * Milliseconds relative to the event_time_ms_epoch. Can be negative or positive. - */ - sint64 relative_time_ms = 1; - - /* - * Strings of active DTC codes - */ - repeated string active_dtc_codes = 2; -} - -message Geohash { - - /* - * Geohash in string format. It's encoded in base 32 format. - * Currently we always upload the maximum resolution of geohash. - * The string is always 9 characters long. - */ - string geohash_string = 1; -} -``` +software 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 @@ -973,626 +821,15 @@ two artifacts: **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: - -```protobuf -syntax = "proto3"; - -message DecoderManifest { - - /* - * Amazon Resource Name and version of the decoder manifest - */ - string arn = 1; - - /* - * List of signals that are sent and received on the CAN BUS in the vehicle - */ - repeated CANSignal can_signals = 2; - - /* - * List of OBDII-PID signals and corresponding decoding rules - */ - repeated OBDPIDSignal obd_pid_signals = 3; -} - -message CANSignal { - - /* - * Unique integer identifier of the signal generated by Cloud Designer - */ - uint32 signal_id = 1; - - /* - * Interface ID for CAN network interface this signal is found on. - * The CAN network interface details are provided as a part of the edge static - * configuration file. - */ - string interface_id = 2; - - /* - * CAN Message Id - */ - uint32 message_id = 3; - - /* - * True when signal is encoded in Big Endian - */ - bool is_big_endian = 4; - - /* - * True when signal is signed - */ - bool is_signed = 5; - - /* - * Start bit position of the signal in the message - */ - uint32 start_bit = 6; - - /* - * physical_signal_value = raw_value * factor + offset - */ - double offset = 7; - - /* - * physical_signal_value = raw_value * factor + offset - */ - double factor = 8; - - /* - * Length of the CAN signal - */ - uint32 length = 9; -} - -/* - * This is the OBDII-PID signal decoding rule. One OBDII-PID could contain - * multiple signals. Below section is the decoder rule per signal, not per PID - */ -message OBDPIDSignal { - - /* - * Unique numeric identifier for the OBDII-PID signal - */ - uint32 signal_id = 1; - - /* - * Interface ID for CAN network interface this signal is found on. - * The CAN network interface details are provided as a part of the Edge Agent - * static configuration file. - */ - string interface_id = 2; - - /* - * Length of the PID response. Note this is not the signal byte length as PID - * might contain multiple signals - */ - uint32 pid_response_length = 3; - - /* - * OBDII-PID Service Mode for the signal in decimal - */ - uint32 service_mode = 4; - - /* - * OBD request PID in decimal - */ - uint32 pid = 5; - - /* - * scaling to decode OBD from raw bytes to double value - * e.g. A * 0.0125 - 40. scaling is 0.01 - */ - double scaling = 6; - - /* - * offset to decode OBD from raw bytes to double value - * e.g. A * 0.0125 - 40. offset is -40.0 - */ - double offset = 7; - - /* - * the start byte order (starting from 0th) for this signal in its PID query - * response e.g. PID 0x14 contains two signals. SHRFT is the second byte. Its - * startByte is 1 - */ - uint32 start_byte = 8; - - /* - * number of bytes for this signal in its PID query response - * e.g. PID 0x14 contains two signals. SHRFT is one byte. Its byteLength is 1 - */ - uint32 byte_length = 9; - - /* - * Right shift on bits to decode this signal from raw bytes. Note the bit - * manipulation is only performed when byteLength is 1. e.g. Boost Pressure B - * Control Status is bit 2, 3 on byte J. The right shift shall be two For - * non-bitmask signals, the right shift shall always be 0 - */ - uint32 bit_right_shift = 10; - - /* - * bit Mask Length to be applied to decode this signal from raw byte. Note the - * bit manipulation is only performed when byteLength is 1. e.g. Boost - * Pressure B Control Status is bit 2, 3 on byte J. The bit Mask Length would - * be 2 For non-bitmask signals, the bit Mask Length shall always be 8. - */ - uint32 bit_mask_length = 11; -} -``` +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. - -```protobuf -message CollectionSchemes { - - /* - * List of collectionSchemes. On receipt of this, Edge Agent will discard - * all collectionSchemes it currently has and enact these. - */ - repeated CollectionScheme collection_schemes = 1; - - /* - * Timestamp of when the collectionScheme list was created. - */ - uint64 timestamp_ms_epoch = 2; -} - -/* - * A definition of an individual collectionScheme containing what/when/how - * to send vehicle data to cloud. A collectionScheme can be condition based, - * where data is sent whenever a condition evaluates to true, or it - * can be time based, where data is sent up at periodic intervals. - */ -message CollectionScheme { - - /* - * Amazon Resource Name of the campaign this collectionScheme is part of - */ - string campaign_arn = 1; - - /* - * Amazon Resource Name of the required decoder manifest for this - * collectionScheme - */ - string decoder_manifest_arn = 2; - - /* - * When collectionScheme should start in milliseconds since the Unix epoch - */ - uint64 start_time_ms_epoch = 3; - - /* - * When collectionScheme should expire in milliseconds since the Unix epoch. - * This collectionScheme expiration date is meant to serve as an end - * date for a collectionScheme so it does not keep running forever in the case - * that the vehicle permanently loses internet connection to the cloud - */ - uint64 expiry_time_ms_epoch = 4; - - /* - * A collectionScheme type containing attributes that are specific to that - * collectionScheme type. Currently support time based (such as a heartbeat) - * and condition based collectionSchemes. - */ - oneof collection_scheme_type { - TimeBasedCollectionScheme time_based_collection_scheme = 5; - ConditionBasedCollectionScheme condition_based_collection_scheme = 6; - } - - /* - * This specifies how much time to spend collecting data after a condition - * evaluates to true. When after_duration_ms elapses whatever data - * collected up to that point ( if any was present on the vehicle ) is sent - * to the cloud. - */ - uint32 after_duration_ms = 7; - - /* - * All active DTCs including the time they were first seen active will - * be sent when the collectionScheme triggers. - */ - bool include_active_dtcs = 8; - - /* - * List of signal ids to collect or have attribute(s) required by a condition - * function node - */ - repeated SignalInformation signal_information = 9; - - /* - * List of Raw CAN Frame(s) to be collected and sent to cloud - */ - repeated RawCanFrame raw_can_frames_to_collect = 10; - - /* - * When true all data will be written to persistant storage when vehicle - * doesn't not have an internet connection - */ - bool persist_all_collected_data = 11; - - /* - * When true, collected data will be compressed and then sent to cloud. - */ - bool compress_collected_data = 12; - - /* - * An integer between describing the priority for the data collection. - * CollectionSchemes with low priority numbers will have higher priority - * and will be processed first. - */ - uint32 priority = 13; - - Probabilities probabilities = 14; - - /* - * Image Data to collect as part of this collectionScheme. - */ - repeated ImageData image_data = 15; -} - -message Probabilities{ - /* - * Double between 0 and 1 giving the probability after the condition is met - * and the minimum interval is over that the message should be actually be - * sent out 0 send never, 1: send always. A uniform random number (0-1) is - * generated before sending the data to cloud and compared to this - * probability. If lower then data will be sent out. The minimum interval will - * start again even when the random number decides to not sent out the data. - * It is both useable for condition and time based collectionSchemes. - */ - double probability_to_send = 1; -} - -/* - * Contains time based specific parameters necessary for time based - * collectionSchemes such as a heartbeat. - */ -message TimeBasedCollectionScheme { - - /* - * Time in milliseconds that will be interval of a time based collectionScheme - * if is_time_based_collection_scheme is set to true. This is not used if - * is_time_based_collection_scheme is set false. - */ - uint32 time_based_collection_scheme_period_ms = 1; -} - -/* - * Contains condition based specific attributes necessary for condition based - * collectionSchemes - */ -message ConditionBasedCollectionScheme { - - /* - * The minimum time in milliseconds required to elapse between conditions - * that evaluate to truefor data to be sent to the cloud. - */ - uint32 condition_minimum_interval_ms = 1; - - /* - * The version number associated with the event condition language used in the - * abstract syntax tree. We are starting at 0 for alpha and we will increment - * as we add features - */ - uint32 condition_language_version = 2; - - /* - * Root condition node for the Abstract Syntax Tree. - */ - ConditionNode condition_tree = 3; - - /* - * Edge Agent can monitor the previous state of a condition and use this - * information to allow the customer to set a trigger mode similar to an - * oscillascope trigger. - */ - enum ConditionTriggerMode { - - /* - * Condition will evaluate to true regardless of previous state - */ - TRIGGER_ALWAYS = 0; - - /* - * Condition will evaluate to true only when it previously evaulated to false - */ - TRIGGER_ONLY_ON_RISING_EDGE = 1; - - } - - /* - * A triggering mode can be applied to the condition to take in account the - * previous state of the condition. - */ - ConditionTriggerMode condition_trigger_mode = 4; -} - -/* - * This message contains information of signals that are to be collected and sent to - * cloud, or are part of the condition logic and require attribute information. - */ -message SignalInformation { - - /* - * Unique identifier of a Signal. Maps directly to a signal defined in the - * decoder manifest. - * Signal can also be an OBD PID. - */ - uint32 signal_id = 1; - - /* - * The size of the ring buffer that will contain the data points for this signal - */ - uint32 sample_buffer_size = 2; - - /* - * Minimum time period in milliseconds that must elapse between collecting - * samples. Samples arriving faster than this period will be dropped. - * A value of 0 will collect samples as fast - * as they arrive. - */ - uint32 minimum_sample_period_ms = 3; - - /* - * The size of a fixed window in milliseconds which will be used by aggregate - * condition functions to calculate min/max/avg etc. - */ - uint32 fixed_window_period_ms = 4; - - /* - * When true, this signal will not be collected and sent to cloud. It will only - * be used in the condition logic with its associated fixed_window_period_ms. - * Default is false. - */ - bool condition_only_signal = 5; -} - -/* - * A node of the condition Abstract Syntax Tree - */ -message ConditionNode { - - /* - * Each Abstract Syntax Tree node can be one of the following types - */ - oneof node { - /* - * An operator node can perform an operation or comparisons on its child - * node(s) - */ - NodeOperator node_operator = 1; - - /* - * Function node is a self-contained module that accomplish a specific task. - */ - NodeFunction node_function = 2; - - /* - * A node containing a floating point constant which can be used as a - * child node to operator nodes. - */ - double node_double_value = 3; - - /* - * A node containing a signal id, whose value will be evaluated every - * time that signal is received on the vehicle network bus. - */ - uint32 node_signal_id = 4; - - /* - * A node containing a boolean constant which can be used as a child node - * to an operator node. - */ - bool node_boolean_value = 5; - } - - /* - * Operator node types contain one or two children. If they are unary operator - * type nodes, only the left child will be used - */ - message NodeOperator{ - - /* - * Left child node - */ - ConditionNode left_child = 1; - - /* - * Right child node - */ - ConditionNode right_child = 2; - - /* - * Operator type used in this node - */ - Operator operator = 3; - - /* - * Enum of an operator which can be an binary or unary operator - */ - enum Operator { - - /* - * COMPARE operators return a bool and their children must return a - * double - */ - COMPARE_SMALLER = 0; - COMPARE_BIGGER = 1; - COMPARE_SMALLER_EQUAL = 2; - COMPARE_BIGGER_EQUAL = 3; - COMPARE_EQUAL = 4; - COMPARE_NOT_EQUAL = 5; - - /* - * LOGICAL operators return a bool and their children must return a bool - */ - LOGICAL_AND = 6; - LOGICAL_OR = 7; - LOGICAL_NOT = 8; // Unary operator that will only have a left child. - - /* - * ARITHMETIC operators return a double and their children must return a - * double - * - * - ARITHMETIC_PLUS = 9; - ARITHMETIC_MINUS = 10; - ARITHMETIC_MULTIPLY = 11; - ARITHMETIC_DIVIDE = 12; - } - } - - /* - * Function node is a self-contained module that accomplish a specific task. - * It takes inputs provided here and output based on specific logic - */ - message NodeFunction{ - - /* - * The function node could be one of the following funtion types. - */ - oneof functionType { - - /* - * A Window function node will sample a signal for the duration specifed - * by fixed_window_period_ms and then run an aggregation funcion over the - * samples and evaluate to a double. - */ - WindowFunction window_function = 1; - - /* - * Geohash function Node that evaluates whether Edge Agent has changed - * Geohash - * It returns true if the Geohash has changed at given precision. - * Otherwise return false - */ - GeohashFunction geohash_function = 2; - } - - /* - * Geohash function evaluates whether Edge Agent has changed Geohash at given - * precision. It will firstly calculate Geohash with latitude and longitude - * It returns true if the Geohash has changed at given precision. - * Otherwise return false - */ - message GeohashFunction{ - - /* - * signal id for latitude - */ - uint32 latitude_signal_id = 1; - - /* - * signal id for longitude - */ - uint32 longitude_signal_id = 2; - - /* - * The geohash precision for dynamic data collection - * Note geohash precision is defined as the length of hash characters - * (base 32 encoding). Longer hash will have higher precision than - * shorter hash. - */ - uint32 geohash_precision = 3; - - /* - * The unit for decoded latitude / longitude signal. GPS Signal might - * be decoded into different unit according to the DBC file. - */ - GPSUnitType gps_unit = 4; - - /* - * The unit type for decoded latitude / longitude signal. This list - * might be extended in future to accommodate different vehicle models. - */ - enum GPSUnitType { - DECIMAL_DEGREE = 0; - MICROARCSECOND = 1; - MILLIARCSECOND = 2; - ARCSECOND = 3; - } - } - - /* - * Function node that will evaluate a function on a signal_id within a - * fixed window - */ - message WindowFunction{ - - /* - * signal id of value to run a function on. The fixed_window_period_ms - * associated signal Information will be used. - */ - uint32 signal_id = 1; - - /* - * Function used over fixed window to evaluate value of physical value - * of signal_id - */ - WindowType window_type = 2; - - /* - * Function to be used to aggregate data in a fixed window - */ - enum WindowType { - - /* - * LAST_WINDOW is the most recently evaluated fixed window - */ - LAST_WINDOW_MIN = 0; - LAST_WINDOW_MAX = 1; - LAST_WINDOW_AVG = 2; - /* - * PREV_LAST_WINDOW is the fixed window previous to LAST_WINDOW - */ - PREV_LAST_WINDOW_MIN = 3; - PREV_LAST_WINDOW_MAX = 4; - PREV_LAST_WINDOW_AVG = 5; - } - } - } -} - -/* - * A raw CAN frame specified to be collected and sent to the cloud. - */ -message RawCanFrame { - - /* - * The interface ID speficied by the Decoder Manifest. This will contain the - * physical channel id of the hardware CAN Bus the frame is present on. - */ - - string can_interface_id = 1; - - /* - * CAN Message ID to collect. This Raw CAN message will be collected. - * Whatever number of bytes present on the bus for this message ID will be - * collected. - */ - uint32 can_message_id = 2; - - /* - * Ring buffer size used to store these sampled CAN frames. One CAN Frame - * is a sample. - */ - uint32 sample_buffer_size = 3; - - /* - * Minimum time period in milliseconds that must elapse between collecting - * samples. Samples arriving faster than this period will be dropped. - * A value of 0 will collect samples as fast as they arrive. - */ - uint32 minimum_sample_period_ms = 4; -} -``` +defined in the collection schemes to generate data snapshots. Refer to +[collection_schemes.proto](../../interfaces/protobuf/schemas/cloudToEdge/collection_schemes.proto). ## Data Persistency @@ -1679,14 +916,18 @@ described below in the configuration section. Each log entry includes the follow | | 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 | +| mqttConnection | endpointUrl | AWS account's IoT device endpoint | 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 | | | canDataTopic | Topic for sending collected data to cloud | string | | | checkinTopic | Topic for sending checkins to the cloud | string | -| | certificateFilename | The path to the device’s certificate file | string | -| | privateKeyFilename | The path to the device’s private key file. | string | +| | certificateFilename | The path to the device's certificate file (either `certificateFilename` or `certificate` must be provided) | string | +| | privateKeyFilename | The path to the device's private key file (either `privateKeyFilename` or `privateKey` must be provided) | string | +| | rootCAFilename | The path to the root CA certificate file (optional, either `rootCAFilename` or `rootCA` can be provided) | string | +| | certificate | The path to the device's certificate file (either `certificateFilename` or `certificate` must be provided) | string | +| | privateKey | The path to the device's private key file (either `privateKeyFilename` or `privateKey` must be provided) | string | +| | rootCA | The path to the root CA certificate file (optional, either `rootCAFilename` or `rootCA` can be provided) | string | | | metricsUploadTopic | Topic used to upload application metrics in plain json. Only used if `remoteProfilerDefaultValues` section is configured | string | | | loggingUploadTopic | Topic used to upload log messages in plain json. Only used if `remoteProfilerDefaultValues` section is configured | string | | remoteProfilerDefaultValues | loggingUploadLevelThreshold | Only log messages with this or higher severity will be uploaded | integer | @@ -1706,7 +947,7 @@ incorporated into four main domains: for further details. - Data in transit: All the data exchanged with the AWS IoT services is encrypted in transit. - Data at rest: the current version of the software does not encrypt the data at rest i.e. during - persistency. It’s assumed that the software operates in a secure partition that the OEM puts in + 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 @@ -1773,165 +1014,3 @@ The following documents or websites provide more information about AWS IoT Fleet 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. - -## Appendix - -### Decoder Manifest Example - -```json -{ - "modelManifestArn": "arn:aws:iotfleetwise:us-east-1:111111111111:model-manifest/my-model-manifest", - "name": "my-decoder-manifest", - "networkInterfaces": [ - { - "interfaceId": "0", - "type": "OBD_INTERFACE", - "obdInterface": { - "name": "can0", - "requestMessageId": 2015, - "obdStandard": "J1979", - "pidRequestIntervalSeconds": 5, - "dtcRequestIntervalSeconds": 5 - } - }, - { - "interfaceId": "1", - "type": "CAN_INTERFACE", - "canInterface": { - "name": "can0", - "protocolName": "CAN", - "protocolVersion": "2.0B" - } - } - ], - "signalDecoders": [ - { - "fullyQualifiedName": "Vehicle.myCanSignal", - "interfaceId": "1", - "type": "CAN_SIGNAL", - "canSignal": { - "name": "myCanSignal", - "factor": 1.0, - "isBigEndian": true, - "isSigned": true, - "length": 8, - "messageId": 1024, - "offset": 0, - "startBit": 0 - } - }, - { - "fullyQualifiedName": "Vehicle.OBD.CoolantTemperature", - "interfaceId": "0", - "type": "OBD_SIGNAL", - "obdSignal": { - "byteLength": 1, - "offset": -40, - "pid": 5, - "pidResponseLength": 1, - "scaling": 1, - "serviceMode": 1, - "startByte": 0, - "bitMaskLength": 8, - "bitRightShift": 0 - } - } - ] -} -``` - -### Campaign Example - -```json -{ - "name": "my-campaign", - "signalCatalogArn": "arn:aws:iotfleetwise:us-east-1:111111111111:signal-catalog/my-signal-catalog", - "targetArn": "arn:aws:iotfleetwise:us-east-1:111111111111:fleet/my-fleet", - "compression": "SNAPPY", - "diagnosticsMode": "OFF", - "spoolingMode": "TO_DISK", - "collectionScheme": { - "conditionBasedCollectionScheme": { - "conditionLanguageVersion": 1, - "expression": "$variable.`Vehicle.DemoBrakePedalPressure` > 7000", - "minimumTriggerIntervalMs": 1000, - "triggerMode": "ALWAYS" - } - }, - "postTriggerCollectionDuration": 1000, - "signalsToCollect": [ - { - "name": "Vehicle.DemoEngineTorque" - }, - { - "name": "Vehicle.DemoBrakePedalPressure" - } - ] -} -``` - ---- - -**Edge Configuration Example** - -```json -{ - "version": "1.0", - "networkInterfaces": [ - { - "canInterface": { - "interfaceName": "vcan0", - "protocolName": "CAN", - "protocolVersion": "2.0B" - }, - "interfaceId": "1", - "type": "canInterface" - }, - { - "obdInterface": { - "interfaceName": "vcan0", - "obdStandard": "J1979", - "pidRequestIntervalSeconds": 0, - "dtcRequestIntervalSeconds": 0 - }, - "interfaceId": "2", - "type": "obdInterface" - } - ], - "staticConfig": { - "bufferSizes": { - "dtcBufferSize": 100, - "decodedSignalsBufferSize": 10000, - "rawCANFrameBufferSize": 10000 - }, - "threadIdleTimes": { - "inspectionThreadIdleTimeMs": 50, - "socketCANThreadIdleTimeMs": 50, - "canDecoderThreadIdleTimeMs": 50 - }, - "persistency": { - "persistencyPath": "/path/to/collection-scheme-and-data-persistency", - "persistencyPartitionMaxSize": 524288 - }, - "internalParameters": { - "readyToPublishDataBufferSize": 10000, - "systemWideLogLevel": "Trace", - "dataReductionProbabilityDisabled": false - }, - "publishToCloudParameters": { - "maxPublishMessageCount": 1000, - "collectionSchemeManagementCheckinIntervalMs": 5000 - }, - "mqttConnection": { - "endpointUrl": "my-endpoint.my-region.amazonaws.com", - "clientId": "ClientId", - "collectionSchemeListTopic": "collection-scheme-list-topic", - "decoderManifestTopic": "decoder-manifest-topic", - "canDataTopic": "can-data", - "checkinTopic": "checkin", - "certificateFilename": "path/to/my-certificate.pem.crt", - "privateKeyFilename": "path/to/my-private.pem.key" - } - } -} -``` diff --git a/docs/rpi-tutorial/raspberry-pi-tutorial.md b/docs/rpi-tutorial/raspberry-pi-tutorial.md index 75493acc..4be03352 100644 --- a/docs/rpi-tutorial/raspberry-pi-tutorial.md +++ b/docs/rpi-tutorial/raspberry-pi-tutorial.md @@ -31,7 +31,7 @@ Amazon. - A Raspberry Pi, version 3 or later, (64-bit) - An SD-card, with a minimum of 4 GB storage -- A CAN ‘Hat’ for Raspberry Pi with an MCP2515 CAN controller such as the +- A CAN 'Hat' for Raspberry Pi with an MCP2515 CAN controller such as the [XYGStudy 2-Channel Isolated CAN Bus Expansion HAT](https://www.amazon.com/Raspberry-2-Channel-SN65HVD230-Protection-XYGStudy/dp/B087PWBFV8?th=1), [Coolwell Waveshare 2-Channel Isolated CAN Bus Expansion Hat](https://www.amazon.de/-/en/Waveshare-CAN-HAT-SN65HVD230-Protection/dp/B087PWNMM8/?th=1), or the @@ -51,7 +51,7 @@ Amazon. 1. Insert the SD card into your Raspberry Pi, attach the CAN hat, connect the Raspberry Pi to your internet router via an Ethernet cable, and turn on the power. 1. SSH to Raspberry Pi, using the initial password `ubuntu`: (Note: If connecting to the hostname - `ubuntu` doesn’t work, find the IP address from your internet router instead.) + `ubuntu` doesn't work, find the IP address from your internet router instead.) ```bash ssh ubuntu@ubuntu ``` diff --git a/interfaces/protobuf/schemas/cloudToEdge/collection_schemes.proto b/interfaces/protobuf/schemas/cloudToEdge/collection_schemes.proto index 2ed6c1c4..87ebe358 100644 --- a/interfaces/protobuf/schemas/cloudToEdge/collection_schemes.proto +++ b/interfaces/protobuf/schemas/cloudToEdge/collection_schemes.proto @@ -30,9 +30,9 @@ message CollectionSchemes { message CollectionScheme { /* - * Amazon Resource Name of the campaign this collectionScheme is part of + * Synchronization ID of the campaign this collectionScheme is part of */ - string campaign_arn = 1; + string campaign_sync_id = 1; /* * Synchronization ID of the required decoder manifest for this collectionScheme diff --git a/interfaces/protobuf/schemas/edgeConfiguration/staticConfiguration.json b/interfaces/protobuf/schemas/edgeConfiguration/staticConfiguration.json index 4bff6c17..c623d752 100644 --- a/interfaces/protobuf/schemas/edgeConfiguration/staticConfiguration.json +++ b/interfaces/protobuf/schemas/edgeConfiguration/staticConfiguration.json @@ -225,7 +225,7 @@ "properties": { "endpointUrl": { "type": "string", - "description": "AWS account’s IoT device endpoint" + "description": "AWS account's IoT device endpoint" }, "clientId": { "type": "string", @@ -249,11 +249,27 @@ }, "certificateFilename": { "type": "string", - "description": "The path to the device’s certificate file" + "description": "The path to the device's certificate file" + }, + "certificate": { + "type": "string", + "description": "The device's certificate" }, "privateKeyFilename": { "type": "string", - "description": "The path to the device’s private key file that was created with its certificate file" + "description": "The path to the device's private key file that was created with its certificate file" + }, + "privateKey": { + "type": "string", + "description": "The device's private key that was created with its certificate" + }, + "rootCAFilename": { + "type": "string", + "description": "The path to the root CA certificate file (optional)" + }, + "rootCA": { + "type": "string", + "description": "The root CA certificate (optional)" } }, "required": [ @@ -262,9 +278,15 @@ "collectionSchemeListTopic", "decoderManifestTopic", "canDataTopic", - "checkinTopic", - "certificateFilename", - "privateKeyFilename" + "checkinTopic" + ], + "oneOf": [ + { + "required": ["certificateFilename", "privateKeyFilename"] + }, + { + "required": ["certificate", "privateKey"] + } ] } }, diff --git a/interfaces/protobuf/schemas/edgeToCloud/checkin.proto b/interfaces/protobuf/schemas/edgeToCloud/checkin.proto index 991411d4..56f28353 100644 --- a/interfaces/protobuf/schemas/edgeToCloud/checkin.proto +++ b/interfaces/protobuf/schemas/edgeToCloud/checkin.proto @@ -8,10 +8,10 @@ package Aws.IoTFleetWise.Schemas.CheckinMsg; message Checkin { /* - * List of document arn's the Edge currently has enacted including collectionSchemes (both idle and active) and + * List of document synchronization IDs the Edge currently has enacted including collectionSchemes (both idle and active) and * decoder manifest. */ - repeated string document_arns = 1; + repeated string document_sync_ids = 1; /* * Timestamp of when check in was generated in milliseconds since the Unix epoch diff --git a/interfaces/protobuf/schemas/edgeToCloud/vehicle_data.proto b/interfaces/protobuf/schemas/edgeToCloud/vehicle_data.proto index d209341b..81df35d4 100644 --- a/interfaces/protobuf/schemas/edgeToCloud/vehicle_data.proto +++ b/interfaces/protobuf/schemas/edgeToCloud/vehicle_data.proto @@ -13,9 +13,9 @@ package Aws.IoTFleetWise.Schemas.VehicleDataMsg; message VehicleData { /* - * Amazon Resource Name of the campaign that triggered the collection of the data in this message + * Synchronization ID of the campaign that triggered the collection of the data in this message */ - string campaign_arn = 1; + string campaign_sync_id = 1; /* * Synchronization ID of the decoder manifest used to decode the signals in this message diff --git a/src/datamanagement/CMakeLists.txt b/src/datamanagement/CMakeLists.txt index d95a607f..c5313667 100644 --- a/src/datamanagement/CMakeLists.txt +++ b/src/datamanagement/CMakeLists.txt @@ -3,6 +3,6 @@ add_subdirectory(datacollection) add_subdirectory(datadecoding) add_subdirectory(datainspection) add_subdirectory(datamanager) -if(FWE_EXAMPLE_IWAVEGPS) +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 840dcb89..9b234f10 100644 --- a/src/datamanagement/custom/CMakeLists.txt +++ b/src/datamanagement/custom/CMakeLists.txt @@ -4,9 +4,15 @@ set(libraryTargetName iotfleetwise.customdata) # The alias name is what other targets will use as a dependency set(libraryAliasName IoTFleetWise::CustomDataSource) +if(FWE_FEATURE_IWAVE_GPS) + set(EXTRA_SOURCE_FILES ${EXTRA_SOURCE_FILES} example/iwavegps/src/IWaveGpsSource.cpp) +endif() +if(FWE_FEATURE_EXTERNAL_GPS) + set(EXTRA_SOURCE_FILES ${EXTRA_SOURCE_FILES} example/externalgps/src/ExternalGpsSource.cpp) +endif() set(SRCS generic/src/CustomDataSource.cpp - example/iwavegps/src/IWaveGpsSource.cpp + ${EXTRA_SOURCE_FILES} ) add_library( @@ -15,7 +21,16 @@ add_library( ${SRCS} ) -target_include_directories(${libraryTargetName} PUBLIC generic/include example/iwavegps/include) +if(FWE_FEATURE_IWAVE_GPS) + set(EXTRA_INCLUDE_DIRS ${EXTRA_INCLUDE_DIRS} example/iwavegps/include) +endif() +if(FWE_FEATURE_EXTERNAL_GPS) + set(EXTRA_INCLUDE_DIRS ${EXTRA_INCLUDE_DIRS} example/externalgps/include) +endif() +target_include_directories(${libraryTargetName} PUBLIC + generic/include + ${EXTRA_INCLUDE_DIRS} +) target_link_libraries( ${libraryTargetName} @@ -34,10 +49,16 @@ add_library(${libraryAliasName} ALIAS ${libraryTargetName}) install(TARGETS ${libraryTargetName} DESTINATION lib) +if(FWE_FEATURE_IWAVE_GPS) + set(EXTRA_INSTALL_FILES ${EXTRA_INSTALL_FILES} example/iwavegps/include/IWaveGpsSource.h) +endif() +if(FWE_FEATURE_EXTERNAL_GPS) + set(EXTRA_INSTALL_FILES ${EXTRA_INSTALL_FILES} example/externalgps/include/ExternalGpsSource.h) +endif() install( FILES generic/include/CustomDataSource.h - example/iwavegps/include/IWaveGpsSource.h + ${EXTRA_INSTALL_FILES} DESTINATION include ) @@ -57,11 +78,16 @@ if(${BUILD_TESTING}) NAMES gmock_main) - + if(FWE_FEATURE_IWAVE_GPS) + set(EXTRA_TEST_FILES ${EXTRA_TEST_FILES} example/iwavegps/test/IWaveGpsSourceTest.cpp) + endif() + if(FWE_FEATURE_EXTERNAL_GPS) + set(EXTRA_TEST_FILES ${EXTRA_TEST_FILES} example/externalgps/test/ExternalGpsSourceTest.cpp) + endif() set( testSources - example/iwavegps/test/IWaveGpsSourceTest.cpp generic/test/CustomDataSourceTest.cpp + ${EXTRA_TEST_FILES} ) # Add the executable targets foreach(testSource ${testSources}) @@ -85,7 +111,7 @@ if(${BUILD_TESTING}) ) add_test(NAME ${testName} COMMAND ${testName} --gtest_output=xml:report-${testName}.xml) - add_valgrind_test(${testName} ${CMAKE_CURRENT_SOURCE_DIR}/example/iwavegps/test/valgrind.supp) + add_valgrind_test(${testName} ${CMAKE_CURRENT_SOURCE_DIR}/generic/test/valgrind.supp) install(TARGETS ${testName} RUNTIME DESTINATION bin/tests) endforeach() diff --git a/src/datamanagement/custom/README.md b/src/datamanagement/custom/README.md index a8786ca0..7cfc8f93 100644 --- a/src/datamanagement/custom/README.md +++ b/src/datamanagement/custom/README.md @@ -103,6 +103,60 @@ read from the static config as the example in `IoTFleetWiseEngine::connect()` sh optional section "iWaveGpsExample" under "staticConfig" will be used so the parameters can be changed without recompilation. +## ExternalGpsSource + +The provided example `ExternalGpsSource` module can be enabled with the `FWE_FEATURE_EXTERNAL_GPS` +build option. This allows the ingestion of GPS data using the custom data source interface from an +in-process source, for example when the FWE code is built as a shared library. For example the +decoder manifest for the latitude and longitude signals can be created as follows: + +```json +{ + "name": "ExternalGpsDecoderManifest", + "modelManifestArn": "arn:aws:iotfleetwise:us-east-1:748151249882:model-manifest/ExternalGPSModel", + "networkInterfaces": [ + { + "interfaceId": "EXTERNAL-GPS-CAN", + "type": "CAN_INTERFACE", + "canInterface": { + "name": "externalgpscan", + "protocolName": "CAN" + } + } + ], + "signalDecoders": [ + { + "fullyQualifiedName": "Vehicle.CurrentLocation.Longitude", + "type": "CAN_SIGNAL", + "interfaceId": "EXTERNAL-GPS-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": -2000.0, + "factor": 0.001, + "length": 32 + } + }, + { + "fullyQualifiedName": "Vehicle.CurrentLocation.Latitude", + "type": "CAN_SIGNAL", + "interfaceId": "EXTERNAL-GPS-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 32, + "offset": -2000.0, + "factor": 0.001, + "length": 32 + } + } + ] +} +``` + # Implementing a new CustomDataSource ## What to do in the init diff --git a/src/datamanagement/custom/example/externalgps/include/ExternalGpsSource.h b/src/datamanagement/custom/example/externalgps/include/ExternalGpsSource.h new file mode 100644 index 00000000..cda4a923 --- /dev/null +++ b/src/datamanagement/custom/example/externalgps/include/ExternalGpsSource.h @@ -0,0 +1,74 @@ +// 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 + +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 ExternalGpsSource : public CustomDataSource +{ +public: + /** + * @param signalBufferPtr the signal buffer is used pass extracted data + */ + ExternalGpsSource( SignalBufferPtr signalBufferPtr ); + /** + * Initialize ExternalGpsSource 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 + * @param latitudeStartBit the startBit used in the decoder manifest for the latitude signal + * @param longitudeStartBit the startBit used in the decoder manifest for the longitude signal + * + * @return on success true otherwise false + */ + bool init( CANChannelNumericID canChannel, + CANRawFrameID canRawFrameId, + uint16_t latitudeStartBit, + uint16_t longitudeStartBit ); + + void setLocation( double latitude, double longitude ); + + static constexpr const char *CAN_CHANNEL_NUMBER = "canChannel"; + static constexpr const char *CAN_RAW_FRAME_ID = "canFrameId"; + static constexpr const char *LATITUDE_START_BIT = "latitudeStartBit"; + static constexpr const char *LONGITUDE_START_BIT = "longitudeStartBit"; + +protected: + void pollData() override; + const char *getThreadName() override; + +private: + 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; + + 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/externalgps/src/ExternalGpsSource.cpp b/src/datamanagement/custom/example/externalgps/src/ExternalGpsSource.cpp new file mode 100644 index 00000000..f4fd6458 --- /dev/null +++ b/src/datamanagement/custom/example/externalgps/src/ExternalGpsSource.cpp @@ -0,0 +1,87 @@ +// 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" + +namespace Aws +{ +namespace IoTFleetWise +{ +namespace DataManagement +{ +constexpr const char *ExternalGpsSource::CAN_CHANNEL_NUMBER; +constexpr const char *ExternalGpsSource::CAN_RAW_FRAME_ID; +constexpr const char *ExternalGpsSource::LATITUDE_START_BIT; +constexpr const char *ExternalGpsSource::LONGITUDE_START_BIT; +ExternalGpsSource::ExternalGpsSource( SignalBufferPtr signalBufferPtr ) + : mSignalBufferPtr{ std::move( signalBufferPtr ) } +{ +} + +bool +ExternalGpsSource::init( CANChannelNumericID canChannel, + CANRawFrameID canRawFrameId, + uint16_t latitudeStartBit, + uint16_t longitudeStartBit ) +{ + if ( canChannel == INVALID_CAN_SOURCE_NUMERIC_ID ) + { + return false; + } + mLatitudeStartBit = latitudeStartBit; + mLongitudeStartBit = longitudeStartBit; + mCanChannel = canChannel; + mCanRawFrameId = canRawFrameId; + setFilter( mCanChannel, mCanRawFrameId ); + return true; +} +const char * +ExternalGpsSource::getThreadName() +{ + return "ExternalGpsSource"; +} + +void +ExternalGpsSource::setLocation( double latitude, double longitude ) +{ + if ( ( !validLatitude( latitude ) ) || ( !validLongitude( longitude ) ) ) + { + FWE_LOG_WARN( "Invalid location: Latitude: " + std::to_string( latitude ) + + ", Longitude: " + std::to_string( longitude ) ); + return; + } + FWE_LOG_TRACE( "Latitude: " + std::to_string( latitude ) + ", Longitude: " + std::to_string( longitude ) ); + auto latitudeSignalId = getSignalIdFromStartBit( mLatitudeStartBit ); + auto longitudeSignalId = getSignalIdFromStartBit( mLongitudeStartBit ); + if ( ( latitudeSignalId == INVALID_SIGNAL_ID ) || ( longitudeSignalId == INVALID_SIGNAL_ID ) ) + { + FWE_LOG_WARN( "Latitude or longitude not in decoder manifest" ); + return; + } + auto timestamp = mClock->systemTimeSinceEpochMs(); + mSignalBufferPtr->push( CollectedSignal( latitudeSignalId, timestamp, latitude ) ); + mSignalBufferPtr->push( CollectedSignal( longitudeSignalId, timestamp, longitude ) ); +} + +void +ExternalGpsSource::pollData() +{ +} + +bool +ExternalGpsSource::validLatitude( double latitude ) +{ + return ( latitude >= -90.0 ) && ( latitude <= 90.0 ); +} +bool +ExternalGpsSource::validLongitude( double longitude ) +{ + return ( longitude >= -180.0 ) && ( longitude <= 180.0 ); +} + +} // 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 new file mode 100644 index 00000000..a502f470 --- /dev/null +++ b/src/datamanagement/custom/example/externalgps/test/ExternalGpsSourceTest.cpp @@ -0,0 +1,79 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +#include "ExternalGpsSource.h" +#include "WaitUntil.h" +#include +#include + +using namespace Aws::IoTFleetWise::DataManagement; + +class ExternalGpsSourceTest : 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.mFirstBitPosition = 0; + sig1.mSignalID = 0x1234; + sig2.mFirstBitPosition = 32; + 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 if valid gps data +TEST_F( ExternalGpsSourceTest, testDecoding ) // NOLINT +{ + SignalBufferPtr signalBufferPtr = std::make_shared( 100 ); + 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; + 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 ); +} + +// Test longitude west +TEST_F( ExternalGpsSourceTest, testWestNegativeLongitude ) // NOLINT +{ + SignalBufferPtr signalBufferPtr = std::make_shared( 100 ); + 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; + 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 +} diff --git a/src/datamanagement/custom/example/iwavegps/README.md b/src/datamanagement/custom/example/iwavegps/README.md index a364b180..59f86888 100644 --- a/src/datamanagement/custom/example/iwavegps/README.md +++ b/src/datamanagement/custom/example/iwavegps/README.md @@ -29,8 +29,9 @@ change the parameters without recompiling AWS IoT FleetWise Edge: } ``` -When configure AWS IoT FleetWise use the `cmake` with the flag `-DFWE_EXAMPLE_IWAVEGPS=On` and then -compile using `make` as usual. +When configure AWS IoT FleetWise use the `cmake` with the flags +`-DFWE_FEATURE_CUSTOM_DATA_SOURCE=On -DFWE_FEATURE_IWAVE_GPS=On` and then compile using `make` as +usual. # Debug diff --git a/src/datamanagement/custom/example/iwavegps/test/valgrind.supp b/src/datamanagement/custom/generic/test/valgrind.supp similarity index 100% rename from src/datamanagement/custom/example/iwavegps/test/valgrind.supp rename to src/datamanagement/custom/generic/test/valgrind.supp diff --git a/src/datamanagement/datacollection/include/CANInterfaceIDTranslator.h b/src/datamanagement/datacollection/include/CANInterfaceIDTranslator.h index a7930ce4..1b110955 100644 --- a/src/datamanagement/datacollection/include/CANInterfaceIDTranslator.h +++ b/src/datamanagement/datacollection/include/CANInterfaceIDTranslator.h @@ -29,7 +29,7 @@ class CANInterfaceIDTranslator } CANChannelNumericID - getChannelNumericID( CANInterfaceID iid ) + getChannelNumericID( const CANInterfaceID &iid ) { for ( auto l : mLookup ) { diff --git a/src/datamanagement/datacollection/src/CollectionSchemeIngestion.cpp b/src/datamanagement/datacollection/src/CollectionSchemeIngestion.cpp index c3bb5364..84c17692 100644 --- a/src/datamanagement/datacollection/src/CollectionSchemeIngestion.cpp +++ b/src/datamanagement/datacollection/src/CollectionSchemeIngestion.cpp @@ -38,7 +38,7 @@ bool CollectionSchemeIngestion::build() { // Check if Collection collectionScheme has an ID and a Decoder Manifest ID - if ( mProtoCollectionSchemeMessagePtr->campaign_arn().empty() || + if ( mProtoCollectionSchemeMessagePtr->campaign_sync_id().empty() || mProtoCollectionSchemeMessagePtr->decoder_manifest_sync_id().empty() ) { FWE_LOG_ERROR( "CollectionScheme does not have ID or DM ID" ); @@ -53,7 +53,7 @@ CollectionSchemeIngestion::build() return false; } - FWE_LOG_TRACE( "Building CollectionScheme with ID: " + mProtoCollectionSchemeMessagePtr->campaign_arn() ); + FWE_LOG_TRACE( "Building CollectionScheme with ID: " + mProtoCollectionSchemeMessagePtr->campaign_sync_id() ); // Build Collected Signals for ( int signalIndex = 0; signalIndex < mProtoCollectionSchemeMessagePtr->signal_information_size(); @@ -168,7 +168,7 @@ CollectionSchemeIngestion::build() } } - FWE_LOG_INFO( "Successfully built CollectionScheme ID: " + mProtoCollectionSchemeMessagePtr->campaign_arn() ); + FWE_LOG_INFO( "Successfully built CollectionScheme ID: " + mProtoCollectionSchemeMessagePtr->campaign_sync_id() ); // Set ready flag to true mReady = true; @@ -426,7 +426,7 @@ CollectionSchemeIngestion::getCollectionSchemeID() const return INVALID_COLLECTION_SCHEME_ID; } - return mProtoCollectionSchemeMessagePtr->campaign_arn(); + return mProtoCollectionSchemeMessagePtr->campaign_sync_id(); } const std::string & diff --git a/src/datamanagement/datacollection/src/DataCollectionProtoWriter.cpp b/src/datamanagement/datacollection/src/DataCollectionProtoWriter.cpp index 31f25101..e0d783ff 100644 --- a/src/datamanagement/datacollection/src/DataCollectionProtoWriter.cpp +++ b/src/datamanagement/datacollection/src/DataCollectionProtoWriter.cpp @@ -29,7 +29,7 @@ DataCollectionProtoWriter::setupVehicleData( const TriggeredCollectionSchemeData mVehicleDataMsgCount = 0U; mVehicleData.Clear(); - mVehicleData.set_campaign_arn( triggeredCollectionSchemeData->metaData.collectionSchemeID ); + 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; diff --git a/src/datamanagement/datacollection/src/DataCollectionSender.cpp b/src/datamanagement/datacollection/src/DataCollectionSender.cpp index eeeadb14..4347ab7d 100644 --- a/src/datamanagement/datacollection/src/DataCollectionSender.cpp +++ b/src/datamanagement/datacollection/src/DataCollectionSender.cpp @@ -161,8 +161,9 @@ DataCollectionSender::transmit() 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 unloaded to AWS IoT Core" ); + " bytes has been uploaded to AWS IoT Core" ); } return ret; } diff --git a/src/datamanagement/datacollection/test/DataCollectionProtoWriterTest.cpp b/src/datamanagement/datacollection/test/DataCollectionProtoWriterTest.cpp index 8dacfe1c..5596e039 100644 --- a/src/datamanagement/datacollection/test/DataCollectionProtoWriterTest.cpp +++ b/src/datamanagement/datacollection/test/DataCollectionProtoWriterTest.cpp @@ -69,7 +69,7 @@ TEST_F( DataCollectionProtoWriterTest, TestVehicleData ) } /* Read and compare to written fields */ - ASSERT_EQ( "123", vehicleDataTest.campaign_arn() ); + ASSERT_EQ( "123", vehicleDataTest.campaign_sync_id() ); ASSERT_EQ( "456", vehicleDataTest.decoder_sync_id() ); ASSERT_EQ( collectionEventID, vehicleDataTest.collection_event_id() ); ASSERT_EQ( testTriggerTime, vehicleDataTest.collection_event_time_ms_epoch() ); @@ -121,7 +121,7 @@ TEST_F( DataCollectionProtoWriterTest, TestDTCData ) } /* Read and compare to written fields */ - ASSERT_EQ( "123", vehicleDataTest.campaign_arn() ); + ASSERT_EQ( "123", vehicleDataTest.campaign_sync_id() ); ASSERT_EQ( "456", vehicleDataTest.decoder_sync_id() ); ASSERT_EQ( collectionEventID, vehicleDataTest.collection_event_id() ); ASSERT_EQ( testTriggerTime, vehicleDataTest.collection_event_time_ms_epoch() ); @@ -169,7 +169,7 @@ TEST_F( DataCollectionProtoWriterTest, TestGeohash ) ASSERT_TRUE( vehicleDataTest.ParseFromString( testProto ) ); /* Read and compare to written fields */ - ASSERT_EQ( "123", vehicleDataTest.campaign_arn() ); + ASSERT_EQ( "123", vehicleDataTest.campaign_sync_id() ); ASSERT_EQ( "456", vehicleDataTest.decoder_sync_id() ); ASSERT_EQ( collectionEventID, vehicleDataTest.collection_event_id() ); ASSERT_EQ( testTriggerTime, vehicleDataTest.collection_event_time_ms_epoch() ); diff --git a/src/datamanagement/datacollection/test/DataCollectionSenderTest.cpp b/src/datamanagement/datacollection/test/DataCollectionSenderTest.cpp index f2518f66..9e8a685b 100644 --- a/src/datamanagement/datacollection/test/DataCollectionSenderTest.cpp +++ b/src/datamanagement/datacollection/test/DataCollectionSenderTest.cpp @@ -59,7 +59,7 @@ class DataCollectionSenderTest : public ::testing::Test ASSERT_TRUE( vehicleDataTest.ParseFromString( expectedProto ) ); /* Read and compare to written fields */ - ASSERT_EQ( "123", vehicleDataTest.campaign_arn() ); + 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 ); @@ -85,7 +85,7 @@ class DataCollectionSenderTest : public ::testing::Test ASSERT_TRUE( vehicleDataTest.ParseFromString( expectedProto ) ); /* Read and compare to written fields */ - ASSERT_EQ( "123", vehicleDataTest.campaign_arn() ); + 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 diff --git a/src/datamanagement/datadecoding/include/OBDDataDecoder.h b/src/datamanagement/datadecoding/include/OBDDataDecoder.h index 3fb99344..4c677536 100644 --- a/src/datamanagement/datadecoding/include/OBDDataDecoder.h +++ b/src/datamanagement/datadecoding/include/OBDDataDecoder.h @@ -24,11 +24,6 @@ using namespace Aws::IoTFleetWise::Platform::Linux; */ using OBDDecoderDictionary = std::unordered_map; -/** - * @brief define shared pointer type for OBD-II PID decoder dictionary - */ -using ConstOBDDecoderDictionaryConstPtr = const std::shared_ptr; - /** * @brief OBD Data Decoder. Decodes OBD ECU responses according * to the J1979 specifications @@ -38,7 +33,7 @@ class OBDDataDecoder { public: - OBDDataDecoder(); + OBDDataDecoder( std::shared_ptr &decoderDictionary ); ~OBDDataDecoder() = default; OBDDataDecoder( const OBDDataDecoder & ) = delete; @@ -55,7 +50,7 @@ class OBDDataDecoder * @return True if we received a positive response and extracted the PIDs. * needed. */ - static bool decodeSupportedPIDs( const SID &sid, + static bool decodeSupportedPIDs( const SID sid, const std::vector &inputData, SupportedPIDs &supportedPIDs ); @@ -69,7 +64,7 @@ class OBDDataDecoder * @return True if we received a positive response and decoded at least one supported PID. * needed. */ - bool decodeEmissionPIDs( const SID &sid, + bool decodeEmissionPIDs( const SID sid, const std::vector &pids, const std::vector &inputData, EmissionInfo &info ); @@ -83,7 +78,7 @@ class OBDDataDecoder * @return True if we received a positive response and decoded the DTCs. * A positive response also can mean that no DTCs were reported by the ECU. */ - static bool decodeDTCs( const SID &sid, const std::vector &inputData, DTCInfo &info ); + static bool decodeDTCs( const SID sid, const std::vector &inputData, DTCInfo &info ); /** * @brief Decodes VIN from the ECU response, @@ -96,19 +91,11 @@ class OBDDataDecoder static bool extractDTCString( const uint8_t &firstByte, const uint8_t &secondByte, std::string &dtcString ); - /** - * @brief Update OBD Data Decoder module with decoder dictionary - * - * @param dictionary Const shared pointer to Const OBD Decoder Dictionary - * @return None - */ - void setDecoderDictionary( ConstOBDDecoderDictionaryConstPtr &dictionary ); - private: Timer mTimer; std::shared_ptr mClock = ClockHandler::getClock(); // shared pointer to decoder dictionary - std::shared_ptr mDecoderDictionaryConstPtr; + std::shared_ptr &mDecoderDictionary; /** * @brief Uses the given formula to transform the raw data and add the results to info diff --git a/src/datamanagement/datadecoding/src/OBDDataDecoder.cpp b/src/datamanagement/datadecoding/src/OBDDataDecoder.cpp index b12a869e..5d4e302a 100644 --- a/src/datamanagement/datadecoding/src/OBDDataDecoder.cpp +++ b/src/datamanagement/datadecoding/src/OBDDataDecoder.cpp @@ -20,13 +20,14 @@ namespace IoTFleetWise namespace DataManagement { -OBDDataDecoder::OBDDataDecoder() +OBDDataDecoder::OBDDataDecoder( std::shared_ptr &decoderDictionary ) + : mDecoderDictionary{ decoderDictionary } { mTimer.reset(); } bool -OBDDataDecoder::decodeSupportedPIDs( const SID &sid, +OBDDataDecoder::decodeSupportedPIDs( const SID sid, const std::vector &inputData, SupportedPIDs &supportedPIDs ) { @@ -96,15 +97,8 @@ OBDDataDecoder::decodeSupportedPIDs( const SID &sid, return !supportedPIDs.empty(); } -void -OBDDataDecoder::setDecoderDictionary( ConstOBDDecoderDictionaryConstPtr &dictionary ) -{ - // OBDDataDecoder is running in one thread, hence we don't need mutext to prevent race condition - mDecoderDictionaryConstPtr = dictionary; -} - bool -OBDDataDecoder::decodeEmissionPIDs( const SID &sid, +OBDDataDecoder::decodeEmissionPIDs( const SID sid, const std::vector &pids, const std::vector &inputData, EmissionInfo &info ) @@ -119,7 +113,7 @@ OBDDataDecoder::decodeEmissionPIDs( const SID &sid, FWE_LOG_WARN( "Invalid response to PID request" ); return false; } - if ( mDecoderDictionaryConstPtr == nullptr ) + if ( mDecoderDictionary == nullptr ) { FWE_LOG_WARN( "Invalid Decoder Dictionary" ); return false; @@ -140,11 +134,11 @@ OBDDataDecoder::decodeEmissionPIDs( const SID &sid, auto pid = inputData[byteCounter]; byteCounter++; // first check whether the decoder dictionary contains this PID - if ( mDecoderDictionaryConstPtr->find( pid ) != mDecoderDictionaryConstPtr->end() ) + if ( mDecoderDictionary->find( pid ) != mDecoderDictionary->end() ) { // The expected number of bytes returned from PID - auto expectedResponseLength = mDecoderDictionaryConstPtr->at( pid ).mSizeInBytes; - auto formulas = mDecoderDictionaryConstPtr->at( pid ).mSignals; + auto expectedResponseLength = mDecoderDictionary->at( pid ).mSizeInBytes; + auto formulas = mDecoderDictionary->at( pid ).mSignals; // first check whether we have received enough bytes for this PID if ( byteCounter + expectedResponseLength <= inputData.size() ) { @@ -173,7 +167,7 @@ OBDDataDecoder::decodeEmissionPIDs( const SID &sid, } bool -OBDDataDecoder::decodeDTCs( const SID &sid, const std::vector &inputData, DTCInfo &info ) +OBDDataDecoder::decodeDTCs( const SID sid, const std::vector &inputData, DTCInfo &info ) { // First look at whether we received a positive response // The positive response can be identified by 0x40 + SID. @@ -292,10 +286,10 @@ OBDDataDecoder::isPIDResponseValid( const std::vector &pids, const std::vec return false; } *foundPid = INVALID_PID; // for every time a PID is requested only one response is expected - if ( mDecoderDictionaryConstPtr->find( pid ) != mDecoderDictionaryConstPtr->end() ) + if ( mDecoderDictionary->find( pid ) != mDecoderDictionary->end() ) { // Move Index into the next PID - responseByteIndex += ( mDecoderDictionaryConstPtr->at( pid ).mSizeInBytes + 1 ); + responseByteIndex += ( mDecoderDictionary->at( pid ).mSizeInBytes + 1 ); } else { @@ -427,10 +421,10 @@ OBDDataDecoder::isFormulaValid( PID pid, const CANSignalFormat &formula ) // 2. Last Bit Position (first bit + sizeInBits) has to be less than or equal to last bit position of PID response // length // 3. If mSizeInBits are greater or equal than 8, both mSizeInBits and first bit position has to be multiple of 8 - if ( ( mDecoderDictionaryConstPtr->find( pid ) != mDecoderDictionaryConstPtr->end() ) && - ( formula.mFirstBitPosition < mDecoderDictionaryConstPtr->at( pid ).mSizeInBytes * BYTE_SIZE ) && + if ( ( mDecoderDictionary->find( pid ) != mDecoderDictionary->end() ) && + ( formula.mFirstBitPosition < mDecoderDictionary->at( pid ).mSizeInBytes * BYTE_SIZE ) && ( formula.mSizeInBits + formula.mFirstBitPosition <= - mDecoderDictionaryConstPtr->at( pid ).mSizeInBytes * BYTE_SIZE ) && + mDecoderDictionary->at( pid ).mSizeInBytes * BYTE_SIZE ) && ( ( formula.mSizeInBits < 8 ) || ( ( ( formula.mSizeInBits & 0x7 ) == 0 ) && ( ( formula.mFirstBitPosition & 0x7 ) == 0 ) ) ) ) { diff --git a/src/datamanagement/datadecoding/test/OBDDataDecoderTest.cpp b/src/datamanagement/datadecoding/test/OBDDataDecoderTest.cpp index dcb8c907..b0630957 100644 --- a/src/datamanagement/datadecoding/test/OBDDataDecoderTest.cpp +++ b/src/datamanagement/datadecoding/test/OBDDataDecoderTest.cpp @@ -18,8 +18,8 @@ using namespace Aws::IoTFleetWise::TestingSupport; class OBDDataDecoderTest : public ::testing::Test { protected: - OBDDataDecoder decoder; std::shared_ptr decoderDictPtr; + OBDDataDecoder decoder{ decoderDictPtr }; void SetUp() override { @@ -48,7 +48,6 @@ class OBDDataDecoderTest : public ::testing::Test } decoderDictPtr->emplace( pid, format ); } - decoder.setDecoderDictionary( decoderDictPtr ); } void @@ -1082,7 +1081,6 @@ TEST_F( OBDDataDecoderTest, OBDDataDecoderCorruptFormulaTest ) const auto &originalFormula = decoderDictPtr->at( pid ).mSignals[0]; // corrupt mFirstBitPosition to out of bound decoderDictPtr->at( pid ).mSignals[0].mFirstBitPosition = 80; - decoder.setDecoderDictionary( decoderDictPtr ); std::vector txPDUData = { 0x41, pid, 0x99 }; EmissionInfo info; @@ -1108,7 +1106,6 @@ TEST_F( OBDDataDecoderTest, OBDDataDecoderCorruptFormulaTest ) // We shall expect decodeEmissionPIDs not decode this invalid ECU response. TEST_F( OBDDataDecoderTest, OBDDataDecoderWithECUResponseMismatchWithDecoderManifest ) { - decoder.setDecoderDictionary( decoderDictPtr ); // Below ECU response contains PID 107 which has mismatched response than decoder manifest std::vector txPDUData = { 0x41, 107, 0, 108, 0, 109, 0, 13, 102, 12, 252, 110, 0, 111, 0, 112, 0 }; EmissionInfo info; @@ -1120,7 +1117,7 @@ TEST_F( OBDDataDecoderTest, OBDDataDecoderDecodedEngineLoadCorruptDecoderDiction { PID pid = 0x04; // corrupt decoder dictionary pointer to nullptr - decoder.setDecoderDictionary( nullptr ); + decoderDictPtr = nullptr; std::vector txPDUData = { 0x41, pid, 0x99 }; EmissionInfo info; diff --git a/src/datamanagement/datainspection/CMakeLists.txt b/src/datamanagement/datainspection/CMakeLists.txt index ac2aa55b..b8dec7e7 100644 --- a/src/datamanagement/datainspection/CMakeLists.txt +++ b/src/datamanagement/datainspection/CMakeLists.txt @@ -13,6 +13,7 @@ set(SRCS src/location/GeohashFunctionNode.cpp src/vehicledatasource/CANDataSource.cpp src/vehicledatasource/CANDataConsumer.cpp + src/vehicledatasource/ExternalCANDataSource.cpp ) add_library( @@ -71,6 +72,7 @@ set( test/CollectionInspectionEngineTest.cpp test/CollectionInspectionWorkerThreadTest.cpp test/CANDataSourceTest.cpp + test/ExternalCANDataSourceTest.cpp ) if(FWE_FEATURE_CAMERA) diff --git a/src/datamanagement/datainspection/include/CANDataConsumer.h b/src/datamanagement/datainspection/include/CANDataConsumer.h index 01bb47fe..093af3f7 100644 --- a/src/datamanagement/datainspection/include/CANDataConsumer.h +++ b/src/datamanagement/datainspection/include/CANDataConsumer.h @@ -11,7 +11,6 @@ #include "SignalTypes.h" #include "TimeTypes.h" #include -#include #include namespace Aws @@ -28,7 +27,7 @@ using namespace Aws::IoTFleetWise::Platform::Linux; class CANDataConsumer { public: - CANDataConsumer( CANChannelNumericID channelId, SignalBufferPtr signalBufferPtr, CANBufferPtr canBufferPtr ); + CANDataConsumer( SignalBufferPtr signalBufferPtr, CANBufferPtr canBufferPtr ); ~CANDataConsumer() = default; CANDataConsumer( const CANDataConsumer & ) = delete; @@ -36,8 +35,11 @@ class CANDataConsumer CANDataConsumer( CANDataConsumer && ) = delete; CANDataConsumer &operator=( CANDataConsumer && ) = delete; - void processMessage( std::shared_ptr &dictionary, - const struct canfd_frame &message, + void processMessage( CANChannelNumericID channelId, + std::shared_ptr &dictionary, + uint32_t messageId, + const uint8_t *data, + size_t dataLength, Timestamp timestamp ); private: @@ -47,13 +49,13 @@ class CANDataConsumer * messageId is an input-output parameter that has the correct value * for the current message's id with either the MSB set or unset */ - bool findDecoderMethod( uint32_t &messageId, - const CANDecoderDictionary::CANMsgDecoderMethodType &decoderMethod, - CANMessageDecoderMethod ¤tMessageDecoderMethod ) const; + static bool findDecoderMethod( CANChannelNumericID channelId, + uint32_t &messageId, + const CANDecoderDictionary::CANMsgDecoderMethodType &decoderMethod, + CANMessageDecoderMethod ¤tMessageDecoderMethod ); CANBufferPtr mCANBufferPtr; SignalBufferPtr mSignalBufferPtr; - CANChannelNumericID mChannelId{ INVALID_CAN_SOURCE_NUMERIC_ID }; }; } // namespace DataInspection } // namespace IoTFleetWise diff --git a/src/datamanagement/datainspection/include/ExternalCANDataSource.h b/src/datamanagement/datainspection/include/ExternalCANDataSource.h new file mode 100644 index 00000000..8d84003c --- /dev/null +++ b/src/datamanagement/datainspection/include/ExternalCANDataSource.h @@ -0,0 +1,73 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +// Includes +#include "CANDataConsumer.h" +#include "ClockHandler.h" +#include "CollectionInspectionAPITypes.h" +#include "IActiveDecoderDictionaryListener.h" +#include "IDecoderDictionary.h" +#include "Signal.h" +#include "SignalTypes.h" +#include "TimeTypes.h" +#include +#include +#include +#include + +using namespace Aws::IoTFleetWise::Platform::Linux; + +namespace Aws +{ +namespace IoTFleetWise +{ +namespace DataInspection +{ + +/** + * @brief External CAN Bus implementation. Allows data from external in-process CAN bus sources to + * be ingested, for example when FWE is compiled as a shared-library. + */ +// 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 ExternalCANDataSource : public IActiveDecoderDictionaryListener +{ +public: + /** + * @brief Construct CAN data source + * @param consumer CAN data consumer + */ + ExternalCANDataSource( CANDataConsumer &consumer ); + ~ExternalCANDataSource() override = default; + + ExternalCANDataSource( const ExternalCANDataSource & ) = delete; + ExternalCANDataSource &operator=( const ExternalCANDataSource & ) = delete; + ExternalCANDataSource( ExternalCANDataSource && ) = delete; + ExternalCANDataSource &operator=( ExternalCANDataSource && ) = delete; + + /** Ingest CAN message. + * @param channelId CAN channel ID + * @param timestamp Timestamp of CAN message in milliseconds since epoch, or zero if unknown. + * @param messageId CAN message ID in Linux SocketCAN format + * @param data CAN message data */ + void ingestMessage( CANChannelNumericID channelId, + Timestamp timestamp, + uint32_t messageId, + const std::vector &data ); + + void onChangeOfActiveDictionary( ConstDecoderDictionaryConstPtr &dictionary, + VehicleDataSourceProtocol networkProtocol ) override; + +private: + std::shared_ptr mClock = ClockHandler::getClock(); + std::mutex mDecoderDictMutex; + std::shared_ptr mDecoderDictionary; + CANDataConsumer &mConsumer; + Timestamp mLastFrameTime{}; +}; + +} // namespace DataInspection +} // namespace IoTFleetWise +} // namespace Aws diff --git a/src/datamanagement/datainspection/include/OBDOverCANECU.h b/src/datamanagement/datainspection/include/OBDOverCANECU.h index dbfdee09..33028ca8 100644 --- a/src/datamanagement/datainspection/include/OBDOverCANECU.h +++ b/src/datamanagement/datainspection/include/OBDOverCANECU.h @@ -73,6 +73,19 @@ class OBDOverCANECU */ size_t requestReceiveEmissionPIDs( const SID sid ); + /** + * @brief Push PIDs to the signal buffer + * + * @param info PID vaues to push + * @param receptionTime Timestamp of reception + * @param signalBufferPtr Signal Buffer shared pointer + * @param streamRxID CAN Receive ID + */ + static void pushPIDs( const EmissionInfo &info, + Timestamp receptionTime, + SignalBufferPtr &signalBufferPtr, + const std::string &streamRxID ); + /** * @brief get DTC from ECU * diff --git a/src/datamanagement/datainspection/include/OBDOverCANModule.h b/src/datamanagement/datainspection/include/OBDOverCANModule.h index 52645d56..5d4d3172 100644 --- a/src/datamanagement/datainspection/include/OBDOverCANModule.h +++ b/src/datamanagement/datainspection/include/OBDOverCANModule.h @@ -122,6 +122,19 @@ class OBDOverCANModule : public IActiveDecoderDictionaryListener, public IActive return mActiveDTCBufferPtr; } + /** + * @brief Gets a list of PIDs to request externally + * @return List of PIDs + */ + std::vector getExternalPIDsToRequest(); + + /** + * @brief Sets the response for the given PID + * @param pid The PID + * @param response The response + */ + void setExternalPIDResponse( PID pid, std::vector response ); + private: /** * @brief Automatically detect all ECUs on a vehicle by sending broadcast request. diff --git a/src/datamanagement/datainspection/src/CollectionInspectionEngine.cpp b/src/datamanagement/datainspection/src/CollectionInspectionEngine.cpp index af65e6c7..f675e5af 100644 --- a/src/datamanagement/datainspection/src/CollectionInspectionEngine.cpp +++ b/src/datamanagement/datainspection/src/CollectionInspectionEngine.cpp @@ -410,6 +410,7 @@ CollectionInspectionEngine::preAllocateBuffers() // reserve the size like new[] buf.mBuffer.resize( buf.mSize ); } + TraceModule::get().setVariable( TraceVariable::SIGNAL_BUFFER_SIZE, usedBytes ); return true; } diff --git a/src/datamanagement/datainspection/src/diag/OBDOverCANECU.cpp b/src/datamanagement/datainspection/src/diag/OBDOverCANECU.cpp index df183137..c700b7c4 100644 --- a/src/datamanagement/datainspection/src/diag/OBDOverCANECU.cpp +++ b/src/datamanagement/datainspection/src/diag/OBDOverCANECU.cpp @@ -125,50 +125,52 @@ OBDOverCANECU::requestReceiveEmissionPIDs( const SID sid ) requestReceivePIDs( pidItr, sid, pids, info ); } - if ( !info.mPIDsToValues.empty() ) + pushPIDs( info, mClock->systemTimeSinceEpochMs(), mSignalBufferPtr, mStreamRxID ); + } + return numRequests; +} + +void +OBDOverCANECU::pushPIDs( const EmissionInfo &info, + Timestamp receptionTime, + SignalBufferPtr &signalBufferPtr, + const std::string &streamRxID ) +{ + for ( auto const &signals : info.mPIDsToValues ) + { + // Note Signal buffer is a multi producer single consumer queue. Besides current thread, + // Vehicle Data Consumer will also push signals onto this buffer + TraceModule::get().incrementAtomicVariable( TraceAtomicVariable::QUEUE_CONSUMER_TO_INSPECTION_SIGNALS ); + struct CollectedSignal collectedSignal; + const auto signalType = signals.second.signalType; + switch ( signalType ) { - auto receptionTime = mClock->systemTimeSinceEpochMs(); - for ( auto const &signals : info.mPIDsToValues ) - { - // Note Signal buffer is a multi producer single consumer queue. Besides current thread, - // Vehicle Data Consumer will also push signals onto this buffer - TraceModule::get().incrementAtomicVariable( TraceAtomicVariable::QUEUE_CONSUMER_TO_INSPECTION_SIGNALS ); - struct CollectedSignal collectedSignal; - const auto signalType = signals.second.signalType; - switch ( signalType ) - { - case SignalType::UINT64: - collectedSignal = CollectedSignal{ - signals.first, receptionTime, signals.second.signalValue.uint64Val, signalType }; - FWE_LOG_TRACE( "Received Signal " + std::to_string( signals.first ) + " : " + - std::to_string( signals.second.signalValue.uint64Val ) + - " for ECU: " + mStreamRxID ); - break; - case SignalType::INT64: - collectedSignal = CollectedSignal{ - signals.first, receptionTime, signals.second.signalValue.int64Val, signalType }; - FWE_LOG_TRACE( "Received Signal " + std::to_string( signals.first ) + " : " + - std::to_string( signals.second.signalValue.int64Val ) + " for ECU: " + mStreamRxID ); - break; - default: - collectedSignal = CollectedSignal{ - signals.first, receptionTime, signals.second.signalValue.doubleVal, signalType }; - FWE_LOG_TRACE( "Received Signal " + std::to_string( signals.first ) + " : " + - std::to_string( signals.second.signalValue.doubleVal ) + - " for ECU: " + mStreamRxID ); - break; - } + case SignalType::UINT64: + collectedSignal = + CollectedSignal{ signals.first, receptionTime, signals.second.signalValue.uint64Val, signalType }; + FWE_LOG_TRACE( "Received Signal " + std::to_string( signals.first ) + " : " + + std::to_string( signals.second.signalValue.uint64Val ) + " for ECU: " + streamRxID ); + break; + case SignalType::INT64: + collectedSignal = + CollectedSignal{ signals.first, receptionTime, signals.second.signalValue.int64Val, signalType }; + FWE_LOG_TRACE( "Received Signal " + std::to_string( signals.first ) + " : " + + std::to_string( signals.second.signalValue.int64Val ) + " for ECU: " + streamRxID ); + break; + default: + collectedSignal = + CollectedSignal{ signals.first, receptionTime, signals.second.signalValue.doubleVal, signalType }; + FWE_LOG_TRACE( "Received Signal " + std::to_string( signals.first ) + " : " + + std::to_string( signals.second.signalValue.doubleVal ) + " for ECU: " + streamRxID ); + break; + } - if ( !mSignalBufferPtr->push( collectedSignal ) ) - { - TraceModule::get().decrementAtomicVariable( - TraceAtomicVariable::QUEUE_CONSUMER_TO_INSPECTION_SIGNALS ); - FWE_LOG_WARN( "Signal Buffer full with ECU " + mStreamRxID ); - } - } + if ( !signalBufferPtr->push( collectedSignal ) ) + { + TraceModule::get().decrementAtomicVariable( TraceAtomicVariable::QUEUE_CONSUMER_TO_INSPECTION_SIGNALS ); + FWE_LOG_WARN( "Signal Buffer full with ECU " + streamRxID ); } } - return numRequests; } bool diff --git a/src/datamanagement/datainspection/src/diag/OBDOverCANModule.cpp b/src/datamanagement/datainspection/src/diag/OBDOverCANModule.cpp index a10a1201..9bb1be75 100644 --- a/src/datamanagement/datainspection/src/diag/OBDOverCANModule.cpp +++ b/src/datamanagement/datainspection/src/diag/OBDOverCANModule.cpp @@ -50,27 +50,15 @@ OBDOverCANModule::init( SignalBufferPtr signalBufferPtr, uint32_t dtcRequestIntervalSeconds, bool broadcastRequests ) { - // Sanity check - if ( ( pidRequestIntervalSeconds == 0 ) && ( dtcRequestIntervalSeconds == 0 ) ) - { - FWE_LOG_TRACE( "Both PID and DTC interval seconds are set to 0. OBD module will not be initialized" ); - // We should not start the module if both intervals are zero - return false; - } - if ( ( signalBufferPtr.get() == nullptr ) || ( activeDTCBufferPtr.get() == nullptr ) ) { FWE_LOG_ERROR( "Received Buffer nullptr" ); return false; } - else - { - mSignalBufferPtr = signalBufferPtr; - mActiveDTCBufferPtr = activeDTCBufferPtr; - } - // Init the OBD Decoder - mOBDDataDecoder = std::make_shared(); + mSignalBufferPtr = signalBufferPtr; + mActiveDTCBufferPtr = activeDTCBufferPtr; + mOBDDataDecoder = std::make_shared( mDecoderDictionaryPtr ); mGatewayCanInterfaceName = gatewayCanInterfaceName; mPIDRequestIntervalSeconds = pidRequestIntervalSeconds; mDTCRequestIntervalSeconds = dtcRequestIntervalSeconds; @@ -202,7 +190,6 @@ OBDOverCANModule::doWork( void *data ) { // A new decoder manifest arrived. Pass it over to the OBD decoder. std::lock_guard lock( obdModule->mDecoderDictMutex ); - obdModule->mOBDDataDecoder->setDecoderDictionary( obdModule->mDecoderDictionaryPtr ); FWE_LOG_TRACE( "Decoder Manifest set on the OBD Decoder " ); // Reset the atomic state obdModule->mDecoderManifestAvailable.store( false, std::memory_order_relaxed ); @@ -538,6 +525,11 @@ OBDOverCANModule::assignPIDsToECUs() bool OBDOverCANModule::connect() { + if ( ( mPIDRequestIntervalSeconds == 0 ) && ( mDTCRequestIntervalSeconds == 0 ) ) + { + FWE_LOG_TRACE( "Both PID and DTC interval seconds are set to 0. Thread will not be started." ); + return true; + } return start(); } @@ -564,6 +556,50 @@ OBDOverCANModule::isAlive() return true; } +std::vector +OBDOverCANModule::getExternalPIDsToRequest() +{ + std::lock_guard lock( mDecoderDictMutex ); + std::vector pids; + if ( mDecoderDictionaryPtr != nullptr ) + { + for ( const auto &decoder : *mDecoderDictionaryPtr ) + { + pids.push_back( decoder.first ); + } + } + return pids; +} + +void +OBDOverCANModule::setExternalPIDResponse( PID pid, std::vector response ) +{ + std::lock_guard lock( mDecoderDictMutex ); + if ( mDecoderDictionaryPtr == nullptr ) + { + return; + } + if ( mDecoderDictionaryPtr->find( pid ) == mDecoderDictionaryPtr->end() ) + { + FWE_LOG_WARN( "Unexpected PID response: " + std::to_string( pid ) ); + return; + } + EmissionInfo info; + std::vector pids = { pid }; + size_t expectedResponseSize = 2 + mDecoderDictionaryPtr->at( pid ).mSizeInBytes; + if ( response.size() < expectedResponseSize ) + { + FWE_LOG_WARN( "Unexpected PID response length: " + std::to_string( pid ) ); + return; + } + response.resize( expectedResponseSize ); + if ( !mOBDDataDecoder->decodeEmissionPIDs( SID::CURRENT_STATS, pids, response, info ) ) + { + return; + } + OBDOverCANECU::pushPIDs( info, mClock->systemTimeSinceEpochMs(), mSignalBufferPtr, "" ); +} + void OBDOverCANModule::onChangeInspectionMatrix( const std::shared_ptr &inspectionMatrix ) { diff --git a/src/datamanagement/datainspection/src/vehicledatasource/CANDataConsumer.cpp b/src/datamanagement/datainspection/src/vehicledatasource/CANDataConsumer.cpp index 44dbde5c..9751b0eb 100644 --- a/src/datamanagement/datainspection/src/vehicledatasource/CANDataConsumer.cpp +++ b/src/datamanagement/datainspection/src/vehicledatasource/CANDataConsumer.cpp @@ -6,6 +6,7 @@ #include "CANDecoder.h" #include "LoggingModule.h" #include "TraceModule.h" +#include namespace Aws { @@ -14,22 +15,19 @@ namespace IoTFleetWise namespace DataInspection { -CANDataConsumer::CANDataConsumer( CANChannelNumericID channelId, - SignalBufferPtr signalBufferPtr, - CANBufferPtr canBufferPtr ) +CANDataConsumer::CANDataConsumer( SignalBufferPtr signalBufferPtr, CANBufferPtr canBufferPtr ) : mCANBufferPtr{ std::move( canBufferPtr ) } , mSignalBufferPtr{ std::move( signalBufferPtr ) } - , mChannelId{ channelId } { - FWE_LOG_TRACE( "Init Network channel consumer with id: " + std::to_string( channelId ) ); } bool -CANDataConsumer::findDecoderMethod( uint32_t &messageId, +CANDataConsumer::findDecoderMethod( CANChannelNumericID channelId, + uint32_t &messageId, const CANDecoderDictionary::CANMsgDecoderMethodType &decoderMethod, - CANMessageDecoderMethod ¤tMessageDecoderMethod ) const + CANMessageDecoderMethod ¤tMessageDecoderMethod ) { - auto outerMapIt = decoderMethod.find( mChannelId ); + auto outerMapIt = decoderMethod.find( channelId ); if ( outerMapIt != decoderMethod.cend() ) { auto it = outerMapIt->second.find( messageId ); @@ -52,8 +50,11 @@ CANDataConsumer::findDecoderMethod( uint32_t &messageId, } void -CANDataConsumer::processMessage( std::shared_ptr &dictionary, - const struct canfd_frame &message, +CANDataConsumer::processMessage( CANChannelNumericID channelId, + std::shared_ptr &dictionary, + uint32_t messageId, + const uint8_t *data, + size_t dataLength, Timestamp timestamp ) { // Skip if the dictionary was invalidated during message processing: @@ -62,9 +63,9 @@ CANDataConsumer::processMessage( std::shared_ptr &di return; } TraceSection traceSection = - ( ( mChannelId < static_cast( toUType( TraceSection::CAN_DECODER_CYCLE_19 ) - - toUType( TraceSection::CAN_DECODER_CYCLE_0 ) ) ) - ? static_cast( mChannelId + toUType( TraceSection::CAN_DECODER_CYCLE_0 ) ) + ( ( channelId < static_cast( toUType( TraceSection::CAN_DECODER_CYCLE_19 ) - + toUType( TraceSection::CAN_DECODER_CYCLE_0 ) ) ) + ? static_cast( channelId + toUType( TraceSection::CAN_DECODER_CYCLE_0 ) ) : TraceSection::CAN_DECODER_CYCLE_19 ); TraceModule::get().sectionBegin( traceSection ); // get decoderMethod from the decoder dictionary @@ -72,14 +73,13 @@ CANDataConsumer::processMessage( std::shared_ptr &di // a set of signalID specifying which signal to collect const auto &signalIDsToCollect = dictionary->signalIDsToCollect; // check if this CAN message ID on this CAN Channel has the decoder method - uint32_t messageId = message.can_id; CANMessageDecoderMethod currentMessageDecoderMethod; // The value of messageId may be changed by the findDecoderMethod function. This is a // workaround as the cloud as of now does not send extended id messages. // If the decoder method for this message is not found in // decoderMethod dictionary, we check for the same id without the MSB set. // The message id which has a decoderMethod gets passed into messageId - if ( findDecoderMethod( messageId, decoderMethod, currentMessageDecoderMethod ) ) + if ( findDecoderMethod( channelId, messageId, decoderMethod, currentMessageDecoderMethod ) ) { // format to be used for decoding const auto &format = currentMessageDecoderMethod.format; @@ -92,11 +92,11 @@ CANDataConsumer::processMessage( std::shared_ptr &di // prepare the raw CAN Frame struct CollectedCanRawFrame canRawFrame; canRawFrame.frameID = messageId; - canRawFrame.channelId = mChannelId; + canRawFrame.channelId = channelId; canRawFrame.receiveTime = timestamp; // CollectedCanRawFrame receive up to 64 CAN Raw Bytes - canRawFrame.size = std::min( static_cast( message.len ), MAX_CAN_FRAME_BYTE_SIZE ); - std::copy( message.data, message.data + canRawFrame.size, canRawFrame.data.begin() ); + canRawFrame.size = std::min( static_cast( dataLength ), MAX_CAN_FRAME_BYTE_SIZE ); + std::copy( data, data + canRawFrame.size, canRawFrame.data.begin() ); // Push raw CAN Frame to the Buffer for next stage to consume // Note buffer is lock_free buffer and multiple Vehicle Data Source Instance could push // data to it. @@ -120,8 +120,7 @@ CANDataConsumer::processMessage( std::shared_ptr &di if ( format.isValid() ) { std::vector decodedSignals; - if ( CANDecoder::decodeCANMessage( - message.data, message.len, format, signalIDsToCollect, decodedSignals ) ) + if ( CANDecoder::decodeCANMessage( data, dataLength, format, signalIDsToCollect, decodedSignals ) ) { for ( auto const &signal : decodedSignals ) { @@ -178,7 +177,7 @@ CANDataConsumer::processMessage( std::shared_ptr &di // The CAN Message format is not valid, report as warning FWE_LOG_WARN( "CANMessageFormat Invalid for format message id: " + std::to_string( format.mMessageID ) + " can message id: " + std::to_string( messageId ) + - " on CAN Channel Id: " + std::to_string( mChannelId ) ); + " on CAN Channel Id: " + std::to_string( channelId ) ); } TraceModule::get().sectionEnd( traceSection ); } diff --git a/src/datamanagement/datainspection/src/vehicledatasource/CANDataSource.cpp b/src/datamanagement/datainspection/src/vehicledatasource/CANDataSource.cpp index 81af3ca5..454e5e15 100644 --- a/src/datamanagement/datainspection/src/vehicledatasource/CANDataSource.cpp +++ b/src/datamanagement/datainspection/src/vehicledatasource/CANDataSource.cpp @@ -212,7 +212,12 @@ CANDataSource::doWork( void *data ) : TraceVariable::READ_SOCKET_FRAMES_19, dataSource->mReceivedMessages ); std::lock_guard lock( dataSource->mDecoderDictMutex ); - dataSource->mConsumer.processMessage( dataSource->mDecoderDictionary, frame[i], timestamp ); + dataSource->mConsumer.processMessage( dataSource->mChannelId, + dataSource->mDecoderDictionary, + frame[i].can_id, + frame[i].data, + frame[i].len, + timestamp ); } } if ( nmsgs < PARALLEL_RECEIVED_FRAMES_FROM_KERNEL ) diff --git a/src/datamanagement/datainspection/src/vehicledatasource/ExternalCANDataSource.cpp b/src/datamanagement/datainspection/src/vehicledatasource/ExternalCANDataSource.cpp new file mode 100644 index 00000000..06a98c97 --- /dev/null +++ b/src/datamanagement/datainspection/src/vehicledatasource/ExternalCANDataSource.cpp @@ -0,0 +1,74 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Includes +#include "ExternalCANDataSource.h" +#include "EnumUtility.h" +#include "LoggingModule.h" +#include "TraceModule.h" + +namespace Aws +{ +namespace IoTFleetWise +{ +namespace DataInspection +{ +using namespace Aws::IoTFleetWise::Platform::Utility; + +ExternalCANDataSource::ExternalCANDataSource( CANDataConsumer &consumer ) + : mConsumer{ consumer } +{ +} + +void +ExternalCANDataSource::ingestMessage( CANChannelNumericID channelId, + Timestamp timestamp, + uint32_t messageId, + const std::vector &data ) +{ + std::lock_guard lock( mDecoderDictMutex ); + if ( mDecoderDictionary == nullptr ) + { + return; + } + if ( timestamp == 0 ) + { + TraceModule::get().incrementVariable( TraceVariable::CAN_POLLING_TIMESTAMP_COUNTER ); + timestamp = mClock->systemTimeSinceEpochMs(); + } + if ( timestamp < mLastFrameTime ) + { + TraceModule::get().incrementAtomicVariable( TraceAtomicVariable::NOT_TIME_MONOTONIC_FRAMES ); + } + mLastFrameTime = timestamp; + unsigned traceFrames = channelId + toUType( TraceVariable::READ_SOCKET_FRAMES_0 ); + TraceModule::get().incrementVariable( + ( traceFrames < static_cast( toUType( TraceVariable::READ_SOCKET_FRAMES_19 ) ) ) + ? static_cast( traceFrames ) + : TraceVariable::READ_SOCKET_FRAMES_19 ); + mConsumer.processMessage( channelId, mDecoderDictionary, messageId, data.data(), data.size(), timestamp ); +} + +void +ExternalCANDataSource::onChangeOfActiveDictionary( ConstDecoderDictionaryConstPtr &dictionary, + VehicleDataSourceProtocol networkProtocol ) +{ + if ( networkProtocol != VehicleDataSourceProtocol::RAW_SOCKET ) + { + return; + } + std::lock_guard lock( mDecoderDictMutex ); + mDecoderDictionary = std::dynamic_pointer_cast( dictionary ); + if ( dictionary == nullptr ) + { + FWE_LOG_TRACE( "Decoder dictionary removed" ); + } + else + { + FWE_LOG_TRACE( "Decoder dictionary updated" ); + } +} + +} // namespace DataInspection +} // namespace IoTFleetWise +} // namespace Aws diff --git a/src/datamanagement/datainspection/test/CANDataSourceTest.cpp b/src/datamanagement/datainspection/test/CANDataSourceTest.cpp index 2e8a4f41..eef91939 100644 --- a/src/datamanagement/datainspection/test/CANDataSourceTest.cpp +++ b/src/datamanagement/datainspection/test/CANDataSourceTest.cpp @@ -180,7 +180,7 @@ TEST_F( CANDataSourceTest, invalidInit ) auto signalBufferPtr = std::make_shared( 10 ); auto canRawBufferPtr = std::make_shared( 10 ); - CANDataConsumer consumer{ INVALID_CAN_SOURCE_NUMERIC_ID, signalBufferPtr, canRawBufferPtr }; + CANDataConsumer consumer{ signalBufferPtr, canRawBufferPtr }; CANDataSource dataSource{ INVALID_CAN_SOURCE_NUMERIC_ID, CanTimestampType::KERNEL_HARDWARE_TIMESTAMP, "vcan0", false, 100, consumer }; ASSERT_FALSE( dataSource.init() ); @@ -192,7 +192,7 @@ TEST_F( CANDataSourceTest, testNoDecoderDictionary ) auto signalBufferPtr = std::make_shared( 10 ); auto canRawBufferPtr = std::make_shared( 10 ); - CANDataConsumer consumer{ 0, signalBufferPtr, canRawBufferPtr }; + CANDataConsumer consumer{ signalBufferPtr, canRawBufferPtr }; CANDataSource dataSource{ 0, CanTimestampType::KERNEL_HARDWARE_TIMESTAMP, "vcan0", false, 100, consumer }; ASSERT_TRUE( dataSource.init() ); ASSERT_TRUE( dataSource.isAlive() ); @@ -209,7 +209,7 @@ TEST_F( CANDataSourceTest, testValidDecoderDictionary ) auto signalBufferPtr = std::make_shared( 10 ); auto canRawBufferPtr = std::make_shared( 10 ); - CANDataConsumer consumer{ 0, signalBufferPtr, canRawBufferPtr }; + CANDataConsumer consumer{ signalBufferPtr, canRawBufferPtr }; CANDataSource dataSource{ 0, CanTimestampType::KERNEL_HARDWARE_TIMESTAMP, "vcan0", false, 100, consumer }; ASSERT_TRUE( dataSource.init() ); ASSERT_TRUE( dataSource.isAlive() ); @@ -256,7 +256,7 @@ TEST_F( CANDataSourceTest, testCanFDSocketMode ) auto signalBufferPtr = std::make_shared( 10 ); auto canRawBufferPtr = std::make_shared( 10 ); - CANDataConsumer consumer{ 0, signalBufferPtr, canRawBufferPtr }; + CANDataConsumer consumer{ signalBufferPtr, canRawBufferPtr }; CANDataSource dataSource{ 0, CanTimestampType::KERNEL_SOFTWARE_TIMESTAMP, "vcan0", false, 100, consumer }; ASSERT_TRUE( dataSource.init() ); ASSERT_TRUE( dataSource.isAlive() ); @@ -290,7 +290,7 @@ TEST_F( CANDataSourceTest, testExtractExtendedID ) auto signalBufferPtr = std::make_shared( 10 ); auto canRawBufferPtr = std::make_shared( 10 ); - CANDataConsumer consumer{ 0, signalBufferPtr, canRawBufferPtr }; + CANDataConsumer consumer{ signalBufferPtr, canRawBufferPtr }; CANDataSource dataSource{ 0, CanTimestampType::KERNEL_HARDWARE_TIMESTAMP, "vcan0", false, 100, consumer }; ASSERT_TRUE( dataSource.init() ); ASSERT_TRUE( dataSource.isAlive() ); diff --git a/src/datamanagement/datainspection/test/ExternalCANDataSourceTest.cpp b/src/datamanagement/datainspection/test/ExternalCANDataSourceTest.cpp new file mode 100644 index 00000000..0dc509f4 --- /dev/null +++ b/src/datamanagement/datainspection/test/ExternalCANDataSourceTest.cpp @@ -0,0 +1,216 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#include "ExternalCANDataSource.h" +#include +#include + +using namespace Aws::IoTFleetWise::DataInspection; + +static void +sendTestMessage( ExternalCANDataSource &dataSource, + CANChannelNumericID channelId, + uint32_t messageId = 0x123, + Timestamp timestamp = 0 ) +{ + std::vector data( 8 ); + for ( uint8_t i = 0; i < 8; ++i ) + { + data[i] = i; + } + dataSource.ingestMessage( channelId, timestamp, messageId, data ); +} + +static void +sendTestFDMessage( ExternalCANDataSource &dataSource, + CANChannelNumericID channelId, + uint32_t messageId = 0x123, + Timestamp timestamp = 0 ) +{ + std::vector data( 64 ); + for ( uint8_t i = 0; i < 64; ++i ) + { + data[i] = i; + } + dataSource.ingestMessage( channelId, timestamp, messageId, data ); +} + +static void +sendTestMessageExtendedID( ExternalCANDataSource &dataSource, + CANChannelNumericID channelId, + uint32_t messageId = 0x123, + Timestamp timestamp = 0 ) +{ + std::vector data( 8 ); + for ( uint8_t i = 0; i < 8; ++i ) + { + data[i] = i; + } + dataSource.ingestMessage( channelId, timestamp, messageId | CAN_EFF_FLAG, data ); +} + +class ExternalCANDataSourceTest : public ::testing::Test +{ +protected: + void + SetUp() override + { + std::unordered_map frameMap; + CANMessageDecoderMethod decoderMethod; + decoderMethod.collectType = CANMessageCollectType::RAW_AND_DECODE; + + decoderMethod.format.mMessageID = 0x123; + decoderMethod.format.mSizeInBytes = 8; + + CANSignalFormat sigFormat1; + sigFormat1.mSignalID = 1; + sigFormat1.mIsBigEndian = true; + sigFormat1.mIsSigned = true; + sigFormat1.mFirstBitPosition = 24; + sigFormat1.mSizeInBits = 30; + sigFormat1.mOffset = 0.0; + sigFormat1.mFactor = 1.0; + + CANSignalFormat sigFormat2; + sigFormat2.mSignalID = 7; + sigFormat2.mIsBigEndian = true; + sigFormat2.mIsSigned = true; + sigFormat2.mFirstBitPosition = 56; + sigFormat2.mSizeInBits = 31; + sigFormat2.mOffset = 0.0; + sigFormat2.mFactor = 1.0; + + decoderMethod.format.mSignals.push_back( sigFormat1 ); + decoderMethod.format.mSignals.push_back( sigFormat2 ); + frameMap[0x123] = decoderMethod; + mDictionary = std::make_shared(); + mDictionary->canMessageDecoderMethod[0] = frameMap; + mDictionary->signalIDsToCollect.emplace( 1 ); + mDictionary->signalIDsToCollect.emplace( 7 ); + } + + void + TearDown() override + { + } + + std::shared_ptr mDictionary; +}; + +TEST_F( ExternalCANDataSourceTest, testNoDecoderDictionary ) +{ + auto signalBufferPtr = std::make_shared( 10 ); + auto canRawBufferPtr = std::make_shared( 10 ); + + CANDataConsumer consumer{ signalBufferPtr, canRawBufferPtr }; + ExternalCANDataSource dataSource{ consumer }; + CollectedSignal signal; + sendTestMessage( dataSource, 0 ); + ASSERT_FALSE( signalBufferPtr->pop( signal ) ); + CollectedCanRawFrame frame; + ASSERT_FALSE( canRawBufferPtr->pop( frame ) ); +} + +TEST_F( ExternalCANDataSourceTest, testValidDecoderDictionary ) +{ + auto signalBufferPtr = std::make_shared( 10 ); + auto canRawBufferPtr = std::make_shared( 10 ); + + CANDataConsumer consumer{ signalBufferPtr, canRawBufferPtr }; + ExternalCANDataSource dataSource{ consumer }; + dataSource.onChangeOfActiveDictionary( mDictionary, VehicleDataSourceProtocol::RAW_SOCKET ); + CollectedSignal signal; + sendTestMessage( dataSource, 0 ); + ASSERT_TRUE( signalBufferPtr->pop( signal ) ); + ASSERT_EQ( signal.value.type, SignalType::DOUBLE ); + ASSERT_EQ( signal.signalID, 1 ); + ASSERT_DOUBLE_EQ( signal.value.value.doubleVal, 0x10203 ); + ASSERT_TRUE( signalBufferPtr->pop( signal ) ); + ASSERT_EQ( signal.signalID, 7 ); + ASSERT_EQ( signal.value.type, SignalType::DOUBLE ); + ASSERT_DOUBLE_EQ( signal.value.value.doubleVal, 0x4050607 ); + CollectedCanRawFrame frame; + ASSERT_TRUE( canRawBufferPtr->pop( frame ) ); + ASSERT_EQ( frame.channelId, 0 ); + ASSERT_EQ( frame.frameID, 0x123 ); + ASSERT_EQ( frame.size, 8 ); + for ( auto i = 0; i < 8; i++ ) + { + ASSERT_EQ( frame.data[i], i ); + } + + // Test message a different message ID and non-monotonic time is not received + sendTestMessage( dataSource, 0, 0x456, 1 ); + ASSERT_FALSE( signalBufferPtr->pop( signal ) || canRawBufferPtr->pop( frame ) ); + + // Test invalidation of decoder dictionary + dataSource.onChangeOfActiveDictionary( nullptr, VehicleDataSourceProtocol::RAW_SOCKET ); + sendTestMessage( dataSource, 0 ); + ASSERT_FALSE( signalBufferPtr->pop( signal ) || canRawBufferPtr->pop( frame ) ); + // Check it ignores dictionaries for other protocols + dataSource.onChangeOfActiveDictionary( mDictionary, VehicleDataSourceProtocol::OBD ); + sendTestMessage( dataSource, 0 ); + ASSERT_FALSE( signalBufferPtr->pop( signal ) || canRawBufferPtr->pop( frame ) ); +} + +TEST_F( ExternalCANDataSourceTest, testCanFDSocketMode ) +{ + auto signalBufferPtr = std::make_shared( 10 ); + auto canRawBufferPtr = std::make_shared( 10 ); + + CANDataConsumer consumer{ signalBufferPtr, canRawBufferPtr }; + ExternalCANDataSource dataSource{ consumer }; + dataSource.onChangeOfActiveDictionary( mDictionary, VehicleDataSourceProtocol::RAW_SOCKET ); + CollectedSignal signal; + sendTestFDMessage( dataSource, 0 ); + ASSERT_TRUE( signalBufferPtr->pop( signal ) ); + ASSERT_EQ( signal.value.type, SignalType::DOUBLE ); + ASSERT_EQ( signal.signalID, 1 ); + ASSERT_DOUBLE_EQ( signal.value.value.doubleVal, 0x10203 ); + ASSERT_TRUE( signalBufferPtr->pop( signal ) ); + ASSERT_EQ( signal.signalID, 7 ); + ASSERT_EQ( signal.value.type, SignalType::DOUBLE ); + ASSERT_DOUBLE_EQ( signal.value.value.doubleVal, 0x4050607 ); + ASSERT_FALSE( signalBufferPtr->pop( signal ) ); + CollectedCanRawFrame frame; + ASSERT_TRUE( canRawBufferPtr->pop( frame ) ); + ASSERT_EQ( frame.channelId, 0 ); + ASSERT_EQ( frame.frameID, 0x123 ); + ASSERT_EQ( frame.size, 64 ); + for ( auto i = 0; i < 64; i++ ) + { + ASSERT_EQ( frame.data[i], i ); + } + ASSERT_FALSE( signalBufferPtr->pop( signal ) ); +} + +TEST_F( ExternalCANDataSourceTest, testExtractExtendedID ) +{ + auto signalBufferPtr = std::make_shared( 10 ); + auto canRawBufferPtr = std::make_shared( 10 ); + + CANDataConsumer consumer{ signalBufferPtr, canRawBufferPtr }; + ExternalCANDataSource dataSource{ consumer }; + dataSource.onChangeOfActiveDictionary( mDictionary, VehicleDataSourceProtocol::RAW_SOCKET ); + CollectedSignal signal; + sendTestMessageExtendedID( dataSource, 0 ); + ASSERT_TRUE( signalBufferPtr->pop( signal ) ); + ASSERT_EQ( signal.value.type, SignalType::DOUBLE ); + ASSERT_EQ( signal.signalID, 1 ); + ASSERT_DOUBLE_EQ( signal.value.value.doubleVal, 0x10203 ); + ASSERT_TRUE( signalBufferPtr->pop( signal ) ); + ASSERT_EQ( signal.signalID, 7 ); + ASSERT_EQ( signal.value.type, SignalType::DOUBLE ); + ASSERT_DOUBLE_EQ( signal.value.value.doubleVal, 0x4050607 ); + ASSERT_FALSE( signalBufferPtr->pop( signal ) ); + CollectedCanRawFrame frame; + ASSERT_TRUE( canRawBufferPtr->pop( frame ) ); + ASSERT_EQ( frame.channelId, 0 ); + ASSERT_EQ( frame.frameID, 0x123 ); + ASSERT_EQ( frame.size, 8 ); + for ( auto i = 0; i < 8; i++ ) + { + ASSERT_EQ( frame.data[i], i ); + } + ASSERT_FALSE( signalBufferPtr->pop( signal ) ); +} diff --git a/src/datamanagement/datainspection/test/OBDOverCANModuleTest.cpp b/src/datamanagement/datainspection/test/OBDOverCANModuleTest.cpp index 2144df33..a00e451f 100644 --- a/src/datamanagement/datainspection/test/OBDOverCANModuleTest.cpp +++ b/src/datamanagement/datainspection/test/OBDOverCANModuleTest.cpp @@ -295,30 +295,21 @@ class OBDOverCANModuleTest : public ::testing::Test TEST_F( OBDOverCANModuleTest, OBDOverCANModuleInitFailure ) { - constexpr uint32_t obdPIDRequestInterval = 0; // 0 seconds - constexpr uint32_t obdDTCRequestInterval = 0; // 0 seconds - ASSERT_FALSE( obdModule.init( - signalBufferPtr, activeDTCBufferPtr, "vcan0", obdPIDRequestInterval, obdDTCRequestInterval, false ) ); + constexpr uint32_t obdPIDRequestInterval = 2; // seconds + constexpr uint32_t obdDTCRequestInterval = 2; // seconds + ASSERT_FALSE( obdModule.init( nullptr, nullptr, "vcan0", obdPIDRequestInterval, obdDTCRequestInterval, false ) ); } TEST_F( OBDOverCANModuleTest, OBDOverCANModuleInitTestSuccess ) { - constexpr uint32_t obdPIDRequestInterval = 1; // 1 second - constexpr uint32_t obdDTCRequestInterval = 1; // 1 seconds + constexpr uint32_t obdPIDRequestInterval = 2; // seconds + constexpr uint32_t obdDTCRequestInterval = 2; // seconds ASSERT_TRUE( obdModule.init( signalBufferPtr, activeDTCBufferPtr, "vcan0", obdPIDRequestInterval, obdDTCRequestInterval, false ) ); ASSERT_TRUE( obdModule.connect() ); ASSERT_TRUE( obdModule.disconnect() ); } -TEST_F( OBDOverCANModuleTest, OBDOverCANModuleInitTestFailure ) -{ - constexpr uint32_t obdPIDRequestInterval = 0; // 2 seconds - constexpr uint32_t obdDTCRequestInterval = 0; // 2 seconds - ASSERT_FALSE( obdModule.init( - signalBufferPtr, activeDTCBufferPtr, "vcan0", obdPIDRequestInterval, obdDTCRequestInterval, false ) ); -} - TEST_F( OBDOverCANModuleTest, OBDOverCANModuleAndDecoderManifestLifecycle ) { // If no decoder manifest is available, the module should be sleeping and not sending @@ -344,8 +335,7 @@ TEST_F( OBDOverCANModuleTest, OBDOverCANModuleAndDecoderManifestLifecycle ) signalBufferPtr, activeDTCBufferPtr, "vcan0", obdPIDRequestInterval, obdDTCRequestInterval, false ) ); ASSERT_TRUE( obdModule.connect() ); // No Requests should be seen on the bus as it hasn't received a valid decoder dictionary yet. - std::this_thread::sleep_for( std::chrono::seconds( obdPIDRequestInterval ) ); - ASSERT_FALSE( engineECU.receivePDU( ecmRxPDUData ) ); + DELAY_ASSERT_FALSE( engineECU.receivePDU( ecmRxPDUData ) ); ASSERT_TRUE( engineECU.disconnect() ); ASSERT_TRUE( obdModule.disconnect() ); } @@ -562,15 +552,8 @@ TEST_F( OBDOverCANModuleTest, DecoderDictionaryUpdatePIDsToCollectTest ) // Update Decoder Dictionary to collect no signals. // publish the new decoder dictionary to OBD module obdModule.onChangeOfActiveDictionary( nullptr, VehicleDataSourceProtocol::OBD ); - std::this_thread::sleep_for( std::chrono::seconds( obdPIDRequestInterval ) ); - // wait for one cycle and clear out the signal buffer - while ( !obdModule.getSignalBufferPtr()->empty() ) - { - obdModule.getSignalBufferPtr()->pop( signal ); - } - std::this_thread::sleep_for( std::chrono::seconds( obdPIDRequestInterval ) ); // We shall not receive any PIDs as decoder dictionary is empty - ASSERT_TRUE( obdModule.getSignalBufferPtr()->empty() ); + DELAY_ASSERT_FALSE( obdModule.getSignalBufferPtr()->pop( signal ) ); } TEST_F( OBDOverCANModuleTest, RequestEmissionPIDAndDTCFromExtendedIDECUTest ) @@ -795,3 +778,80 @@ TEST_F( OBDOverCANModuleTest, BroadcastRequestsExtendedIDs ) WAIT_ASSERT_TRUE( obdModule.getSignalBufferPtr()->pop( signal ) ); ASSERT_DOUBLE_EQ( signal.value.value.doubleVal, expectedPIDSignalValue[signal.signalID] ); } + +TEST_F( OBDOverCANModuleTest, getExternalPIDsToRequest ) +{ + // Request PIDs every 2 seconds and no DTC request + constexpr uint32_t obdPIDRequestInterval = 0; // no PID request + constexpr uint32_t obdDTCRequestInterval = 0; // no DTC request + ASSERT_TRUE( obdModule.init( + signalBufferPtr, activeDTCBufferPtr, "vcan0", obdPIDRequestInterval, obdDTCRequestInterval, false ) ); + ASSERT_TRUE( obdModule.connect() ); + ASSERT_EQ( obdModule.getExternalPIDsToRequest(), std::vector() ); + // Create decoder dictionary + auto decoderDictPtr = initDecoderDictionary(); + // publish decoder dictionary to OBD module + obdModule.onChangeOfActiveDictionary( decoderDictPtr, VehicleDataSourceProtocol::OBD ); + auto getPids = [&]() -> std::unordered_set { + auto pids = obdModule.getExternalPIDsToRequest(); + return std::unordered_set( pids.begin(), pids.end() ); + }; + WAIT_ASSERT_EQ( + getPids(), + std::unordered_set( + { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, + 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, + 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, + 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, + 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xC0, 0xC1 } ) ); +} + +TEST_F( OBDOverCANModuleTest, setExternalPIDResponse ) +{ + // Request PIDs every 2 seconds and no DTC request + constexpr uint32_t obdPIDRequestInterval = 0; // no PID request + constexpr uint32_t obdDTCRequestInterval = 0; // no DTC request + ASSERT_TRUE( obdModule.init( + signalBufferPtr, activeDTCBufferPtr, "vcan0", obdPIDRequestInterval, obdDTCRequestInterval, false ) ); + ASSERT_TRUE( obdModule.connect() ); + // Check before decoder manifest arrives: + obdModule.setExternalPIDResponse( static_cast( EmissionPIDs::ENGINE_LOAD ), + { 0x41, 0x04, 153, 0x00, 0x00, 0x00, 0x00 } ); + // Create decoder dictionary + auto decoderDictPtr = initDecoderDictionary(); + // publish decoder dictionary to OBD module + obdModule.onChangeOfActiveDictionary( decoderDictPtr, VehicleDataSourceProtocol::OBD ); + CollectedSignal signal; + DELAY_ASSERT_FALSE( obdModule.getSignalBufferPtr()->pop( signal ) ); + // Check unknown PID: + obdModule.setExternalPIDResponse( 0xCF, { 0x41, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00 } ); + // Check incorrect length: + obdModule.setExternalPIDResponse( static_cast( EmissionPIDs::ENGINE_LOAD ), { 0x41, 0x04 } ); + // Check negative response: + obdModule.setExternalPIDResponse( static_cast( EmissionPIDs::ENGINE_LOAD ), { 0x7F, 0x01, 0x04 } ); + // Check valid PID values: + obdModule.setExternalPIDResponse( static_cast( EmissionPIDs::ENGINE_LOAD ), + { 0x41, 0x04, 153, 0x00, 0x00, 0x00, 0x00 } ); + obdModule.setExternalPIDResponse( static_cast( EmissionPIDs::ENGINE_COOLANT_TEMPERATURE ), + { 0x41, 0x05, 110, 0x00, 0x00, 0x00, 0x00 } ); + obdModule.setExternalPIDResponse( static_cast( EmissionPIDs::VEHICLE_SPEED ), + { 0x41, 0x0D, 35, 0x00, 0x00, 0x00, 0x00 } ); + + // Expected value for PID signals + std::map expectedPIDSignalValue = { + { toUType( EmissionPIDs::ENGINE_LOAD ), 60 }, + { toUType( EmissionPIDs::ENGINE_COOLANT_TEMPERATURE ), 70 }, + { toUType( EmissionPIDs::VEHICLE_SPEED ), 35 } }; + // Verify all PID Signals are correctly decoded + WAIT_ASSERT_TRUE( obdModule.getSignalBufferPtr()->pop( signal ) ); + ASSERT_DOUBLE_EQ( signal.value.value.doubleVal, expectedPIDSignalValue[signal.signalID] ); + ASSERT_TRUE( obdModule.getSignalBufferPtr()->pop( signal ) ); + ASSERT_DOUBLE_EQ( signal.value.value.doubleVal, expectedPIDSignalValue[signal.signalID] ); + ASSERT_TRUE( obdModule.getSignalBufferPtr()->pop( signal ) ); + ASSERT_DOUBLE_EQ( signal.value.value.doubleVal, expectedPIDSignalValue[signal.signalID] ); +} diff --git a/src/datamanagement/datamanager/include/CollectionSchemeManager.h b/src/datamanagement/datamanager/include/CollectionSchemeManager.h index ac6c7174..1bfce317 100644 --- a/src/datamanagement/datamanager/include/CollectionSchemeManager.h +++ b/src/datamanagement/datamanager/include/CollectionSchemeManager.h @@ -146,6 +146,12 @@ class CollectionSchemeManager : public ICollectionSchemeManager, mSchemaListenerPtr = collectionSchemeIngestionListenerPtr; } + /** + * @brief Returns the current list of collection scheme ARNs + * @return List of collection scheme ARNs + */ + std::vector getCollectionSchemeArns(); + private: using ThreadListeners::notifyListeners; using ThreadListeners::notifyListeners; diff --git a/src/datamanagement/datamanager/src/CollectionSchemeManager.cpp b/src/datamanagement/datamanager/src/CollectionSchemeManager.cpp index c07447dc..3aaa8f79 100644 --- a/src/datamanagement/datamanager/src/CollectionSchemeManager.cpp +++ b/src/datamanagement/datamanager/src/CollectionSchemeManager.cpp @@ -521,6 +521,21 @@ CollectionSchemeManager::rebuildMapsandTimeLine( const TimePoint &currTime ) return ret; } +std::vector +CollectionSchemeManager::getCollectionSchemeArns() +{ + std::lock_guard lock( mSchemaUpdateMutex ); + std::vector collectionSchemeArns; + if ( mCollectionSchemeList != nullptr ) + { + for ( auto &collectionScheme : mCollectionSchemeList->getCollectionSchemes() ) + { + collectionSchemeArns.push_back( collectionScheme->getCollectionSchemeID() ); + } + } + return collectionSchemeArns; +} + /* * This function goes through collectionSchemeList and updates mIdleCollectionSchemeMap, mEnabledCollectionSchemeMap * and mTimeLine; diff --git a/src/datamanagement/datamanager/src/Schema.cpp b/src/datamanagement/datamanager/src/Schema.cpp index 0c3d01c9..bbb61a42 100644 --- a/src/datamanagement/datamanager/src/Schema.cpp +++ b/src/datamanagement/datamanager/src/Schema.cpp @@ -48,7 +48,7 @@ Schema::sendCheckin( const std::vector &documentARNs ) for ( auto const &doc : documentARNs ) { // Note: a class member is used to store the serialized proto output to avoid heap fragmentation - mProtoCheckinMsg.add_document_arns( doc ); + mProtoCheckinMsg.add_document_sync_ids( doc ); } // Add the timestamp @@ -86,15 +86,15 @@ Schema::transmitCheckin() // Trace log for more verbose Checkin Info std::string checkinDebugString; checkinDebugString = "Checkin data: timestamp: " + std::to_string( mProtoCheckinMsg.timestamp_ms_epoch() ); - checkinDebugString += " with " + std::to_string( mProtoCheckinMsg.document_arns_size() ) + " documents: ["; + checkinDebugString += " with " + std::to_string( mProtoCheckinMsg.document_sync_ids_size() ) + " documents: ["; - for ( int i = 0; i < mProtoCheckinMsg.document_arns_size(); i++ ) + for ( int i = 0; i < mProtoCheckinMsg.document_sync_ids_size(); i++ ) { if ( i > 0 ) { checkinDebugString += ", "; } - checkinDebugString += mProtoCheckinMsg.document_arns( i ); + checkinDebugString += mProtoCheckinMsg.document_sync_ids( i ); } checkinDebugString += "]"; diff --git a/src/datamanagement/datamanager/test/CollectionSchemeManagerTest.cpp b/src/datamanagement/datamanager/test/CollectionSchemeManagerTest.cpp index 82a09706..930c78b0 100644 --- a/src/datamanagement/datamanager/test/CollectionSchemeManagerTest.cpp +++ b/src/datamanagement/datamanager/test/CollectionSchemeManagerTest.cpp @@ -218,3 +218,42 @@ TEST( CollectionSchemeManagerTest, MockProducerTest ) WAIT_ASSERT_TRUE( test.disconnect() ); } + +TEST( CollectionSchemeManagerTest, getCollectionSchemeArns ) +{ + CANInterfaceIDTranslator canIDTranslator; + CollectionSchemeManagerTest test; + test.init( 50, nullptr, canIDTranslator ); + test.myRegisterListener(); + ASSERT_TRUE( test.connect() ); + + ASSERT_EQ( test.getCollectionSchemeArns(), std::vector() ); + + /* build DMs */ + IDecoderManifestPtr testDM1 = std::make_shared( "DM1" ); + std::vector testList1; + + /* build collectionScheme list1 */ + std::shared_ptr testClock = ClockHandler::getClock(); + /* mock currTime, and 3 collectionSchemes */ + TimePoint currTime = testClock->timeSinceEpoch(); + Timestamp startTime = currTime.systemTimeMs + SECOND_TO_MILLISECOND( 1 ); + Timestamp stopTime = startTime + SECOND_TO_MILLISECOND( 25 ); + ICollectionSchemePtr collectionScheme = + std::make_shared( "COLLECTIONSCHEME1", "DM1", startTime, stopTime ); + testList1.emplace_back( collectionScheme ); + + std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); + /* create ICollectionSchemeList */ + test.mPlTest = std::make_shared( testList1 ); + /* sending lists and dm to PM */ + test.mDmTest = testDM1; + test.myInvokeDecoderManifest(); + std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); + test.myInvokeCollectionScheme(); + + WAIT_ASSERT_EQ( test.getCollectionSchemeArns(), std::vector( { "COLLECTIONSCHEME1" } ) ); + + /* stopping main thread servicing a collectionScheme ending in 25 seconds */ + WAIT_ASSERT_TRUE( test.disconnect() ); +} diff --git a/src/datamanagement/datamanager/test/SchemaTest.cpp b/src/datamanagement/datamanager/test/SchemaTest.cpp index fb87f569..994e04b0 100644 --- a/src/datamanagement/datamanager/test/SchemaTest.cpp +++ b/src/datamanagement/datamanager/test/SchemaTest.cpp @@ -101,14 +101,14 @@ class CheckinTest : public ::testing::Test ASSERT_TRUE( sentCheckin.ParseFromArray( buf, (int)size ) ); // Make sure the size of the documents is the same - ASSERT_EQ( sentCheckin.document_arns_size(), sampleDocList.size() ); + ASSERT_EQ( sentCheckin.document_sync_ids_size(), sampleDocList.size() ); // Iterate over all the documents found in the checkin - for ( int i = 0; i < sentCheckin.document_arns_size(); i++ ) + for ( int i = 0; i < sentCheckin.document_sync_ids_size(); i++ ) { - ASSERT_GE( documentSet.count( sentCheckin.document_arns( i ) ), 1 ); + ASSERT_GE( documentSet.count( sentCheckin.document_sync_ids( i ) ), 1 ); // Erase the entry from the multiset - documentSet.erase( documentSet.find( sentCheckin.document_arns( i ) ) ); + documentSet.erase( documentSet.find( sentCheckin.document_sync_ids( i ) ) ); } // Make sure we have erased all the elements from our set @@ -404,9 +404,9 @@ TEST( SchemaTest, CollectionSchemeIngestionList ) // Make a list of collectionScheme ARNs std::vector collectionSchemeARNs = { "P1", "P2", "P3" }; - p1->set_campaign_arn( collectionSchemeARNs[0] ); - p2->set_campaign_arn( collectionSchemeARNs[1] ); - p3->set_campaign_arn( collectionSchemeARNs[2] ); + p1->set_campaign_sync_id( collectionSchemeARNs[0] ); + p2->set_campaign_sync_id( collectionSchemeARNs[1] ); + p3->set_campaign_sync_id( collectionSchemeARNs[2] ); // Serialize the protobuffer to a string to avoid malloc with cstyle arrays std::string protoSerializedBuffer; @@ -434,7 +434,7 @@ TEST( SchemaTest, CollectionSchemeIngestionHeartBeat ) { // Create a collection scheme Proto Message CollectionSchemesMsg::CollectionScheme collectionSchemeTestMessage; - collectionSchemeTestMessage.set_campaign_arn( "arn:aws:iam::2.23606797749:user/Development/product_1234/*" ); + collectionSchemeTestMessage.set_campaign_sync_id( "arn:aws:iam::2.23606797749:user/Development/product_1234/*" ); collectionSchemeTestMessage.set_decoder_manifest_sync_id( "model_manifest_12" ); collectionSchemeTestMessage.set_start_time_ms_epoch( 1621448160000 ); collectionSchemeTestMessage.set_expiry_time_ms_epoch( 2621448160000 ); @@ -578,7 +578,7 @@ TEST( SchemaTest, SchemaCollectionEventBased ) { // Create a collection scheme Proto Message CollectionSchemesMsg::CollectionScheme collectionSchemeTestMessage; - collectionSchemeTestMessage.set_campaign_arn( "arn:aws:iam::2.23606797749:user/Development/product_1235/*" ); + collectionSchemeTestMessage.set_campaign_sync_id( "arn:aws:iam::2.23606797749:user/Development/product_1235/*" ); collectionSchemeTestMessage.set_decoder_manifest_sync_id( "model_manifest_13" ); collectionSchemeTestMessage.set_start_time_ms_epoch( 162144816000 ); collectionSchemeTestMessage.set_expiry_time_ms_epoch( 262144816000 ); @@ -820,7 +820,7 @@ TEST( SchemaTest, SchemaGeohashFunctionNode ) { // Create a collection scheme Proto Message CollectionSchemesMsg::CollectionScheme collectionSchemeTestMessage; - collectionSchemeTestMessage.set_campaign_arn( "arn:aws:iam::2.23606797749:user/Development/product_1235/*" ); + collectionSchemeTestMessage.set_campaign_sync_id( "arn:aws:iam::2.23606797749:user/Development/product_1235/*" ); collectionSchemeTestMessage.set_decoder_manifest_sync_id( "model_manifest_13" ); collectionSchemeTestMessage.set_start_time_ms_epoch( 162144816000 ); collectionSchemeTestMessage.set_expiry_time_ms_epoch( 262144816000 ); diff --git a/src/executionmanagement/CMakeLists.txt b/src/executionmanagement/CMakeLists.txt index cb84620e..a80cac59 100644 --- a/src/executionmanagement/CMakeLists.txt +++ b/src/executionmanagement/CMakeLists.txt @@ -24,22 +24,36 @@ target_link_libraries( IoTFleetWise::DataDecoding IoTFleetWise::Platform::Linux IoTFleetWise::OffboardConnectivityAwsIot - $<$:IoTFleetWise::CustomDataSource> + $<$:IoTFleetWise::CustomDataSource> ) add_library(${libraryAliasName} ALIAS ${libraryTargetName}) ### AWS IoT FleetWise Edge Executable ### -add_executable(aws-iot-fleetwise-edge src/main.cpp) +if(FWE_BUILD_EXECUTABLE) + add_executable(aws-iot-fleetwise-edge src/main.cpp) + target_link_libraries( + aws-iot-fleetwise-edge + ${libraryTargetName} + IoTFleetWise::Platform::Linux + -Xlinker + -Map=aws-iot-fleetwise-edge.map + ) +endif() -target_link_libraries( - aws-iot-fleetwise-edge - ${libraryTargetName} - IoTFleetWise::Platform::Linux - -Xlinker - -Map=aws-iot-fleetwise-edge.map -) +if(FWE_BUILD_ANDROID_SHARED_LIBRARY) + add_library(aws-iot-fleetwise-edge SHARED src/android_shared_library.cpp) + target_link_libraries( + aws-iot-fleetwise-edge + ${libraryTargetName} + IoTFleetWise::Platform::Linux + log + android + -Xlinker + -Map=aws-iot-fleetwise-edge.map + ) +endif() ### Version ### @@ -82,6 +96,9 @@ if(${BUILD_TESTING}) file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/test/em-example-config-corrupt.json DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/test/em-example-config-inline-creds.json + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + set( testSources test/IoTFleetWiseConfigTest.cpp diff --git a/src/executionmanagement/include/IoTFleetWiseEngine.h b/src/executionmanagement/include/IoTFleetWiseEngine.h index 1c585a6e..df8e6763 100644 --- a/src/executionmanagement/include/IoTFleetWiseEngine.h +++ b/src/executionmanagement/include/IoTFleetWiseEngine.h @@ -7,6 +7,7 @@ #include "AwsIotChannel.h" #include "AwsIotConnectivityModule.h" #include "CANDataSource.h" +#include "CANInterfaceIDTranslator.h" #include "CacheAndPersist.h" #include "ClockHandler.h" #include "CollectionInspectionWorkerThread.h" @@ -15,9 +16,13 @@ #ifdef FWE_FEATURE_CAMERA #include "DataOverDDSModule.h" #endif // FWE_FEATURE_CAMERA -#ifdef FWE_EXAMPLE_IWAVEGPS +#ifdef FWE_FEATURE_IWAVE_GPS #include "IWaveGpsSource.h" #endif +#ifdef FWE_FEATURE_EXTERNAL_GPS +#include "ExternalGpsSource.h" +#endif +#include "ExternalCANDataSource.h" #include "IDataReadyToPublishListener.h" #include "OBDOverCANModule.h" #include "RemoteProfiler.h" @@ -87,6 +92,44 @@ class IoTFleetWiseEngine : public IDataReadyToPublishListener */ bool checkAndSendRetrievedData(); + /** + * @brief Gets a list of OBD PIDs to request externally + */ + std::vector getExternalOBDPIDsToRequest(); + + /** + * @brief Sets the OBD response for the given PID + * @param pid The OBD PID + * @param response The OBD response + */ + void setExternalOBDPIDResponse( PID pid, const std::vector &response ); + + /** Ingest a CAN message from an external source + * @param interfaceId Interface identifier + * @param timestamp Timestamp of CAN message in milliseconds since epoch, or zero if unknown. + * @param messageId CAN message ID in Linux SocketCAN format + * @param data CAN message data */ + void ingestExternalCANMessage( const std::string &interfaceId, + Timestamp timestamp, + uint32_t messageId, + const std::vector &data ); + +#ifdef FWE_FEATURE_EXTERNAL_GPS + /** + * @brief Sets the location for the ExternalGpsSource + * @param latitude NMEA latitude in degrees + * @param longitude NMEA longitude in degrees + */ + void setExternalGpsLocation( double latitude, double longitude ); +#endif + + /** + * @brief Return a status summary, including the MQTT connection status, the campaign ARNs, + * and the number of payloads sent. + * @return String with the status summary + */ + std::string getStatusSummary(); + private: // atomic state of the bus. If true, we should stop bool shouldStop() const; @@ -114,9 +157,11 @@ class IoTFleetWiseEngine : public IDataReadyToPublishListener std::shared_ptr mClock = ClockHandler::getClock(); + CANInterfaceIDTranslator mCANIDTranslator; std::shared_ptr mOBDOverCANModule; std::vector> mCANDataSources; - std::vector> mCANDataConsumers; + std::unique_ptr mExternalCANDataSource; + std::unique_ptr mCANDataConsumer; std::shared_ptr mDataCollectionSender; std::shared_ptr mAwsIotModule; @@ -138,9 +183,12 @@ class IoTFleetWiseEngine : public IDataReadyToPublishListener // DDS Module std::shared_ptr mDataOverDDSModule; #endif // FWE_FEATURE_CAMERA -#ifdef FWE_EXAMPLE_IWAVEGPS +#ifdef FWE_FEATURE_IWAVE_GPS std::shared_ptr mIWaveGpsSource; #endif +#ifdef FWE_FEATURE_EXTERNAL_GPS + std::shared_ptr mExternalGpsSource; +#endif }; } // namespace ExecutionManagement } // namespace IoTFleetWise diff --git a/src/executionmanagement/src/IoTFleetWiseEngine.cpp b/src/executionmanagement/src/IoTFleetWiseEngine.cpp index ea987df0..cee643f5 100644 --- a/src/executionmanagement/src/IoTFleetWiseEngine.cpp +++ b/src/executionmanagement/src/IoTFleetWiseEngine.cpp @@ -9,7 +9,7 @@ #include "TraceModule.h" #include #include - +#include namespace Aws { namespace IoTFleetWise @@ -26,6 +26,7 @@ const uint64_t IoTFleetWiseEngine::FAST_RETRY_UPLOAD_PERSISTED_INTERVAL_MS = 100 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"; namespace @@ -54,7 +55,7 @@ getFileContents( const std::string &p ) return ret; } -#ifdef FWE_EXAMPLE_IWAVEGPS +#if defined( FWE_FEATURE_IWAVE_GPS ) || defined( FWE_FEATURE_EXTERNAL_GPS ) uint32_t stringToU32( const std::string &value ) { @@ -118,25 +119,34 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) /*************************Payload Manager and Persistency library bootstrap end************/ /*************************CAN InterfaceID to InternalID Translator begin*********/ - CANInterfaceIDTranslator canIDTranslator; - - // Initialize for ( const auto &interfaceName : config["networkInterfaces"] ) { - if ( interfaceName["type"].asString() == CAN_INTERFACE_TYPE ) + if ( ( interfaceName["type"].asString() == CAN_INTERFACE_TYPE ) || + ( interfaceName["type"].asString() == EXTERNAL_CAN_INTERFACE_TYPE ) ) { - canIDTranslator.add( interfaceName["interfaceId"].asString() ); + mCANIDTranslator.add( interfaceName["interfaceId"].asString() ); } } -#ifdef FWE_EXAMPLE_IWAVEGPS +#ifdef FWE_FEATURE_IWAVE_GPS if ( config["staticConfig"].isMember( "iWaveGpsExample" ) ) { - canIDTranslator.add( + mCANIDTranslator.add( config["staticConfig"]["iWaveGpsExample"][IWaveGpsSource::CAN_CHANNEL_NUMBER].asString() ); } else { - canIDTranslator.add( "IWAVE-GPS-CAN" ); + mCANIDTranslator.add( "IWAVE-GPS-CAN" ); + } +#endif +#ifdef FWE_FEATURE_EXTERNAL_GPS + if ( config["staticConfig"].isMember( "externalGpsExample" ) ) + { + mCANIDTranslator.add( + config["staticConfig"]["externalGpsExample"][ExternalGpsSource::CAN_CHANNEL_NUMBER].asString() ); + } + else + { + mCANIDTranslator.add( "EXTERNAL-GPS-CAN" ); } #endif /*************************CAN InterfaceID to InternalID Translator end*********/ @@ -187,18 +197,42 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) mDataCollectionSender = std::make_shared( mAwsIotChannelSendCanData, config["staticConfig"]["publishToCloudParameters"]["maxPublishMessageCount"].asUInt(), - canIDTranslator ); + mCANIDTranslator ); // Pass on the AWS SDK Bootstrap handle to the IoTModule. auto bootstrapPtr = AwsBootstrap::getInstance().getClientBootStrap(); - const auto privateKey = - getFileContents( config["staticConfig"]["mqttConnection"]["privateKeyFilename"].asString() ); - const auto certificate = - getFileContents( config["staticConfig"]["mqttConnection"]["certificateFilename"].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, @@ -309,7 +343,7 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) config["staticConfig"]["publishToCloudParameters"]["collectionSchemeManagementCheckinIntervalMs"] .asUInt(), mPersistDecoderManifestCollectionSchemesAndData, - canIDTranslator ) ) + mCANIDTranslator ) ) { FWE_LOG_ERROR( "Failed to init the CollectionScheme Manager" ); return false; @@ -339,6 +373,7 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) /********************************Data source bootstrap start*******************************/ auto obdOverCANModuleInit = false; + mCANDataConsumer = std::make_unique( signalBufferPtr, canRawBufferPtr ); for ( const auto &interfaceName : config["networkInterfaces"] ) { const auto &interfaceType = interfaceName["type"].asString(); @@ -356,16 +391,14 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) " so default to Software" ); } } - auto canChannelId = canIDTranslator.getChannelNumericID( interfaceName["interfaceId"].asString() ); - auto canConsumerPtr = - std::make_unique( canChannelId, signalBufferPtr, canRawBufferPtr ); + auto canChannelId = mCANIDTranslator.getChannelNumericID( interfaceName["interfaceId"].asString() ); auto canSourcePtr = std::make_unique( canChannelId, canTimestampType, interfaceName[CAN_INTERFACE_TYPE]["interfaceName"].asString(), interfaceName[CAN_INTERFACE_TYPE]["protocolName"].asString() == "CAN-FD", config["staticConfig"]["threadIdleTimes"]["socketCANThreadIdleTimeMs"].asUInt(), - *canConsumerPtr ); + *mCANDataConsumer ); if ( !canSourcePtr->init() ) { FWE_LOG_ERROR( "Failed to initialize CANDataSource" ); @@ -377,7 +410,6 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) FWE_LOG_ERROR( "Failed to register the CANDataSource to the CollectionScheme Manager" ); return false; } - mCANDataConsumers.push_back( std::move( canConsumerPtr ) ); mCANDataSources.push_back( std::move( canSourcePtr ) ); } else if ( interfaceType == OBD_INTERFACE_TYPE ) @@ -387,7 +419,6 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) auto obdOverCANModule = std::make_shared(); obdOverCANModuleInit = true; const auto &broadcastRequests = interfaceName[OBD_INTERFACE_TYPE]["broadcastRequests"]; - // Init returns false if no collection is configured: if ( obdOverCANModule->init( signalBufferPtr, activeDTCBufferPtr, @@ -424,6 +455,19 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) FWE_LOG_ERROR( "obdOverCANModule already initialised" ); } } + else if ( interfaceType == EXTERNAL_CAN_INTERFACE_TYPE ) + { + if ( mExternalCANDataSource == nullptr ) + { + mExternalCANDataSource = std::make_unique( *mCANDataConsumer ); + if ( !mCollectionSchemeManagerPtr->subscribeListener( + static_cast( mExternalCANDataSource.get() ) ) ) + { + FWE_LOG_ERROR( "Failed to register the ExternalCANDataSource to the CollectionScheme Manager" ); + return false; + } + } + } else { FWE_LOG_ERROR( interfaceName["type"].asString() + " is not supported" ); @@ -524,7 +568,7 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) /********************************DDS Module bootstrap end*********************************/ #endif // FWE_FEATURE_CAMERA -#ifdef FWE_EXAMPLE_IWAVEGPS +#ifdef FWE_FEATURE_IWAVE_GPS /********************************IWave GPS Example NMEA reader *********************************/ mIWaveGpsSource = std::make_shared( signalBufferPtr ); bool iWaveInitSuccessful = false; @@ -533,7 +577,7 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) FWE_LOG_TRACE( "Found 'iWaveGpsExample' section in config file" ); iWaveInitSuccessful = mIWaveGpsSource->init( config["staticConfig"]["iWaveGpsExample"][IWaveGpsSource::PATH_TO_NMEA].asString(), - canIDTranslator.getChannelNumericID( + mCANIDTranslator.getChannelNumericID( config["staticConfig"]["iWaveGpsExample"][IWaveGpsSource::CAN_CHANNEL_NUMBER].asString() ), stringToU32( config["staticConfig"]["iWaveGpsExample"][IWaveGpsSource::CAN_RAW_FRAME_ID].asString() ), static_cast( stringToU32( @@ -545,7 +589,7 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) { // If not config available default to this values iWaveInitSuccessful = mIWaveGpsSource->init( - "/dev/ttyUSB1", canIDTranslator.getChannelNumericID( "IWAVE-GPS-CAN" ), 1, 32, 0 ); + "/dev/ttyUSB1", mCANIDTranslator.getChannelNumericID( "IWAVE-GPS-CAN" ), 1, 32, 0 ); } if ( iWaveInitSuccessful && mIWaveGpsSource->connect() ) { @@ -565,6 +609,48 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) /********************************IWave GPS Example NMEA reader end******************************/ #endif +#ifdef FWE_FEATURE_EXTERNAL_GPS + /********************************External GPS Example NMEA reader *********************************/ + mExternalGpsSource = std::make_shared( signalBufferPtr ); + bool externalGpsInitSuccessful = false; + if ( config["staticConfig"].isMember( "externalGpsExample" ) ) + { + FWE_LOG_TRACE( "Found 'externalGpsExample' section in config file" ); + externalGpsInitSuccessful = mExternalGpsSource->init( + mCANIDTranslator.getChannelNumericID( + config["staticConfig"]["externalGpsExample"][ExternalGpsSource::CAN_CHANNEL_NUMBER].asString() ), + stringToU32( + config["staticConfig"]["externalGpsExample"][ExternalGpsSource::CAN_RAW_FRAME_ID].asString() ), + static_cast( stringToU32( + config["staticConfig"]["externalGpsExample"][ExternalGpsSource::LATITUDE_START_BIT].asString() ) ), + static_cast( + stringToU32( config["staticConfig"]["externalGpsExample"][ExternalGpsSource::LONGITUDE_START_BIT] + .asString() ) ) ); + } + else + { + // If not config available default to this values + externalGpsInitSuccessful = + mExternalGpsSource->init( mCANIDTranslator.getChannelNumericID( "EXTERNAL-GPS-CAN" ), 1, 32, 0 ); + } + if ( externalGpsInitSuccessful ) + { + if ( !mCollectionSchemeManagerPtr->subscribeListener( + static_cast( mExternalGpsSource.get() ) ) ) + { + FWE_LOG_ERROR( "Failed to register the ExternalGpsSource to the CollectionScheme Manager" ); + return false; + } + mExternalGpsSource->start(); + } + else + { + FWE_LOG_ERROR( "ExternalGpsSource initialization failed" ); + return false; + } + /********************************External GPS Example NMEA reader end******************************/ +#endif + mPrintMetricsCyclicPeriodMs = config["staticConfig"]["internalParameters"]["metricsCyclicPrintIntervalMs"].asUInt(); // default to 0 } @@ -582,7 +668,13 @@ IoTFleetWiseEngine::connect( const Json::Value &config ) bool IoTFleetWiseEngine::disconnect() { -#ifdef FWE_EXAMPLE_IWAVEGPS +#ifdef FWE_FEATURE_EXTERNAL_GPS + if ( mExternalGpsSource ) + { + mExternalGpsSource->stop(); + } +#endif +#ifdef FWE_FEATURE_IWAVE_GPS if ( mIWaveGpsSource ) { mIWaveGpsSource->stop(); @@ -936,6 +1028,94 @@ IoTFleetWiseEngine::checkAndSendRetrievedData() } } +std::vector +IoTFleetWiseEngine::getExternalOBDPIDsToRequest() +{ + std::vector pids; + if ( mOBDOverCANModule != nullptr ) + { + pids = mOBDOverCANModule->getExternalPIDsToRequest(); + } + return pids; +} + +void +IoTFleetWiseEngine::setExternalOBDPIDResponse( PID pid, const std::vector &response ) +{ + if ( mOBDOverCANModule == nullptr ) + { + return; + } + mOBDOverCANModule->setExternalPIDResponse( pid, response ); +} + +void +IoTFleetWiseEngine::ingestExternalCANMessage( const std::string &interfaceId, + Timestamp timestamp, + uint32_t messageId, + const std::vector &data ) +{ + auto canChannelId = mCANIDTranslator.getChannelNumericID( interfaceId ); + if ( canChannelId == INVALID_CAN_SOURCE_NUMERIC_ID ) + { + FWE_LOG_ERROR( "Unknown interface ID: " + interfaceId ); + return; + } + if ( mExternalCANDataSource == nullptr ) + { + FWE_LOG_ERROR( "No external CAN interface present" ); + return; + } + mExternalCANDataSource->ingestMessage( canChannelId, timestamp, messageId, data ); +} + +#ifdef FWE_FEATURE_EXTERNAL_GPS +void +IoTFleetWiseEngine::setExternalGpsLocation( double latitude, double longitude ) +{ + if ( mExternalGpsSource == nullptr ) + { + return; + } + mExternalGpsSource->setLocation( latitude, longitude ); +} +#endif + +std::string +IoTFleetWiseEngine::getStatusSummary() +{ + if ( mAwsIotModule == nullptr || mCollectionSchemeManagerPtr == nullptr || mAwsIotChannelSendCanData == nullptr || + mOBDOverCANModule == nullptr +#ifdef FWE_FEATURE_EXTERNAL_GPS + || mExternalGpsSource == nullptr +#endif + ) + { + return ""; + } + std::string status; + status += + std::string( "MQTT connection: " ) + ( mAwsIotModule->isAlive() ? "CONNECTED" : "NOT CONNECTED" ) + "\n\n"; + + status += "Campaign ARNs:\n"; + auto collectionSchemeArns = mCollectionSchemeManagerPtr->getCollectionSchemeArns(); + if ( collectionSchemeArns.empty() ) + { + status += "NONE\n"; + } + else + { + for ( auto &collectionSchemeArn : collectionSchemeArns ) + { + status += collectionSchemeArn + "\n"; + } + } + status += "\n"; + + status += "Payloads sent: " + std::to_string( mAwsIotChannelSendCanData->getPayloadCountSent() ) + "\n\n"; + return status; +} + } // namespace ExecutionManagement } // namespace IoTFleetWise } // namespace Aws diff --git a/src/executionmanagement/src/android_shared_library.cpp b/src/executionmanagement/src/android_shared_library.cpp new file mode 100644 index 00000000..b3d82843 --- /dev/null +++ b/src/executionmanagement/src/android_shared_library.cpp @@ -0,0 +1,276 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Includes +#include "ConsoleLogger.h" +#include "IoTFleetWiseEngine.h" +#include "IoTFleetWiseVersion.h" +#include "LogLevel.h" +#include +#include +#include +#include +#include +#include +#include + +#define LOG_TAG "FWE" +#define LOGE( x ) __android_log_print( ANDROID_LOG_ERROR, LOG_TAG, "%s", ( x ).c_str() ) +#define LOGW( x ) __android_log_print( ANDROID_LOG_WARN, LOG_TAG, "%s", ( x ).c_str() ) +#define LOGI( x ) __android_log_print( ANDROID_LOG_INFO, LOG_TAG, "%s", ( x ).c_str() ) +#define LOGD( x ) __android_log_print( ANDROID_LOG_DEBUG, LOG_TAG, "%s", ( x ).c_str() ) + +using namespace Aws::IoTFleetWise::ExecutionManagement; + +static std::atomic mExit; +static std::shared_ptr mEngine; + +static void +printVersion() +{ + LOGI( std::string( "Version: " ) + VERSION_PROJECT_VERSION + ", git tag: " + VERSION_GIT_TAG + + ", git commit sha: " + VERSION_GIT_COMMIT_SHA + ", Build time: " + VERSION_BUILD_TIME ); +} + +static void +configureLogging( const Json::Value &config ) +{ + Aws::IoTFleetWise::Platform::Linux::LogLevel logLevel = Aws::IoTFleetWise::Platform::Linux::LogLevel::Trace; + stringToLogLevel( config["staticConfig"]["internalParameters"]["systemWideLogLevel"].asString(), logLevel ); + gSystemWideLogLevel = logLevel; + gLogColorOption = Aws::IoTFleetWise::Platform::Linux::LogColorOption::No; +} + +static std::string +readAssetFile( JNIEnv *env, jobject assetManager, std::string filename ) +{ + AAsset *asset = AAssetManager_open( AAssetManager_fromJava( env, assetManager ), filename.c_str(), 0 ); + if ( !asset ) + { + throw std::runtime_error( "could not open " + filename ); + } + std::string content; + for ( ;; ) + { + char buf[1024]; + auto sizeRead = AAsset_read( asset, buf, sizeof( buf ) - 1 ); + if ( sizeRead < 0 ) + { + AAsset_close( asset ); + throw std::runtime_error( "error reading from " + filename ); + } + if ( sizeRead == 0 ) + { + break; + } + buf[sizeRead] = '\0'; + content += buf; + } + AAsset_close( asset ); + return content; +} + +extern "C" JNIEXPORT jint JNICALL +Java_com_aws_iotfleetwise_Fwe_run( JNIEnv *env, + jobject me, + jobject assetManager, + jstring vehicleNameJString, + jstring endpointUrlJString, + jstring certificateJString, + jstring privateKeyJString, + jstring mqttTopicPrefixJString ) +{ + static_cast( me ); + try + { + auto vehicleNameCString = env->GetStringUTFChars( vehicleNameJString, 0 ); + std::string vehicleName( vehicleNameCString ); + env->ReleaseStringUTFChars( vehicleNameJString, vehicleNameCString ); + auto endpointUrlCString = env->GetStringUTFChars( endpointUrlJString, 0 ); + std::string endpointUrl( endpointUrlCString ); + env->ReleaseStringUTFChars( endpointUrlJString, endpointUrlCString ); + auto certificateCString = env->GetStringUTFChars( certificateJString, 0 ); + std::string certificate( certificateCString ); + env->ReleaseStringUTFChars( certificateJString, certificateCString ); + auto privateKeyCString = env->GetStringUTFChars( privateKeyJString, 0 ); + std::string privateKey( privateKeyCString ); + env->ReleaseStringUTFChars( privateKeyJString, privateKeyCString ); + auto mqttTopicPrefixCString = env->GetStringUTFChars( mqttTopicPrefixJString, 0 ); + std::string mqttTopicPrefix( mqttTopicPrefixCString ); + env->ReleaseStringUTFChars( mqttTopicPrefixJString, mqttTopicPrefixCString ); + if ( mqttTopicPrefix.empty() ) + { + mqttTopicPrefix = "$aws/iotfleetwise/"; + } + + printVersion(); + LOGI( "vehicleName: " + vehicleName + ", endpointUrl: " + endpointUrl + + ", mqttTopicPrefix: " + mqttTopicPrefix ); + + std::string configFilename = "config-0.json"; + auto configJson = readAssetFile( env, assetManager, configFilename ); + Json::Value config; + try + { + std::stringstream configFileStream( configJson ); + configFileStream >> config; + } + catch ( ... ) + { + LOGE( std::string( "Failed to parse: " ) + configFilename ); + return EXIT_FAILURE; + } + auto rootCA = readAssetFile( env, assetManager, "AmazonRootCA1.pem" ); + auto &mqttConnection = config["staticConfig"]["mqttConnection"]; + mqttConnection["endpointUrl"] = endpointUrl; + mqttConnection["privateKey"] = privateKey; + mqttConnection["certificate"] = certificate; + mqttConnection["rootCA"] = rootCA; + mqttConnection["clientId"] = vehicleName; + mqttTopicPrefix += "vehicles/" + vehicleName; + mqttConnection["collectionSchemeListTopic"] = mqttTopicPrefix + "/collection_schemes"; + mqttConnection["decoderManifestTopic"] = mqttTopicPrefix + "/decoder_manifests"; + mqttConnection["canDataTopic"] = mqttTopicPrefix + "/signals"; + mqttConnection["checkinTopic"] = mqttTopicPrefix + "/checkins"; + // Set system wide log level + configureLogging( config ); + + mEngine = std::make_shared(); + // Connect the Engine + if ( mEngine->connect( config ) && mEngine->start() ) + { + LOGI( std::string( " AWS IoT FleetWise Edge Service Started successfully " ) ); + } + else + { + mEngine = nullptr; + return EXIT_FAILURE; + } + + mExit = false; + while ( !mExit ) + { + sleep( 1 ); + } + if ( mEngine->stop() && mEngine->disconnect() ) + { + LOGI( std::string( " AWS IoT FleetWise Edge Service Stopped successfully " ) ); + mEngine = nullptr; + return EXIT_SUCCESS; + } + + LOGE( std::string( " AWS IoT FleetWise Edge Service Stopped with errors " ) ); + mEngine = nullptr; + return EXIT_FAILURE; + } + catch ( const std::exception &e ) + { + LOGE( std::string( "Unhandled exception: " ) + std::string( e.what() ) ); + mEngine = nullptr; + return EXIT_FAILURE; + } + catch ( ... ) + { + LOGE( std::string( "Unknown exception" ) ); + mEngine = nullptr; + return EXIT_FAILURE; + } +} + +extern "C" JNIEXPORT void JNICALL +Java_com_aws_iotfleetwise_Fwe_stop( JNIEnv *env, jobject me ) +{ + static_cast( env ); + static_cast( me ); + mExit = true; +} + +#ifdef FWE_FEATURE_EXTERNAL_GPS +extern "C" JNIEXPORT void JNICALL +Java_com_aws_iotfleetwise_Fwe_setLocation( JNIEnv *env, jobject me, jdouble latitude, jdouble longitude ) +{ + static_cast( env ); + static_cast( me ); + if ( mEngine == nullptr ) + { + return; + } + mEngine->setExternalGpsLocation( latitude, longitude ); +} +#endif + +extern "C" JNIEXPORT jintArray JNICALL +Java_com_aws_iotfleetwise_Fwe_getObdPidsToRequest( JNIEnv *env, jobject me ) +{ + static_cast( me ); + std::vector requests; + if ( mEngine != nullptr ) + { + requests = mEngine->getExternalOBDPIDsToRequest(); + } + jintArray ret = env->NewIntArray( static_cast( requests.size() ) ); + for ( size_t i = 0; i < requests.size(); i++ ) + { + jint pid = requests[i]; + env->SetIntArrayRegion( ret, static_cast( i ), 1, &pid ); + } + return ret; +} + +extern "C" JNIEXPORT void JNICALL +Java_com_aws_iotfleetwise_Fwe_setObdPidResponse( JNIEnv *env, jobject me, jint pid, jintArray responseJArray ) +{ + static_cast( me ); + if ( mEngine == nullptr ) + { + return; + } + auto len = env->GetArrayLength( responseJArray ); + std::vector response( static_cast( len ) ); + for ( jsize i = 0; i < len; i++ ) + { + jint byte; + env->GetIntArrayRegion( responseJArray, i, 1, &byte ); + response[static_cast( i )] = static_cast( byte ); + } + mEngine->setExternalOBDPIDResponse( static_cast( pid ), response ); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_aws_iotfleetwise_Fwe_ingestCanMessage( + JNIEnv *env, jobject me, jstring interfaceIdJString, jlong timestamp, jint messageId, jbyteArray dataJArray ) +{ + static_cast( me ); + if ( mEngine == nullptr ) + { + return; + } + auto interfaceIdCString = env->GetStringUTFChars( interfaceIdJString, 0 ); + std::string interfaceId( interfaceIdCString ); + env->ReleaseStringUTFChars( interfaceIdJString, interfaceIdCString ); + auto len = env->GetArrayLength( dataJArray ); + std::vector data( static_cast( len ) ); + env->GetByteArrayRegion( dataJArray, 0, len, (int8_t *)data.data() ); + mEngine->ingestExternalCANMessage( + interfaceId, static_cast( timestamp ), static_cast( messageId ), data ); +} + +extern "C" JNIEXPORT jstring JNICALL +Java_com_aws_iotfleetwise_Fwe_getStatusSummary( JNIEnv *env, jobject me ) +{ + static_cast( me ); + std::string status; + if ( mEngine != nullptr ) + { + status = mEngine->getStatusSummary(); + } + return env->NewStringUTF( status.c_str() ); +} + +extern "C" JNIEXPORT jstring JNICALL +Java_com_aws_iotfleetwise_Fwe_getVersion( JNIEnv *env, jobject me ) +{ + static_cast( me ); + std::string version = std::string( "v" ) + VERSION_PROJECT_VERSION; + return env->NewStringUTF( version.c_str() ); +} diff --git a/src/executionmanagement/test/IoTFleetWiseEngineTest.cpp b/src/executionmanagement/test/IoTFleetWiseEngineTest.cpp index 4e617c4c..c72f0d67 100644 --- a/src/executionmanagement/test/IoTFleetWiseEngineTest.cpp +++ b/src/executionmanagement/test/IoTFleetWiseEngineTest.cpp @@ -43,7 +43,7 @@ class IoTFleetWiseEngineTest : public ::testing::Test { GTEST_SKIP() << "Skipping test fixture due to unavailability of socket"; } -#ifdef FWE_EXAMPLE_IWAVEGPS +#ifdef FWE_FEATURE_IWAVE_GPS std::ofstream iWaveGpsFile( "/tmp/engineTestIWaveGPSfile.txt" ); iWaveGpsFile << "NO valid NMEA data"; iWaveGpsFile.close(); @@ -65,6 +65,20 @@ TEST_F( IoTFleetWiseEngineTest, InitAndStartEngine ) ASSERT_TRUE( engine.stop() ); } +TEST_F( IoTFleetWiseEngineTest, InitAndStartEngineInlineCreds ) +{ + Json::Value config; + ASSERT_TRUE( IoTFleetWiseConfig::read( "em-example-config-inline-creds.json", config ) ); + IoTFleetWiseEngine engine; + + ASSERT_TRUE( engine.connect( config ) ); + + ASSERT_TRUE( engine.start() ); + ASSERT_TRUE( engine.isAlive() ); + ASSERT_TRUE( engine.disconnect() ); + ASSERT_TRUE( engine.stop() ); +} + TEST_F( IoTFleetWiseEngineTest, CheckPublishDataQueue ) { Json::Value config; diff --git a/src/executionmanagement/test/em-example-config-inline-creds.json b/src/executionmanagement/test/em-example-config-inline-creds.json new file mode 100644 index 00000000..96ec0559 --- /dev/null +++ b/src/executionmanagement/test/em-example-config-inline-creds.json @@ -0,0 +1,82 @@ +{ + "version": "1.0", + "networkInterfaces": [ + { + "canInterface": { + "interfaceName": "vcan0", + "protocolName": "CAN", + "protocolVersion": "2.0A" + }, + "interfaceId": "1", + "type": "canInterface" + }, + { + "obdInterface": { + "interfaceName": "vcan0", + "obdStandard": "J1979", + "pidRequestIntervalSeconds": 0, + "dtcRequestIntervalSeconds": 0 + }, + "interfaceId": "2", + "type": "obdInterface" + } + ], + "staticConfig": { + "bufferSizes": { + "dtcBufferSize": 100, + "decodedSignalsBufferSize": 10000, + "rawCANFrameBufferSize": 10000 + }, + "threadIdleTimes": { + "inspectionThreadIdleTimeMs": 50, + "socketCANThreadIdleTimeMs": 50, + "canDecoderThreadIdleTimeMs": 50 + }, + "persistency": { + "persistencyPath": "./", + "persistencyPartitionMaxSize": 524288, + "persistencyUploadRetryInterval": 10000 + }, + "internalParameters": { + "readyToPublishDataBufferSize": 10000, + "systemWideLogLevel": "Trace", + "dataReductionProbabilityDisabled": false + }, + "publishToCloudParameters": { + "maxPublishMessageCount": 1000, + "collectionSchemeManagementCheckinIntervalMs": 5000 + }, + "mqttConnection": { + "endpointUrl": "my-endpoint.my-region.amazonaws.com", + "clientId": "ClientId", + "collectionSchemeListTopic": "collection-scheme-list-topic", + "decoderManifestTopic": "decoder-manifest-topic", + "canDataTopic": "can-data", + "checkinTopic": "checkin", + "certificate": "MY_INLINE_CERTIFICATE", + "privateKey": "MY_INLINE_PRIVATE_KEY", + "rootCA": "MY_INLINE_ROOT_CA" + }, + "iWaveGpsExample": { + "nmeaFilePath": "/tmp/engineTestIWaveGPSfile.txt", + "canChannel": "IWAVE-GPS-CAN", + "canFrameId": "1", + "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 9728de54..78fbe336 100644 --- a/src/executionmanagement/test/em-example-config.json +++ b/src/executionmanagement/test/em-example-config.json @@ -40,7 +40,8 @@ "internalParameters": { "readyToPublishDataBufferSize": 10000, "systemWideLogLevel": "Trace", - "dataReductionProbabilityDisabled": false + "dataReductionProbabilityDisabled": false, + "persistencyUploadRetryIntervalMs": 5000 }, "publishToCloudParameters": { "maxPublishMessageCount": 1000, @@ -51,10 +52,13 @@ "clientId": "ClientId", "collectionSchemeListTopic": "collection-scheme-list-topic", "decoderManifestTopic": "decoder-manifest-topic", + "metricsUploadTopic": "aws-iot-fleetwise-metrics-upload", + "loggingUploadTopic": "aws-iot-fleetwise-logging-upload", "canDataTopic": "can-data", "checkinTopic": "checkin", "certificateFilename": "path/to/my-certificate.pem.crt", - "privateKeyFilename": "path/to/my-private.pem.key" + "privateKeyFilename": "path/to/my-private.pem.key", + "rootCAFilename": "path/to/my-root-ca.pem.crt" }, "iWaveGpsExample": { "nmeaFilePath": "/tmp/engineTestIWaveGPSfile.txt", @@ -62,6 +66,12 @@ "canFrameId": "1", "longitudeStartBit": "32", "latitudeStartBit": "0" + }, + "remoteProfilerDefaultValues": { + "loggingUploadLevelThreshold": "Warning", + "metricsUploadIntervalMs": 60000, + "loggingUploadMaxWaitBeforeUploadMs": 60000, + "profilerPrefix": "TestVehicle1" } }, "dds-nodes-configuration": [ diff --git a/src/offboardconnectivity/implementation/aws/bootstrap/src/AwsSDKMemoryManager.cpp b/src/offboardconnectivity/implementation/aws/bootstrap/src/AwsSDKMemoryManager.cpp index 5acd2d35..7012dd73 100644 --- a/src/offboardconnectivity/implementation/aws/bootstrap/src/AwsSDKMemoryManager.cpp +++ b/src/offboardconnectivity/implementation/aws/bootstrap/src/AwsSDKMemoryManager.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "AwsSDKMemoryManager.h" +#include "TraceModule.h" #include #include @@ -11,9 +12,10 @@ namespace IoTFleetWise { namespace OffboardConnectivityAwsIot { - namespace { + +using namespace Aws::IoTFleetWise::Platform::Linux; using Byte = unsigned char; // offset to store value of memory size allocated @@ -64,6 +66,7 @@ AwsSDKMemoryManager::AllocateMemory( std::size_t blockSize, std::size_t alignmen // store the allocated memory's size *( static_cast( pMem ) ) = realSize; mMemoryUsedAndReserved += realSize; + TraceModule::get().setVariable( TraceVariable::MQTT_HEAP_USAGE, mMemoryUsedAndReserved ); // return a pointer to the block offset from the size storage location return static_cast( pMem ) + ALIGN_OFFSET; @@ -87,12 +90,14 @@ AwsSDKMemoryManager::FreeMemory( void *memoryPtr ) // update the stats mMemoryUsedAndReserved -= realSize; + TraceModule::get().setVariable( TraceVariable::MQTT_HEAP_USAGE, mMemoryUsedAndReserved ); } std::size_t AwsSDKMemoryManager::reserveMemory( std::size_t bytes ) { mMemoryUsedAndReserved += ( bytes + ALIGN_OFFSET ); + TraceModule::get().setVariable( TraceVariable::MQTT_HEAP_USAGE, mMemoryUsedAndReserved ); return mMemoryUsedAndReserved; } @@ -100,6 +105,7 @@ std::size_t AwsSDKMemoryManager::releaseReservedMemory( std::size_t bytes ) { mMemoryUsedAndReserved -= ( bytes + ALIGN_OFFSET ); + TraceModule::get().setVariable( TraceVariable::MQTT_HEAP_USAGE, mMemoryUsedAndReserved ); return mMemoryUsedAndReserved; } diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsIotChannel.h b/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsIotChannel.h index adba04a4..f588ca3e 100644 --- a/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsIotChannel.h +++ b/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsIotChannel.h @@ -135,6 +135,16 @@ class AwsIotChannel : public Aws::IoTFleetWise::OffboardConnectivity::ISender, return mSubscribeAsynchronously; } + /** + * @brief Returns the number of payloads successfully passed to the AWS IoT SDK + * @return Number of payloads + */ + unsigned + getPayloadCountSent() const + { + return mPayloadCountSent; + } + private: bool isAliveNotThreadSafe(); @@ -151,6 +161,7 @@ class AwsIotChannel : public Aws::IoTFleetWise::OffboardConnectivity::ISender, std::shared_ptr mPayloadManager; std::string mTopicName; std::atomic mSubscribed; + std::atomic mPayloadCountSent{}; bool mSubscribeAsynchronously; }; diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsIotConnectivityModule.h b/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsIotConnectivityModule.h index 26299523..35ace682 100644 --- a/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsIotConnectivityModule.h +++ b/src/offboardconnectivity/implementation/aws/iotcpp/include/AwsIotConnectivityModule.h @@ -57,6 +57,7 @@ class AwsIotConnectivityModule : public IRetryable, public IConnectivityModule * 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 @@ -68,6 +69,7 @@ class AwsIotConnectivityModule : public IRetryable, public IConnectivityModule */ 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, @@ -128,6 +130,7 @@ class AwsIotConnectivityModule : public IRetryable, public IConnectivityModule 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::unique_ptr mMqttClient; diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsIotChannel.cpp b/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsIotChannel.cpp index 6e4c59d1..16d64583 100644 --- a/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsIotChannel.cpp +++ b/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsIotChannel.cpp @@ -225,6 +225,7 @@ AwsIotChannel::sendBuffer( const std::uint8_t *buf, size_t size, struct Collecti if ( ( packetId != 0U ) && ( errorCode == 0 ) ) { FWE_LOG_TRACE( "Operation on packetId " + std::to_string( packetId ) + " Succeeded" ); + mPayloadCountSent++; } else { diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsIotConnectivityModule.cpp b/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsIotConnectivityModule.cpp index c7b9d72b..d9c26da8 100644 --- a/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsIotConnectivityModule.cpp +++ b/src/offboardconnectivity/implementation/aws/iotcpp/src/AwsIotConnectivityModule.cpp @@ -55,6 +55,7 @@ AwsIotConnectivityModule::releaseMemoryUsage( std::size_t bytes ) 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, @@ -66,6 +67,7 @@ AwsIotConnectivityModule::connect( const std::string &privateKey, 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 ) ) { @@ -253,7 +255,10 @@ AwsIotConnectivityModule::createMqttConnection( Aws::Crt::Io::ClientBootstrap *c Aws::Iot::MqttClientConnectionConfigBuilder builder; builder = Aws::Iot::MqttClientConnectionConfigBuilder( mCertificate, mPrivateKey ); - + if ( mRootCA.len > 0 ) + { + builder.WithCertificateAuthority( mRootCA ); + } builder.WithEndpoint( mEndpointUrl ); TraceModule::get().sectionBegin( TraceSection::BUILD_MQTT ); diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/test/include/AwsIotSdkMock.h b/src/offboardconnectivity/implementation/aws/iotcpp/test/include/AwsIotSdkMock.h index e094ee0f..b607d8de 100644 --- a/src/offboardconnectivity/implementation/aws/iotcpp/test/include/AwsIotSdkMock.h +++ b/src/offboardconnectivity/implementation/aws/iotcpp/test/include/AwsIotSdkMock.h @@ -79,6 +79,7 @@ class MqttClientConnectionConfigBuilderMock { public: MOCK_METHOD( (void), WithEndpoint, ( const Aws::Crt::String &endpoint ) ); + MOCK_METHOD( (void), WithCertificateAuthority, ( const Crt::ByteCursor &rootCA ) ); MOCK_METHOD( ( Aws::Iot::MqttClientConnectionConfig ), Build, () ); }; diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/test/include/aws-iot-cpp-sdk-mock/aws/iot/MqttClient.h b/src/offboardconnectivity/implementation/aws/iotcpp/test/include/aws-iot-cpp-sdk-mock/aws/iot/MqttClient.h index 04017079..1269cbf7 100644 --- a/src/offboardconnectivity/implementation/aws/iotcpp/test/include/aws-iot-cpp-sdk-mock/aws/iot/MqttClient.h +++ b/src/offboardconnectivity/implementation/aws/iotcpp/test/include/aws-iot-cpp-sdk-mock/aws/iot/MqttClient.h @@ -103,6 +103,7 @@ class MqttClientConnectionConfigBuilder final } MqttClientConnectionConfigBuilder &WithEndpoint( const Crt::String &endpoint ); + MqttClientConnectionConfigBuilder &WithCertificateAuthority( const Crt::ByteCursor &rootCA ); MqttClientConnectionConfig Build() noexcept; }; class MqttClient diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/test/src/AwsIotConnectivityModuleTest.cpp b/src/offboardconnectivity/implementation/aws/iotcpp/test/src/AwsIotConnectivityModuleTest.cpp index 3b7c27df..1d76f658 100644 --- a/src/offboardconnectivity/implementation/aws/iotcpp/test/src/AwsIotConnectivityModuleTest.cpp +++ b/src/offboardconnectivity/implementation/aws/iotcpp/test/src/AwsIotConnectivityModuleTest.cpp @@ -112,7 +112,7 @@ class AwsIotConnectivityModuleTest : public ::testing::Test TEST_F( AwsIotConnectivityModuleTest, disconnectAfterFailedConnect ) { std::shared_ptr m = std::make_shared(); - ASSERT_FALSE( m->connect( "", "", "", "", bootstrap ) ); + ASSERT_FALSE( m->connect( "", "", "", "", "", bootstrap ) ); // disconnect must only disconnect when connection is available so this should not seg fault m->disconnect(); } @@ -128,7 +128,7 @@ TEST_F( AwsIotConnectivityModuleTest, connectSuccessfull ) std::shared_ptr m = std::make_shared(); - ASSERT_TRUE( m->connect( "key", "cert", endpoint, "clientIdTest", bootstrap ) ); + ASSERT_TRUE( m->connect( "key", "cert", "rootca", endpoint, "clientIdTest", bootstrap ) ); con->OnDisconnect( *con ); @@ -144,7 +144,7 @@ TEST_F( AwsIotConnectivityModuleTest, connectFailsOnClientBootstrapCreation ) auto con = setupValidConnection(); std::shared_ptr m = std::make_shared(); - ASSERT_FALSE( m->connect( "key", "cert", "endpoint", "clientIdTest", nullptr ) ); + ASSERT_FALSE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", nullptr ) ); } /** @brief Test trying to connect, where creation of the client fails */ @@ -154,7 +154,7 @@ TEST_F( AwsIotConnectivityModuleTest, connectFailsOnClientCreation ) EXPECT_CALL( clientMock, operatorBool() ).Times( AtLeast( 1 ) ).WillRepeatedly( Return( false ) ); std::shared_ptr m = std::make_shared(); - ASSERT_FALSE( m->connect( "key", "cert", "endpoint", "clientIdTest", bootstrap ) ); + ASSERT_FALSE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ) ); } /** @brief Test opening a connection, then interrupting it and resuming it */ @@ -163,7 +163,7 @@ TEST_F( AwsIotConnectivityModuleTest, connectionInterrupted ) auto con = setupValidConnection(); std::shared_ptr m = std::make_shared(); - ASSERT_TRUE( m->connect( "key", "cert", "endpoint", "clientIdTest", bootstrap ) ); + ASSERT_TRUE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ) ); con->OnConnectionInterrupted( *con, 10 ); con->OnConnectionResumed( *con, ReturnCode::AWS_MQTT_CONNECT_ACCEPTED, true ); @@ -203,7 +203,7 @@ TEST_F( AwsIotConnectivityModuleTest, connectFailsServerUnavailableWithDelay ) // We want to see exactly one call to disconnect EXPECT_CALL( *con, Disconnect() ).Times( 1 ).WillRepeatedly( Return( true ) ); - ASSERT_FALSE( m->connect( "key", "cert", "endpoint", "clientIdTest", bootstrap ) ); + ASSERT_FALSE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ) ); std::this_thread::sleep_for( std::chrono::milliseconds( 20 ) ); killAllThread = true; completeThread.join(); @@ -237,7 +237,7 @@ TEST_F( AwsIotConnectivityModuleTest, subscribeSuccessfully ) std::shared_ptr m = std::make_shared(); AwsIotChannel c( m.get(), nullptr ); - ASSERT_TRUE( m->connect( "key", "cert", "endpoint", "clientIdTest", bootstrap ) ); + ASSERT_TRUE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ) ); c.setTopic( "topic" ); EXPECT_CALL( *con, Subscribe( _, _, _, _ ) ) .Times( 1 ) @@ -290,7 +290,7 @@ 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", "endpoint", "clientIdTest", bootstrap ) ); + ASSERT_TRUE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ) ); c.setTopic( "topic" ); ASSERT_EQ( c.sendBuffer( nullptr, 10 ), ConnectivityError::WrongInputData ); con->OnDisconnect( *con ); @@ -303,7 +303,7 @@ 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", "endpoint", "clientIdTest", bootstrap ) ); + ASSERT_TRUE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ) ); c.setTopic( "topic" ); std::vector a; a.resize( c.getMaxSendSize() + 1U ); @@ -320,7 +320,7 @@ 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", "endpoint", "clientIdTest", bootstrap ) ); + ASSERT_TRUE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ) ); std::uint8_t input[] = { 0xca, 0xfe }; c.setTopic( "topic" ); std::list completeHandlers; @@ -354,6 +354,8 @@ TEST_F( AwsIotConnectivityModuleTest, sendMultiple ) completeHandlers.front().operator()( *con, 0, 0 ); completeHandlers.pop_front(); + ASSERT_EQ( c.getPayloadCountSent(), 2 ); + con->OnDisconnect( *con ); c.invalidateConnection(); } @@ -364,7 +366,7 @@ TEST_F( AwsIotConnectivityModuleTest, sdkRAMExceeded ) auto con = setupValidConnection(); std::shared_ptr m = std::make_shared(); - ASSERT_TRUE( m->connect( "key", "cert", "endpoint", "clientIdTest", bootstrap ) ); + ASSERT_TRUE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap ) ); auto &memMgr = AwsSDKMemoryManager::getInstance(); void *alloc1 = memMgr.AllocateMemory( 600000000, alignof( std::size_t ) ); @@ -438,7 +440,7 @@ TEST_F( AwsIotConnectivityModuleTest, asyncConnect ) EXPECT_CALL( *con, Connect( _, _, _, _ ) ).Times( 1 ).WillOnce( Return( true ) ); - ASSERT_TRUE( m->connect( "key", "cert", "endpoint", "clientIdTest", bootstrap, true ) ); + ASSERT_TRUE( m->connect( "key", "cert", "rootca", "endpoint", "clientIdTest", bootstrap, true ) ); std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); // first attempt should come immediately diff --git a/src/offboardconnectivity/implementation/aws/iotcpp/test/src/MqttClient.cpp b/src/offboardconnectivity/implementation/aws/iotcpp/test/src/MqttClient.cpp index 2dfe5522..3be1d471 100644 --- a/src/offboardconnectivity/implementation/aws/iotcpp/test/src/MqttClient.cpp +++ b/src/offboardconnectivity/implementation/aws/iotcpp/test/src/MqttClient.cpp @@ -45,6 +45,13 @@ Aws::Iot::MqttClientConnectionConfigBuilder::WithEndpoint( const Aws::Crt::Strin return *this; } +Aws::Iot::MqttClientConnectionConfigBuilder & +Aws::Iot::MqttClientConnectionConfigBuilder::WithCertificateAuthority( const Crt::ByteCursor &rootCA ) +{ + getConfBuilderMock()->WithCertificateAuthority( rootCA ); + return *this; +} + Aws::Iot::MqttClientConnectionConfig Aws::Iot::MqttClientConnectionConfigBuilder::Build() noexcept { diff --git a/src/platform/linux/logmanagement/include/TraceModule.h b/src/platform/linux/logmanagement/include/TraceModule.h index 308fc1e8..98274b4b 100644 --- a/src/platform/linux/logmanagement/include/TraceModule.h +++ b/src/platform/linux/logmanagement/include/TraceModule.h @@ -68,7 +68,9 @@ enum class TraceVariable CE_TRIGGERS, OBD_POSSIBLE_PRECISION_LOSS_UINT64, OBD_POSSIBLE_PRECISION_LOSS_INT64, - + MQTT_SIGNAL_MESSAGES_SENT_OUT, // Can be multiple messages per event id + MQTT_HEAP_USAGE, + SIGNAL_BUFFER_SIZE, // If you add more, remember to add the name to TraceModule::getVariableName TRACE_VARIABLE_SIZE }; diff --git a/src/platform/linux/logmanagement/src/ConsoleLogger.cpp b/src/platform/linux/logmanagement/src/ConsoleLogger.cpp index 3223dba9..b7e78844 100644 --- a/src/platform/linux/logmanagement/src/ConsoleLogger.cpp +++ b/src/platform/linux/logmanagement/src/ConsoleLogger.cpp @@ -6,6 +6,9 @@ #include "ConsoleLogger.h" #include "ClockHandler.h" +#ifdef __ANDROID__ +#include +#endif #include #include #include @@ -76,6 +79,25 @@ ConsoleLogger::ConsoleLogger() } } +#ifdef __ANDROID__ +static android_LogPriority +levelToAndroidLevel( LogLevel level ) +{ + switch ( level ) + { + case LogLevel::Error: + return ANDROID_LOG_ERROR; + case LogLevel::Warning: + return ANDROID_LOG_WARN; + case LogLevel::Trace: + return ANDROID_LOG_DEBUG; + case LogLevel::Info: + default: + return ANDROID_LOG_INFO; + } +} +#endif + void ConsoleLogger::logMessage( LogLevel level, const std::string &filename, @@ -85,6 +107,17 @@ ConsoleLogger::logMessage( LogLevel level, { if ( level >= gSystemWideLogLevel ) { +#ifdef __ANDROID__ + __android_log_print( levelToAndroidLevel( level ), + "FWE", + "[Thread: %" PRIu64 "] [%s] [%s:%i] [%s()]: [%s]", + currentThreadId(), + timeAsString().c_str(), + filename.c_str(), + lineNumber, + function.c_str(), + logEntry.c_str() ); +#else std::printf( "%s[Thread: %" PRIu64 "] [%s] [%s] [%s:%i] [%s()]: [%s]%s\n", levelToColor( level ).c_str(), currentThreadId(), @@ -95,6 +128,7 @@ ConsoleLogger::logMessage( LogLevel level, function.c_str(), logEntry.c_str(), mColorEnabled ? Color::reset.c_str() : "" ); +#endif forwardLog( level, filename, lineNumber, function, logEntry ); } } diff --git a/src/platform/linux/logmanagement/src/TraceModule.cpp b/src/platform/linux/logmanagement/src/TraceModule.cpp index 26caeb10..76a22ff2 100644 --- a/src/platform/linux/logmanagement/src/TraceModule.cpp +++ b/src/platform/linux/logmanagement/src/TraceModule.cpp @@ -145,6 +145,12 @@ TraceModule::getVariableName( TraceVariable variable ) return "ObdPrecU64_id60"; case TraceVariable::OBD_POSSIBLE_PRECISION_LOSS_INT64: return "ObdPrecI64_id61"; + case TraceVariable::MQTT_SIGNAL_MESSAGES_SENT_OUT: // Can be multiple messages per event id + return "MqttSignalMessages"; + case TraceVariable::MQTT_HEAP_USAGE: + return "MqttHeapSize"; + case TraceVariable::SIGNAL_BUFFER_SIZE: + return "SigBufSize"; default: return nullptr; } diff --git a/tools/android-app/.gitignore b/tools/android-app/.gitignore new file mode 100644 index 00000000..aa1f8029 --- /dev/null +++ b/tools/android-app/.gitignore @@ -0,0 +1,10 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/captures +.externalNativeBuild +.cxx +local.properties +app/src/main/assets/THIRD-PARTY-LICENSES diff --git a/tools/android-app/README.md b/tools/android-app/README.md new file mode 100644 index 00000000..ff5787a8 --- /dev/null +++ b/tools/android-app/README.md @@ -0,0 +1,127 @@ +# Android App for AWS IoT FleetWise Edge + +This app demonstrates AWS IoT FleetWise using a smart phone with Android 8.0+ and a commonly +available [ELM327 Bluetooth OBD adapter](https://www.amazon.com/s?k=elm327+bluetooth). + +## User guide + +1. Download the latest APK from GitHub to the phone: + https://github.com/aws/aws-iot-fleetwise-edge/releases/latest/download/aws-iot-fleetwise-edge.apk + +1. Install the app, which will require enabling installation from unknown sources. + +1. Open the app, which will ask permission to collect GPS location data and access Bluetooth + devices. + +1. Open the AWS CloudShell: [Launch CloudShell](https://console.aws.amazon.com/cloudshell/home) + +1. Run the following script to provision credentials for the app to connect to your AWS account. You + will be asked to enter the name of an existing S3 bucket, in which the credentials will be saved. + **Note:** It is important that the chosen S3 bucket is not public. + + ```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 \ + && pip3 install segno \ + && ./provision.sh + ``` + +1. When the script completes, the path to a QR code image file is given: + `/home/cloudshell-user/aws-iot-fleetwise-edge/tools/android-app/cloud/config/provisioning-qr-code.png`. + Copy this 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 image. + +1. Scan the QR code with the phone, which will open the link in the AWS IoT FleetWise Edge app and + download the credentials. This will cause the app to connect to the cloud and you should shortly + see the status `MQTT connection: CONNECTED`. + + - **If scanning the QR code does not open the app, and instead opens a webpage:** Copy the + webpage link, then open the AWS IoT FleetWise Edge app, go to 'Vehicle configuration' and paste + the link. + +1. **Optional:** If you want to delete the credentials from the S3 bucket for security, run the + command suggested by the output of the script which will be similar to: + + ```bash + aws s3 rm s3:///fwdemo-android--creds.json + ``` + +1. In the cloud shell, run the following command to create an AWS IoT FleetWise campaign to collect + GPS and OBD PID 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. Plug in the ELM327 OBD Bluetooth adapter to your vehicle and switch on the ignition. + +1. Go to the Bluetooth menu of your phone, then pair the ELM327 Bluetooth adapter. Typically the the + name of the device is `OBDII` and the pairing PIN number is `1234`. + +1. Go back to the AWS IoT FleetWise Edge app, then go to 'Bluetooth device' and select the ELM327 + device you just paired. + +1. After a short time you should see that the status is updated with: + `Bluetooth: Connected to ELM327 vX.X` and `Supported OBD PIDs: XX XX XX`. + +1. Go to the [Amazon Timestream console](https://us-east-1.console.aws.amazon.com/timestream/home) + and view the collected data. + +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 + ``` + +## 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 + +An Ubuntu 20.04 development machine with 200GB free disk space should be used. + +1. Clone the source code: + + ```bash + git clone https://github.com/aws/aws-iot-fleetwise-edge.git ~/aws-iot-fleetwise-edge \ + && cd ~/aws-iot-fleetwise-edge + ``` + +1. Install the dependencies: + + ```bash + sudo -H ./tools/install-deps-cross-android.sh + ``` + +1. Build the shared libraries: + + ```bash + ./tools/build-fwe-cross-android.sh \ + && ./tools/build-dist.sh \ + build/arm64-v8a/src/executionmanagement/libaws-iot-fleetwise-edge.so:arm64-v8a \ + build/armeabi-v7a/src/executionmanagement/libaws-iot-fleetwise-edge.so:armeabi-v7a + ``` + +1. Build the app: + + ```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 THIRD-PARTY-LICENSES tools/android-app/app/src/main/assets \ + && cd tools/android-app \ + && export ANDROID_HOME=/usr/local/android_sdk ./gradlew assemble + ``` + +### 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. diff --git a/tools/android-app/app/.gitignore b/tools/android-app/app/.gitignore new file mode 100644 index 00000000..aaa06f52 --- /dev/null +++ b/tools/android-app/app/.gitignore @@ -0,0 +1,2 @@ +/build +/src/main/jniLibs diff --git a/tools/android-app/app/build.gradle b/tools/android-app/app/build.gradle new file mode 100644 index 00000000..7f319f53 --- /dev/null +++ b/tools/android-app/app/build.gradle @@ -0,0 +1,32 @@ +plugins { + id 'com.android.application' +} + +android { + namespace 'com.aws.iotfleetwise' + compileSdk 33 + + defaultConfig { + applicationId "com.aws.iotfleetwise" + minSdk 26 + targetSdk 33 + versionCode 1 + versionName "1.0" + } + + buildTypes { + release { + minifyEnabled false + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.9.0' + implementation 'pub.devrel:easypermissions:3.0.0' +} diff --git a/tools/android-app/app/src/main/AndroidManifest.xml b/tools/android-app/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..0b678dbc --- /dev/null +++ b/tools/android-app/app/src/main/AndroidManifest.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/android-app/app/src/main/assets/AmazonRootCA1.pem b/tools/android-app/app/src/main/assets/AmazonRootCA1.pem new file mode 100644 index 00000000..a6f3e92a --- /dev/null +++ b/tools/android-app/app/src/main/assets/AmazonRootCA1.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- diff --git a/tools/android-app/app/src/main/assets/config-0.json b/tools/android-app/app/src/main/assets/config-0.json new file mode 100644 index 00000000..2f464737 --- /dev/null +++ b/tools/android-app/app/src/main/assets/config-0.json @@ -0,0 +1,53 @@ +{ + "version": "1.0", + "networkInterfaces": [ + { + "obdInterface": { + "interfaceName": "vcan0", + "obdStandard": "J1979", + "pidRequestIntervalSeconds": 0, + "dtcRequestIntervalSeconds": 0, + "broadcastRequests": false + }, + "interfaceId": "0", + "type": "obdInterface" + } + ], + "staticConfig": { + "bufferSizes": { + "dtcBufferSize": 100, + "decodedSignalsBufferSize": 10000, + "rawCANFrameBufferSize": 10000 + }, + "threadIdleTimes": { + "inspectionThreadIdleTimeMs": 50, + "socketCANThreadIdleTimeMs": 50, + "canDecoderThreadIdleTimeMs": 50 + }, + "persistency": { + "persistencyPath": ".", + "persistencyPartitionMaxSize": 524288, + "persistencyUploadRetryIntervalMs": 10000 + }, + "internalParameters": { + "readyToPublishDataBufferSize": 10000, + "systemWideLogLevel": "Trace", + "dataReductionProbabilityDisabled": false + }, + "publishToCloudParameters": { + "maxPublishMessageCount": 1000, + "collectionSchemeManagementCheckinIntervalMs": 5000 + }, + "mqttConnection": { + "endpointUrl": "MQTT_ENDPOINT_GOES_HERE", + "clientId": "VEHICLE_ID_GOES_HERE", + "collectionSchemeListTopic": "$aws/iotfleetwise/vehicles/VEHICLE_ID_GOES_HERE/collection_schemes", + "decoderManifestTopic": "$aws/iotfleetwise/vehicles/VEHICLE_ID_GOES_HERE/decoder_manifests", + "canDataTopic": "$aws/iotfleetwise/vehicles/VEHICLE_ID_GOES_HERE/signals", + "checkinTopic": "$aws/iotfleetwise/vehicles/VEHICLE_ID_GOES_HERE/checkins", + "certificate": "CERTIFICATE_GOES_HERE", + "privateKey": "PRIVATE_KEY_GOES_HERE", + "rootCA": "ROOT_CA_GOES_HERE" + } + } +} diff --git a/tools/android-app/app/src/main/java/com/aws/iotfleetwise/AboutActivity.java b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/AboutActivity.java new file mode 100644 index 00000000..d6cb92d7 --- /dev/null +++ b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/AboutActivity.java @@ -0,0 +1,39 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.aws.iotfleetwise; + +import android.app.Activity; +import android.graphics.Color; +import android.os.Bundle; +import android.text.method.LinkMovementMethod; +import android.widget.EditText; +import android.widget.TextView; + +import java.io.IOException; +import java.io.InputStream; + +public class AboutActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_about); + setResult(Activity.RESULT_CANCELED); + + TextView githubTextView = findViewById(R.id.github); + githubTextView.setMovementMethod(LinkMovementMethod.getInstance()); + githubTextView.setLinkTextColor(Color.BLUE); + + try { + InputStream stream = getAssets().open("THIRD-PARTY-LICENSES"); + int size = stream.available(); + byte[] buffer = new byte[size]; + stream.read(buffer); + stream.close(); + String licenses = new String(buffer); + EditText editText = findViewById(R.id.licenses); + editText.setText(licenses); + } catch (IOException ignored) { + } + } +} diff --git a/tools/android-app/app/src/main/java/com/aws/iotfleetwise/BluetoothActivity.java b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/BluetoothActivity.java new file mode 100644 index 00000000..65bbda94 --- /dev/null +++ b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/BluetoothActivity.java @@ -0,0 +1,61 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.aws.iotfleetwise; + +import android.app.Activity; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.Intent; +import android.os.Bundle; + +import android.os.ParcelUuid; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.Set; + +public class BluetoothActivity + extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_bluetooth_device_list); + setResult(Activity.RESULT_CANCELED); + ArrayAdapter deviceListAdapter = new ArrayAdapter<>(this, R.layout.bluetooth_device); + ListView deviceListView = findViewById(R.id.device_list); + deviceListView.setAdapter(deviceListAdapter); + try { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter == null || !adapter.isEnabled()) { + deviceListAdapter.add("Bluetooth disabled"); + return; + } + Set devices = adapter.getBondedDevices(); + for (BluetoothDevice device : devices) { + ParcelUuid[] uuids = device.getUuids(); + for (ParcelUuid uuid : uuids) { + if (uuid.getUuid().equals(Elm327.SERIAL_PORT_UUID)) { + deviceListAdapter.add(device.getName() + "\t" + device.getAddress()); + } + } + } + if (deviceListAdapter.getCount() == 0) { + deviceListAdapter.add("No supported devices"); + return; + } + } + catch (SecurityException e) { + deviceListAdapter.add("Bluetooth access denied"); + return; + } + deviceListView.setOnItemClickListener((parent, view, position, id) -> { + Intent intent = new Intent(); + intent.putExtra("bluetooth_device", ((TextView)view).getText().toString()); + setResult(Activity.RESULT_OK, intent); + finish(); + }); + } +} 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 new file mode 100644 index 00000000..1f466d9e --- /dev/null +++ b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/ConfigureVehicleActivity.java @@ -0,0 +1,38 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.aws.iotfleetwise; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.widget.EditText; + +public class ConfigureVehicleActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_configure_vehicle); + setResult(Activity.RESULT_CANCELED); + EditText linkEditText = findViewById(R.id.provisioning_link); + linkEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + } + + @Override + public void afterTextChanged(Editable editable) { + Intent intent = new Intent(); + intent.putExtra("provisioning_link", linkEditText.getText().toString()); + setResult(Activity.RESULT_OK, intent); + finish(); + } + }); + } +} diff --git a/tools/android-app/app/src/main/java/com/aws/iotfleetwise/Elm327.java b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/Elm327.java new file mode 100644 index 00000000..053f0bc3 --- /dev/null +++ b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/Elm327.java @@ -0,0 +1,166 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.aws.iotfleetwise; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothSocket; +import android.util.Log; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +public class Elm327 { + public static final UUID SERIAL_PORT_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); + private static final int TIMEOUT_RESET_MS = 2000; + private static final int TIMEOUT_SETUP_MS = 500; + private static final int TIMEOUT_OBD_MS = 500; + private static final int TIMEOUT_POLL_MS = 50; + private static final String CMD_RESET = "AT Z"; + private static final String CMD_SET_PROTOCOL_AUTO = "AT SP 0"; + public static final String CMD_OBD_SUPPORTED_PIDS_0 = "01 00"; + + private BluetoothSocket mSocket = null; + private String mLastAddress = ""; + private String mStatus = ""; + + void connect(String deviceParam) + { + if (!deviceParam.contains("\t")) { + mStatus = "No device selected"; + return; + } + String[] deviceInfo = deviceParam.split("\t"); + String deviceAddress = deviceInfo[1]; + try { + if (mSocket != null && mSocket.isConnected()) { + if (mLastAddress.equals(deviceAddress)) { + return; + } + Log.i("ELM327.connect", "Closing connection to " + mLastAddress); + mSocket.close(); + } + Log.i("ELM327.connect", "Connecting to " + deviceAddress); + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + BluetoothDevice device = bluetoothAdapter.getRemoteDevice(deviceAddress); + mSocket = device.createRfcommSocketToServiceRecord(SERIAL_PORT_UUID); + mSocket.connect(); + mLastAddress = deviceAddress; + + Log.i("ELM327.connect", "Resetting device"); + List res = sendCommand(CMD_RESET, TIMEOUT_RESET_MS); + if (res == null) { + throw new RuntimeException("Error resetting ELM327"); + } + String version = res.get(0); + + Log.i("ELM327.connect", "Set automatic protocol"); + res = sendCommand(CMD_SET_PROTOCOL_AUTO, TIMEOUT_SETUP_MS); + if (res == null) { + throw new RuntimeException("Error setting ELM327 protocol"); + } + + mStatus = "Connected to "+version; + } + catch (SecurityException e) { + mSocket = null; + mStatus = "Access denied"; + } + catch (Exception e) { + mSocket = null; + mStatus = "Connection error: "+e.getMessage(); + } + } + + public String sendObdRequest(String request) { + if (mSocket == null) { + return ""; + } + List res = sendCommand(request, TIMEOUT_OBD_MS); + if (res == null || res.size() == 0) { + return ""; + } + return res.get(0); + } + + private List sendCommand(String request, int timeout) { + try { + // Flush the input: + receiveResponse(0, null); + // Send the command: + Log.i("ELM327.tx", request); + OutputStream outputStream = mSocket.getOutputStream(); + outputStream.write((request + "\r").getBytes()); + outputStream.flush(); + // Receive the response: + return receiveResponse(timeout, request); + } + catch (Exception e) { + mSocket = null; + mStatus = "Request error: "+e.getMessage(); + return null; + } + } + + private List receiveResponse(int timeout, String request) throws IOException { + InputStream inputStream = mSocket.getInputStream(); + List result = new ArrayList<>(); + StringBuilder lineBuilder = new StringBuilder(); + int timeoutLeft = timeout; + while (true) { + if (inputStream.available() == 0) { + if (timeoutLeft <= 0) { + if (timeout > 0) { + Log.e("ELM327.rx", "timeout"); + } + return null; + } + try { + Thread.sleep(TIMEOUT_POLL_MS); + timeoutLeft -= TIMEOUT_POLL_MS; + } catch (InterruptedException e) { + // Carry on + } + continue; + } + char chr = (char)inputStream.read(); + switch (chr) { + case '>': + return result; + case '\n': + case '\r': + String line = lineBuilder.toString(); + lineBuilder.setLength(0); + if (line.equals("?")) { + Log.e("ELM327.rx", line); + return null; + } + else if (line.equals("SEARCHING...")) { + Log.d("ELM327.rx", line); + } + else if (line.equals("") || line.equals(request)) { + // Ignore blank lines or the echoed command + } + else { + Log.i("ELM327.rx", line); + result.add(line); + } + break; + default: + lineBuilder.append(chr); + break; + } + } + } + + public String getStatus() + { + return mStatus; + } +} 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 new file mode 100644 index 00000000..3b266150 --- /dev/null +++ b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/Fwe.java @@ -0,0 +1,76 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.aws.iotfleetwise; + +import android.content.res.AssetManager; + +public class Fwe { + /** + * Run FWE. This will block until the `stop` method is called. + * @param assetManager Android asset manager object, used to access the files in `assets/` + * @param vehicleName Name of the vehicle + * @param endpointUrl AWS IoT Core endpoint URL + * @param certificate AWS IoT Core certificate + * @param privateKey AWS IoT Core private key + * @param mqttTopicPrefix MQTT topic prefix, which should be "$aws/iotfleetwise/" + * @return Zero on success, non-zero on error + */ + public native static int run( + AssetManager assetManager, + String vehicleName, + String endpointUrl, + String certificate, + String privateKey, + String mqttTopicPrefix); + + /** + * Stop FWE + */ + public native static void stop(); + + /** + * Get the OBD PIDs that should be requested according to the campaign. + * @return Array of OBD PID identifiers to request + */ + public native static int[] getObdPidsToRequest(); + + /** + * Set the OBD response to a PID request for a given PID. + * @param pid PID + * @param response Array of response bytes received from vehicle + */ + public native static void setObdPidResponse(int pid, int[] response); + + /** + * Ingest raw CAN message + * @param interfaceId Interface identifier, as defined in the static config JSON file + * @param timestamp Timestamp of the message in milliseconds since the epoch, or zero to use the system time + * @param messageId CAN message ID in Linux SocketCAN format + * @param data CAN message data + */ + public native static void ingestCanMessage(String interfaceId, long timestamp, int messageId, byte[] data); + + /** + * Set the GPS location + * @param latitude Latitude + * @param longitude Longitude + */ + public native static void setLocation(double latitude, double longitude); + + /** + * Get a status summary + * @return Status summary + */ + public native static String getStatusSummary(); + + /** + * Get the version + * @return Version + */ + public native static String getVersion(); + + static { + System.loadLibrary("aws-iot-fleetwise-edge"); + } +} 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 new file mode 100644 index 00000000..a818da10 --- /dev/null +++ b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/FweApplication.java @@ -0,0 +1,291 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.aws.iotfleetwise; + +import android.app.Application; +import android.content.Context; +import android.content.SharedPreferences; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Build; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +public class FweApplication + extends Application + implements + SharedPreferences.OnSharedPreferenceChangeListener, + LocationListener { + private static final String LOCATION_PROVIDER = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + ? LocationManager.FUSED_PROVIDER + : LocationManager.GPS_PROVIDER; + + private SharedPreferences mPrefs = null; + private Elm327 mElm327 = null; + private LocationManager mLocationManager = null; + private Location mLastLocation = null; + private List mSupportedPids = null; + private final Object mSupportedPidsLock = new Object(); + + @Override + public void onLocationChanged(Location loc) { + Log.i("FweApplication", "Location change: Lat: " + loc.getLatitude() + " Long: " + loc.getLongitude()); + } + + @Override + public void onStatusChanged(String s, int i, Bundle bundle) { + Log.i("FweApplication", "Location status changed"); + } + + @Override + public void onProviderEnabled(String s) { + Log.i("FweApplication", "Location provider enabled: " + s); + } + + @Override + public void onProviderDisabled(String s) { + Log.i("FweApplication", "Location provider disabled: " + s); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (!mPrefs.getString("vehicle_name", "").equals("") + && !mPrefs.getString("mqtt_endpoint_url", "").equals("") + && !mPrefs.getString("mqtt_certificate", "").equals("") + && !mPrefs.getString("mqtt_private_key", "").equals("") + && !mFweThread.isAlive()) { + mFweThread.start(); + } + mDataAcquisitionThread.interrupt(); + } + + @Override + public void onCreate() { + super.onCreate(); + mPrefs = PreferenceManager.getDefaultSharedPreferences(this); + mPrefs.registerOnSharedPreferenceChangeListener(this); + onSharedPreferenceChanged(null, null); + mElm327 = new Elm327(); + mDataAcquisitionThread.start(); + } + + void requestLocationUpdates() { + if (mLocationManager == null) { + Log.i("FweApplication", "Requesting location access"); + mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); + try { + mLocationManager.requestLocationUpdates(LOCATION_PROVIDER, 5000, 10, this); + } catch (SecurityException e) { + Log.e("FweApplication", "Location access denied"); + mLocationManager = null; + } + } + } + + void serviceLocation() { + if (mLocationManager == null) { + return; + } + try { + mLastLocation = mLocationManager.getLastKnownLocation(LOCATION_PROVIDER); + if (mLastLocation == null) { + Log.d("FweApplication", "Location unknown"); + return; + } + Fwe.setLocation(mLastLocation.getLatitude(), mLastLocation.getLongitude()); + } + catch (SecurityException e) { + Log.d("FweApplication", "Location access denied"); + } + } + + private String getLocationSummary() + { + if (mLastLocation == null) { + return "UNKNOWN"; + } + return String.format(Locale.getDefault(), "%f, %f", mLastLocation.getLatitude(), mLastLocation.getLongitude()); + } + + private int getUpdateTime() + { + int updateTime = R.string.default_update_time; + try { + updateTime = Integer.parseInt(mPrefs.getString("update_time", String.valueOf(R.string.default_update_time))); + } + catch (Exception ignored) { + } + if (updateTime == 0) { + updateTime = R.string.default_update_time; + } + updateTime *= 1000; + return updateTime; + } + + private static int chrToNibble(char chr) + { + int res; + if (chr >= '0' && chr <= '9') { + res = chr - '0'; + } + else if (chr >= 'A' && chr <= 'F') { + res = 10 + chr - 'A'; + } + else { + res = -1; // Invalid hex char + } + return res; + } + + private static int[] convertResponse(String response) + { + List responseList = new ArrayList<>(); + for (int i = 0; (i + 1) < response.length(); i+=2) + { + int highNibble = chrToNibble(response.charAt(i)); + int lowNibble = chrToNibble(response.charAt(i+1)); + if (highNibble < 0 || lowNibble < 0) + { + return null; + } + responseList.add((highNibble << 4) + lowNibble); + // Skip over spaces: + if ((i + 2) < response.length() && response.charAt(i+2) == ' ') { + i++; + } + } + // Convert list to array: + return responseList.stream().mapToInt(Integer::intValue).toArray(); + } + + Thread mDataAcquisitionThread = new Thread(() -> { + while (true) { + Log.i("FweApplication", "Starting data acquisition"); + String bluetoothDevice = mPrefs.getString("bluetooth_device", ""); + + serviceOBD(bluetoothDevice); + serviceLocation(); + + // Wait for update time: + try { + Thread.sleep(getUpdateTime()); + } catch (InterruptedException e) { + // Carry on + } + } + }); + + private void serviceOBD(String bluetoothDevice) + { + mElm327.connect(bluetoothDevice); + if (!checkVehicleConnected()) { + return; + } + int[] pidsToRequest = Arrays.stream(Fwe.getObdPidsToRequest()).sorted().toArray(); + if (pidsToRequest.length == 0) { + return; + } + List supportedPids = new ArrayList<>(); + for (int pid : pidsToRequest) { + if ((mSupportedPids != null) && !mSupportedPids.contains(pid)) { + continue; + } + Log.i("FweApplication", String.format("Requesting PID: 0x%02X", pid)); + String request = String.format("01 %02X", pid); + String responseString = mElm327.sendObdRequest(request); + int[] responseBytes = convertResponse(responseString); + if ((responseBytes == null) || (responseBytes.length == 0)) { + Log.e("FweApplication", String.format("No response for PID: 0x%02X", pid)); + // If vehicle is disconnected: + if (mSupportedPids != null) { + synchronized (mSupportedPidsLock) { + mSupportedPids = null; + } + return; + } + } + else { + supportedPids.add(pid); + Fwe.setObdPidResponse(pid, responseBytes); + } + } + if ((mSupportedPids == null) && (supportedPids.size() > 0)) { + StringBuilder sb = new StringBuilder(); + for (int b : supportedPids) { + sb.append(String.format("%02X ", b)); + } + Log.i("FweApplication", "Supported PIDs: " + sb.toString()); + synchronized (mSupportedPidsLock) { + mSupportedPids = supportedPids; + } + } + } + + private boolean checkVehicleConnected() + { + if (mSupportedPids != null) { + return true; + } + Log.i("FweApplication", "Checking if vehicle connected..."); + String response = mElm327.sendObdRequest(Elm327.CMD_OBD_SUPPORTED_PIDS_0); + int[] responseBytes = convertResponse(response); + boolean result = (responseBytes != null) && (responseBytes.length > 0); + Log.i("FweApplication", "Vehicle is " + (result ? "CONNECTED" : "DISCONNECTED")); + return result; + } + + Thread mFweThread = new Thread(() -> { + Log.i("FweApplication", "Starting FWE"); + String vehicleName = mPrefs.getString("vehicle_name", ""); + String endpointUrl = mPrefs.getString("mqtt_endpoint_url", ""); + String certificate = mPrefs.getString("mqtt_certificate", ""); + String privateKey = mPrefs.getString("mqtt_private_key", ""); + String mqttTopicPrefix = mPrefs.getString("mqtt_topic_prefix", ""); + int res = Fwe.run( + getAssets(), + vehicleName, + endpointUrl, + certificate, + privateKey, + mqttTopicPrefix); + if (res != 0) + { + Log.e("FweApplication", String.format("FWE exited with code %d", res)); + } + else { + Log.i("FweApplication", "FWE finished"); + } + }); + + public String getStatusSummary() + { + String supportedPids; + synchronized (mSupportedPidsLock) { + if (mSupportedPids == null) { + supportedPids = "VEHICLE DISCONNECTED"; + } + else if (mSupportedPids.size() == 0) { + supportedPids = "NONE"; + } + else { + StringBuilder sb = new StringBuilder(); + 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(); + } +} 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 new file mode 100644 index 00000000..99c010c5 --- /dev/null +++ b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/MainActivity.java @@ -0,0 +1,257 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.aws.iotfleetwise; + +import android.Manifest; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceManager; +import android.util.JsonReader; +import android.util.Log; +import android.view.View; +import android.view.WindowManager; +import android.widget.TextView; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.EasyPermissions; + +public class MainActivity extends PreferenceActivity + implements SharedPreferences.OnSharedPreferenceChangeListener +{ + private static final int REQUEST_PERMISSIONS = 100; + private static final int REQUEST_BLUETOOTH = 200; + private static final int REQUEST_CONFIGURE_VEHICLE = 300; + private static final int REQUEST_ABOUT = 400; + SharedPreferences mPrefs = null; + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } + + @AfterPermissionGranted(REQUEST_PERMISSIONS) // This causes this function to be called again after the user has approved access + private void requestPermissions() + { + List perms = new ArrayList<>(); + perms.add(Manifest.permission.ACCESS_FINE_LOCATION); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + perms.add(Manifest.permission.BLUETOOTH_CONNECT); + } + String[] permsArray = perms.toArray(new String[0]); + if (!EasyPermissions.hasPermissions(this, permsArray)) { + EasyPermissions.requestPermissions(this, "Location and Bluetooth access required", REQUEST_PERMISSIONS, permsArray); + } + else { + ((FweApplication)getApplication()).requestLocationUpdates(); + } + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + 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))); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case REQUEST_BLUETOOTH: + if (resultCode == Activity.RESULT_OK) { + String deviceParam = data.getExtras().getString("bluetooth_device"); + SharedPreferences.Editor edit = mPrefs.edit(); + edit.putString("bluetooth_device", deviceParam); + edit.apply(); + } + break; + case REQUEST_CONFIGURE_VEHICLE: + if (resultCode == Activity.RESULT_OK) { + String link = data.getExtras().getString("provisioning_link"); + downloadCredentials(link); + } + break; + } + } + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.preferences); + setContentView(R.layout.activity_main); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + TextView versionTextView = (TextView)findViewById(R.id.version); + versionTextView.setText(Fwe.getVersion()); + mPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + mPrefs.registerOnSharedPreferenceChangeListener(this); + 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; + }); + mStatusUpdateThread.start(); + + // Handle deep link: + Intent appLinkIntent = getIntent(); + Uri appLinkData = appLinkIntent.getData(); + if (appLinkData != null) { + final String link = appLinkData.toString(); + if (link != null) { + downloadCredentials(link); + } + } + } + + @Override + public void onDestroy() { + mStatusUpdateThread.interrupt(); + mPrefs.unregisterOnSharedPreferenceChangeListener(this); + super.onDestroy(); + } + + public void onAboutClick(View v) { + startActivityForResult(new Intent(MainActivity.this, AboutActivity.class), REQUEST_ABOUT); + } + + private void downloadCredentials(String deepLink) + { + Thread t = new Thread(() -> { + Log.i("DownloadCredentials", "Deep link: " + deepLink); + final String urlParam = "url="; + + // First try getting the S3 link normally from the deep link: + Uri uri = Uri.parse(deepLink); + String fragment = uri.getFragment(); + if (fragment != null) { + int urlStart = fragment.indexOf(urlParam); + if (urlStart >= 0) { + String s3Link = fragment.substring(urlStart + urlParam.length()); + if (downloadCredentialsFromS3(s3Link)) { + return; + } + } + } + + // Some QR code scanning apps url decode the deep link, so try that next: + int urlStart = deepLink.indexOf(urlParam); + if (urlStart >= 0) { + String s3Link = deepLink.substring(urlStart + urlParam.length()); + if (downloadCredentialsFromS3(s3Link)) { + return; + } + } + + // Neither worked, show an error: + runOnUiThread(() -> { + AlertDialog alertDialog = new AlertDialog.Builder(MainActivity.this).create(); + alertDialog.setTitle("Error"); + alertDialog.setMessage("Invalid credentials link"); + alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", (dialog, which) -> dialog.dismiss()); + alertDialog.show(); + }); + }); + t.start(); + } + + private boolean downloadCredentialsFromS3(String s3Link) + { + 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; + } + } finally { + urlConnection.disconnect(); + } + } catch (IOException e) { + e.printStackTrace(); + } + return false; + } + + Thread mStatusUpdateThread = new Thread(() -> { + Log.i("MainActivity", "Status update thread started"); + try + { + while (true) + { + Thread.sleep(1000); + String status = ((FweApplication)getApplication()).getStatusSummary(); + runOnUiThread(() -> findPreference("debug_status").setSummary(status)); + } + } + catch (InterruptedException ignored) + { + } + Log.i("MainActivity", "Status update thread finished"); + }); +} diff --git a/tools/android-app/app/src/main/java/com/aws/iotfleetwise/StatusEditTextPreference.java b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/StatusEditTextPreference.java new file mode 100644 index 00000000..cb9caddf --- /dev/null +++ b/tools/android-app/app/src/main/java/com/aws/iotfleetwise/StatusEditTextPreference.java @@ -0,0 +1,36 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.aws.iotfleetwise; + +import android.content.Context; +import android.preference.EditTextPreference; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; + +public class StatusEditTextPreference extends EditTextPreference { + + public StatusEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public StatusEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public StatusEditTextPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public StatusEditTextPreference(Context context) { + super(context); + } + + @Override + protected void onBindView(View view) { + super.onBindView(view); + TextView summary = (TextView)view.findViewById(android.R.id.summary); + summary.setMaxLines(20); + } +} diff --git a/tools/android-app/app/src/main/res/layout/activity_about.xml b/tools/android-app/app/src/main/res/layout/activity_about.xml new file mode 100644 index 00000000..09b449d8 --- /dev/null +++ b/tools/android-app/app/src/main/res/layout/activity_about.xml @@ -0,0 +1,34 @@ + + + + + + + + + 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 new file mode 100644 index 00000000..1c807597 --- /dev/null +++ b/tools/android-app/app/src/main/res/layout/activity_bluetooth_device_list.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/tools/android-app/app/src/main/res/layout/activity_configure_vehicle.xml b/tools/android-app/app/src/main/res/layout/activity_configure_vehicle.xml new file mode 100644 index 00000000..4a207d80 --- /dev/null +++ b/tools/android-app/app/src/main/res/layout/activity_configure_vehicle.xml @@ -0,0 +1,29 @@ + + + + + + + + + diff --git a/tools/android-app/app/src/main/res/layout/activity_main.xml b/tools/android-app/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..78bd1f41 --- /dev/null +++ b/tools/android-app/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + diff --git a/tools/android-app/app/src/main/res/layout/bluetooth_device.xml b/tools/android-app/app/src/main/res/layout/bluetooth_device.xml new file mode 100644 index 00000000..2633ea62 --- /dev/null +++ b/tools/android-app/app/src/main/res/layout/bluetooth_device.xml @@ -0,0 +1,6 @@ + + diff --git a/tools/android-app/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/tools/android-app/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 00000000..948a3070 Binary files /dev/null and b/tools/android-app/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/tools/android-app/app/src/main/res/values-night/themes.xml b/tools/android-app/app/src/main/res/values-night/themes.xml new file mode 100644 index 00000000..5ad3279d --- /dev/null +++ b/tools/android-app/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + diff --git a/tools/android-app/app/src/main/res/values/colors.xml b/tools/android-app/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..f3ea23d3 --- /dev/null +++ b/tools/android-app/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #C5E1A5 + #8BC34A + #689F38 + #FFCC80 + #F57C00 + #FF000000 + #FFFFFFFF + diff --git a/tools/android-app/app/src/main/res/values/strings.xml b/tools/android-app/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..e0302d86 --- /dev/null +++ b/tools/android-app/app/src/main/res/values/strings.xml @@ -0,0 +1,15 @@ + + AWS IoT FleetWise Edge + Configure vehicle + Use your QR code scanner app to scan a provisioning QR code.\n\nAlternatively paste a provisioning link below: + Provisioning link + Bluetooth device list + Vehicle configuration + Update time [seconds] + Status + Bluetooth device + 5 + About + Open source licenses: + Learn more about AWS IoT FleetWise Edge at:\nhttps://github.com/aws/aws-iot-fleetwise-edge + diff --git a/tools/android-app/app/src/main/res/values/themes.xml b/tools/android-app/app/src/main/res/values/themes.xml new file mode 100644 index 00000000..4d968f5d --- /dev/null +++ b/tools/android-app/app/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + diff --git a/tools/android-app/app/src/main/res/xml/preferences.xml b/tools/android-app/app/src/main/res/xml/preferences.xml new file mode 100644 index 00000000..7d0c8e97 --- /dev/null +++ b/tools/android-app/app/src/main/res/xml/preferences.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + diff --git a/tools/android-app/build.gradle b/tools/android-app/build.gradle new file mode 100644 index 00000000..9d4048d4 --- /dev/null +++ b/tools/android-app/build.gradle @@ -0,0 +1,5 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id 'com.android.application' version '7.3.1' apply false + id 'com.android.library' version '7.3.1' apply false +} diff --git a/tools/android-app/cloud/.gitignore b/tools/android-app/cloud/.gitignore new file mode 100644 index 00000000..f733c4b5 --- /dev/null +++ b/tools/android-app/cloud/.gitignore @@ -0,0 +1 @@ +config/ diff --git a/tools/android-app/cloud/campaign-android.json b/tools/android-app/cloud/campaign-android.json new file mode 100644 index 00000000..ee9a7de2 --- /dev/null +++ b/tools/android-app/cloud/campaign-android.json @@ -0,0 +1,93 @@ +{ + "compression": "SNAPPY", + "diagnosticsMode": "SEND_ACTIVE_DTCS", + "spoolingMode": "TO_DISK", + "collectionScheme": { + "timeBasedCollectionScheme": { + "periodMs": 10000 + } + }, + "signalsToCollect": [ + { + "name": "Vehicle.CurrentLocation.Latitude" + }, + { + "name": "Vehicle.CurrentLocation.Longitude" + }, + { + "name": "Vehicle.OBD.EngineLoad" + }, + { + "name": "Vehicle.OBD.CoolantTemperature" + }, + { + "name": "Vehicle.OBD.FuelPressure" + }, + { + "name": "Vehicle.OBD.EngineSpeed" + }, + { + "name": "Vehicle.OBD.Speed" + }, + { + "name": "Vehicle.OBD.IntakeTemp" + }, + { + "name": "Vehicle.OBD.ThrottlePosition" + }, + { + "name": "Vehicle.OBD.RunTime" + }, + { + "name": "Vehicle.OBD.FuelLevel" + }, + { + "name": "Vehicle.OBD.BarometricPressure" + }, + { + "name": "Vehicle.OBD.ControlModuleVoltage" + }, + { + "name": "Vehicle.OBD.AbsoluteLoad" + }, + { + "name": "Vehicle.OBD.AmbientAirTemperature" + }, + { + "name": "Vehicle.OBD.FuelType" + }, + { + "name": "Vehicle.OBD.HybridBatteryRemaining" + }, + { + "name": "Vehicle.OBD.OilTemperature" + }, + { + "name": "Vehicle.OBD.FuelRate" + }, + { + "name": "Vehicle.OBD.DemandEngineTorque" + }, + { + "name": "Vehicle.OBD.ActualEngineTorque" + }, + { + "name": "Vehicle.OBD.MafA" + }, + { + "name": "Vehicle.OBD.MafB" + }, + { + "name": "Vehicle.OBD.MAF" + }, + { + "name": "Vehicle.OBD.TransmissionActualGearStatusSupport" + }, + { + "name": "Vehicle.OBD.TransmissionActualGearRatio" + }, + { + "name": "Vehicle.OBD.Odometer" + } + ] +} diff --git a/tools/android-app/cloud/clean-up.sh b/tools/android-app/cloud/clean-up.sh new file mode 100755 index 00000000..5ce390b0 --- /dev/null +++ b/tools/android-app/cloud/clean-up.sh @@ -0,0 +1,184 @@ +#!/bin/bash +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +ENDPOINT_URL="" +ENDPOINT_URL_OPTION="" +REGION="us-east-1" +VEHICLE_NAME_FILENAME="config/vehicle-name.txt" +VEHICLE_NAME="" +THING_POLICY_FILENAME="config/thing-policy.txt" +THING_POLICY="" +SERVICE_ROLE_ARN_FILENAME="config/service-role-arn.txt" +SERVICE_ROLE_ARN="" +TIMESTAMP_FILENAME="config/timestamp.txt" +TIMESTAMP="" + +parse_args() { + while [ "$#" -gt 0 ]; do + case $1 in + --vehicle-name) + VEHICLE_NAME=$2 + shift + ;; + --timestamp) + TIMESTAMP=$2 + shift + ;; + --service-role-arn) + SERVICE_ROLE_ARN=$2 + shift + ;; + --thing-policy) + THING_POLICY=$2 + shift + ;; + --endpoint-url) + ENDPOINT_URL=$2 + shift + ;; + --region) + REGION=$2 + shift + ;; + --help) + echo "Usage: $0 [OPTION]" + echo " --vehicle-name Vehicle name, by default will be read from ${VEHICLE_NAME_FILENAME}" + echo " --timestamp Timestamp of setup-iotfleetwise.sh script, by default will be read from ${TIMESTAMP_FILENAME}" + echo " --service-role-arn Service role ARN for accessing Timestream, by default will be read from ${SERVICE_ROLE_ARN_FILENAME}" + echo " --thing-policy Name of IoT thing policy, by default will be read from ${THING_POLICY_FILENAME}" + 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 + shift + done + + if [ "${ENDPOINT_URL}" != "" ]; then + ENDPOINT_URL_OPTION="--endpoint-url ${ENDPOINT_URL}" + fi + + if [ "${VEHICLE_NAME}" == "" ]; then + if [ -f "${VEHICLE_NAME_FILENAME}" ]; then + VEHICLE_NAME=`cat ${VEHICLE_NAME_FILENAME}` + else + echo "Error: vehicle name not provided" + exit -1 + fi + fi + + if [ "${TIMESTAMP}" == "" ]; then + if [ -f "${TIMESTAMP_FILENAME}" ]; then + TIMESTAMP=`cat ${TIMESTAMP_FILENAME}` + fi + fi + + if [ "${SERVICE_ROLE_ARN}" == "" ]; then + if [ -f "${SERVICE_ROLE_ARN_FILENAME}" ]; then + SERVICE_ROLE_ARN=`cat ${SERVICE_ROLE_ARN_FILENAME}` + fi + fi + + if [ "${THING_POLICY}" == "" ]; then + if [ -f "${THING_POLICY_FILENAME}" ]; then + THING_POLICY=`cat ${THING_POLICY_FILENAME}` + fi + fi +} + +echo "=========================================" +echo "AWS IoT FleetWise Android Clean-up Script" +echo "=========================================" + +parse_args "$@" + +echo "Getting AWS account ID..." +ACCOUNT_ID=`aws sts get-caller-identity --query "Account" --output text` +echo ${ACCOUNT_ID} + +if [ "${TIMESTAMP}" != "" ]; then + NAME="${VEHICLE_NAME}-${TIMESTAMP}" + + echo "Suspending campaign..." + aws iotfleetwise update-campaign \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --name ${NAME}-campaign \ + --action SUSPEND | jq -r .arn || true + + echo "Deleting campaign..." + aws iotfleetwise delete-campaign \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --name ${NAME}-campaign | jq -r .arn || true + + echo "Disassociating vehicle ${VEHICLE_NAME}..." + aws iotfleetwise disassociate-vehicle-fleet \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --fleet-id ${NAME}-fleet \ + --vehicle-name "${VEHICLE_NAME}" || true + + echo "Deleting vehicle ${VEHICLE_NAME}..." + aws iotfleetwise delete-vehicle \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --vehicle-name "${VEHICLE_NAME}" | jq -r .arn || true + + echo "Deleting fleet..." + aws iotfleetwise delete-fleet \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --fleet-id ${NAME}-fleet | jq -r .arn || true + + echo "Deleting decoder manifest..." + aws iotfleetwise delete-decoder-manifest \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --name ${NAME}-decoder-manifest | jq -r .arn || true + + echo "Deleting model manifest..." + aws iotfleetwise delete-model-manifest \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --name ${NAME}-model-manifest | jq -r .arn || true + + rm -f ${TIMESTAMP_FILENAME} +fi + +if [ "${SERVICE_ROLE_ARN}" != "" ]; then + SERVICE_ROLE=`echo "${SERVICE_ROLE_ARN}" | cut -d "/" -f 2` + SERVICE_ROLE_POLICY_ARN="arn:aws:iam::${ACCOUNT_ID}:policy/${SERVICE_ROLE}-policy" + echo "Detatching policy..." + aws iam detach-role-policy --role-name ${SERVICE_ROLE} --policy-arn ${SERVICE_ROLE_POLICY_ARN} --region ${REGION} || true + echo "Deleting policy..." + aws iam delete-policy --policy-arn ${SERVICE_ROLE_POLICY_ARN} --region ${REGION} || true + echo "Deleting role..." + aws iam delete-role --role-name ${SERVICE_ROLE} --region ${REGION} || true + + rm -f ${SERVICE_ROLE_ARN_FILENAME} +fi + +echo "Listing thing principals..." +THING_PRINCIPAL=`aws iot list-thing-principals --region ${REGION} --thing-name ${VEHICLE_NAME} | jq -r ".principals[0]" || true` +echo "Detaching thing principal..." +aws iot detach-thing-principal --region ${REGION} --thing-name ${VEHICLE_NAME} --principal ${THING_PRINCIPAL} || true +echo "Detaching thing policy..." +aws iot detach-policy --region ${REGION} --policy-name ${THING_POLICY} --target ${THING_PRINCIPAL} || true +echo "Deleting thing..." +aws iot delete-thing --region ${REGION} --thing-name ${VEHICLE_NAME} || true +echo "Deleting thing policy..." +aws iot delete-policy --region ${REGION} --policy-name ${THING_POLICY} || true +CERTIFICATE_ID=`echo "${THING_PRINCIPAL}" | cut -d "/" -f 2 || true` +echo "Updating certificate as INACTIVE..." +aws iot update-certificate --region ${REGION} --certificate-id ${CERTIFICATE_ID} --new-status INACTIVE || true +echo "Deleting certificate..." +aws iot delete-certificate --region ${REGION} --certificate-id ${CERTIFICATE_ID} --force-delete || true + +rm -f ${VEHICLE_NAME_FILENAME} +rm -f ${THING_POLICY_FILENAME} +rm -f config/certificate.pem +rm -f config/private-key.key +rm -f config/creds.json +rm -f config/endpoint.txt +rm -f config/provisioning-qr-code.png + +echo "=========" +echo "Finished!" +echo "=========" diff --git a/tools/android-app/cloud/externalGpsDecoders.json b/tools/android-app/cloud/externalGpsDecoders.json new file mode 100644 index 00000000..c422a421 --- /dev/null +++ b/tools/android-app/cloud/externalGpsDecoders.json @@ -0,0 +1,30 @@ +[ + { + "fullyQualifiedName": "Vehicle.CurrentLocation.Longitude", + "type": "CAN_SIGNAL", + "interfaceId": "EXTERNAL-GPS-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 0, + "offset": -2000.0, + "factor": 0.001, + "length": 32 + } + }, + { + "fullyQualifiedName": "Vehicle.CurrentLocation.Latitude", + "type": "CAN_SIGNAL", + "interfaceId": "EXTERNAL-GPS-CAN", + "canSignal": { + "messageId": 1, + "isBigEndian": true, + "isSigned": true, + "startBit": 32, + "offset": -2000.0, + "factor": 0.001, + "length": 32 + } + } +] diff --git a/tools/android-app/cloud/externalGpsNodes.json b/tools/android-app/cloud/externalGpsNodes.json new file mode 100644 index 00000000..7c24d1ec --- /dev/null +++ b/tools/android-app/cloud/externalGpsNodes.json @@ -0,0 +1,22 @@ +[ + { + "branch": { + "fullyQualifiedName": "Vehicle.CurrentLocation", + "description": "CurrentLocation" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.CurrentLocation.Latitude", + "description": "Latitude" + } + }, + { + "sensor": { + "dataType": "DOUBLE", + "fullyQualifiedName": "Vehicle.CurrentLocation.Longitude", + "description": "Longitude" + } + } +] diff --git a/tools/android-app/cloud/network-interfaces.json b/tools/android-app/cloud/network-interfaces.json new file mode 100644 index 00000000..729cd9eb --- /dev/null +++ b/tools/android-app/cloud/network-interfaces.json @@ -0,0 +1,22 @@ +[ + { + "interfaceId": "0", + "type": "OBD_INTERFACE", + "obdInterface": { + "name": "can0", + "requestMessageId": 2015, + "obdStandard": "J1979", + "pidRequestIntervalSeconds": 5, + "dtcRequestIntervalSeconds": 5 + } + }, + { + "interfaceId": "EXTERNAL-GPS-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 new file mode 100755 index 00000000..763ea496 --- /dev/null +++ b/tools/android-app/cloud/provision.sh @@ -0,0 +1,114 @@ +#!/bin/bash +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +S3_BUCKET="" +S3_KEY_PREFIX="" +S3_PRESIGNED_URL_EXPIRY=86400 # One day +ENDPOINT_URL="" +ENDPOINT_URL_OPTION="" +REGION="us-east-1" +TOPIC_PREFIX="\$aws/iotfleetwise/" +TIMESTAMP=`date +%s` +VEHICLE_NAME="fwdemo-android-${TIMESTAMP}" + +parse_args() { + while [ "$#" -gt 0 ]; do + case $1 in + --s3-bucket) + S3_BUCKET=$2 + shift + ;; + --s3-key-prefix) + S3_KEY_PREFIX=$2 + shift + ;; + --s3-presigned-url-expiry) + S3_PRESIGNED_URL_EXPIRY=$2 + shift + ;; + --endpoint-url) + ENDPOINT_URL=$2 + shift + ;; + --region) + REGION=$2 + shift + ;; + --topic-prefix) + TOPIC_PREFIX=$2 + shift + ;; + --help) + echo "Usage: $0 [OPTION]" + 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}" + echo " --endpoint-url The endpoint URL used for AWS CLI calls" + echo " --region The region used for AWS CLI calls, default: ${REGION}" + echo " --topic-prefix IoT MQTT topic prefix, default: ${TOPIC_PREFIX}" + exit 0 + ;; + esac + shift + done + + if [ "${ENDPOINT_URL}" != "" ]; then + ENDPOINT_URL_OPTION="--endpoint-url ${ENDPOINT_URL}" + fi +} + +parse_args "$@" + +if [ "${S3_BUCKET}" == "" ]; then + 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 + +mkdir -p config +../../provision.sh \ + --vehicle-name ${VEHICLE_NAME} \ + ${ENDPOINT_URL_OPTION} \ + --region ${REGION} \ + --certificate-pem-outfile config/certificate.pem \ + --private-key-outfile config/private-key.key \ + --endpoint-url-outfile config/endpoint.txt \ + --vehicle-name-outfile config/vehicle-name.txt \ + --thing-policy-outfile config/thing-policy.txt + +CERTIFICATE=`cat config/certificate.pem` +PRIVATE_KEY=`cat config/private-key.key` +MQTT_ENDPOINT_URL=`cat config/endpoint.txt` +echo {} | jq ".vehicle_name=\"${VEHICLE_NAME}\"" \ + | jq ".endpoint_url=\"${MQTT_ENDPOINT_URL}\"" \ + | jq ".certificate=\"${CERTIFICATE}\"" \ + | jq ".private_key=\"${PRIVATE_KEY}\"" \ + | 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 ${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 "Provisioning link:" +echo ${PROVISIONING_LINK} +QR_CODE_FILENAME="config/provisioning-qr-code.png" +segno --scale 5 --output ${QR_CODE_FILENAME} "${PROVISIONING_LINK}" + +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}" diff --git a/tools/android-app/cloud/setup-iotfleetwise.sh b/tools/android-app/cloud/setup-iotfleetwise.sh new file mode 100755 index 00000000..5a1c1757 --- /dev/null +++ b/tools/android-app/cloud/setup-iotfleetwise.sh @@ -0,0 +1,348 @@ +#!/bin/bash +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +ENDPOINT_URL="" +ENDPOINT_URL_OPTION="" +REGION="us-east-1" +TIMESTAMP_FILENAME="config/timestamp.txt" +TIMESTAMP=`date +%s` +VEHICLE_NAME_FILENAME="config/vehicle-name.txt" +VEHICLE_NAME="" +SERVICE_ROLE="IoTFleetWiseServiceRole-${TIMESTAMP}" +DEFAULT_TIMESTREAM_DB_NAME="IoTFleetWiseDB-${TIMESTAMP}" +SERVICE_PRINCIPAL="iotfleetwise.amazonaws.com" +TIMESTREAM_DB_NAME_FILENAME="config/timestream-db-name.txt" +TIMESTREAM_DB_NAME="" +TIMESTREAM_TABLE_NAME="VehicleDataTable" +SERVICE_ROLE_ARN_FILENAME="config/service-role-arn.txt" +SERVICE_ROLE_ARN="" +CAMPAIGN_FILE="campaign-android.json" + +parse_args() { + while [ "$#" -gt 0 ]; do + case $1 in + --vehicle-name) + VEHICLE_NAME=$2 + shift + ;; + --timestream-db-name) + TIMESTREAM_DB_NAME=$2 + shift + ;; + --timestream-table-name) + TIMESTREAM_TABLE_NAME=$2 + shift + ;; + --service-role-arn) + SERVICE_ROLE_ARN=$2 + shift + ;; + --service-principal) + SERVICE_PRINCIPAL=$2 + shift + ;; + --endpoint-url) + ENDPOINT_URL=$2 + shift + ;; + --region) + REGION=$2 + shift + ;; + --help) + echo "Usage: $0 [OPTION]" + echo " --vehicle-name Vehicle name, by default will be read from ${VEHICLE_NAME_FILENAME}" + echo " --timestream-db-name Timestream database name, by default will be read from ${TIMESTREAM_DB_NAME_FILENAME}" + echo " --timestream-table-name Timetream table name, default: ${TIMESTREAM_TABLE_NAME}" + echo " --service-role-arn Service role ARN for accessing Timestream, by default will be read from ${SERVICE_ROLE_ARN_FILENAME}" + echo " --endpoint-url The endpoint URL used for AWS CLI calls" + echo " --region The region used for AWS CLI calls, default: ${REGION}" + echo " --service-principal AWS service principal for policies, default: ${SERVICE_PRINCIPAL}" + exit 0 + ;; + esac + shift + done + + if [ "${ENDPOINT_URL}" != "" ]; then + ENDPOINT_URL_OPTION="--endpoint-url ${ENDPOINT_URL}" + fi + + if [ "${VEHICLE_NAME}" == "" ]; then + if [ -f "${VEHICLE_NAME_FILENAME}" ]; then + VEHICLE_NAME=`cat ${VEHICLE_NAME_FILENAME}` + else + echo "Error: provision.sh script not run" + exit -1 + fi + fi + + if [ "${TIMESTREAM_DB_NAME}" == "" ]; then + if [ -f "${TIMESTREAM_DB_NAME_FILENAME}" ]; then + TIMESTREAM_DB_NAME=`cat ${TIMESTREAM_DB_NAME_FILENAME}` + fi + fi + + if [ "${SERVICE_ROLE_ARN}" == "" ]; then + if [ -f "${SERVICE_ROLE_ARN_FILENAME}" ]; then + SERVICE_ROLE_ARN=`cat ${SERVICE_ROLE_ARN_FILENAME}` + fi + fi +} + +echo "================================" +echo "AWS IoT FleetWise Android Script" +echo "================================" + +mkdir -p config +parse_args "$@" + +echo "Getting AWS account ID..." +ACCOUNT_ID=`aws sts get-caller-identity --query "Account" --output text` +echo ${ACCOUNT_ID} + +if [ "${TIMESTREAM_DB_NAME}" != "" ] && [ "${SERVICE_ROLE_ARN}" != "" ]; then + TIMESTREAM_TABLE_ARN="arn:aws:timestream:${REGION}:${ACCOUNT_ID}:database/${TIMESTREAM_DB_NAME}/table/${TIMESTREAM_TABLE_NAME}" +else + TIMESTREAM_DB_NAME=${DEFAULT_TIMESTREAM_DB_NAME} + + echo "Creating Timestream database..." + aws timestream-write create-database \ + --region ${REGION} \ + --database-name ${TIMESTREAM_DB_NAME} | jq -r .Database.Arn + + echo "Creating Timestream table..." + TIMESTREAM_TABLE_ARN=$( aws timestream-write create-table \ + --region ${REGION} \ + --database-name ${TIMESTREAM_DB_NAME} \ + --table-name ${TIMESTREAM_TABLE_NAME} \ + --retention-properties "{\"MemoryStoreRetentionPeriodInHours\":2, \ + \"MagneticStoreRetentionPeriodInDays\":2}" | jq -r .Table.Arn ) + + echo "Creating service role..." + SERVICE_ROLE_TRUST_POLICY=$(cat << EOF +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "$SERVICE_PRINCIPAL" + ] + }, + "Action": "sts:AssumeRole" + } + ] +} +EOF +) + SERVICE_ROLE_ARN=`aws iam create-role \ + --role-name "${SERVICE_ROLE}" \ + --assume-role-policy-document "${SERVICE_ROLE_TRUST_POLICY}" | jq -r .Role.Arn` + echo ${SERVICE_ROLE_ARN} + + echo "Waiting for role to be created..." + aws iam wait role-exists \ + --role-name "${SERVICE_ROLE}" + + echo "Creating service role policy..." + SERVICE_ROLE_POLICY=$(cat <<'EOF' +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "timestreamIngestion", + "Effect": "Allow", + "Action": [ + "timestream:WriteRecords", + "timestream:Select" + ] + }, + { + "Sid": "timestreamDescribeEndpoint", + "Effect": "Allow", + "Action": [ + "timestream:DescribeEndpoints" + ], + "Resource": "*" + } + ] +} +EOF +) + SERVICE_ROLE_POLICY=`echo "${SERVICE_ROLE_POLICY}" \ + | jq ".Statement[0].Resource=\"arn:aws:timestream:${REGION}:${ACCOUNT_ID}:database/${TIMESTREAM_DB_NAME}/*\""` + SERVICE_ROLE_POLICY_ARN=`aws iam create-policy \ + --policy-name ${SERVICE_ROLE}-policy \ + --policy-document "${SERVICE_ROLE_POLICY}" | jq -r .Policy.Arn` + echo ${SERVICE_ROLE_POLICY_ARN} + + echo "Waiting for policy to be created..." + aws iam wait policy-exists \ + --policy-arn "${SERVICE_ROLE_POLICY_ARN}" + + echo "Attaching policy to service role..." + aws iam attach-role-policy \ + --policy-arn ${SERVICE_ROLE_POLICY_ARN} \ + --role-name "${SERVICE_ROLE}" + + echo ${TIMESTREAM_DB_NAME} > ${TIMESTREAM_DB_NAME_FILENAME} + echo ${SERVICE_ROLE_ARN} > ${SERVICE_ROLE_ARN_FILENAME} +fi + +NAME="${VEHICLE_NAME}-${TIMESTAMP}" +echo ${TIMESTAMP} > ${TIMESTAMP_FILENAME} + +echo "Deleting vehicle ${VEHICLE_NAME} if it already exists..." +aws iotfleetwise delete-vehicle \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --vehicle-name "${VEHICLE_NAME}" | jq -r .arn + +VEHICLE_NODE=`cat ../../cloud/vehicle-node.json` +OBD_NODES=`cat ../../cloud/obd-nodes.json` +DBC_NODES=`cat externalGpsNodes.json` + +echo "Checking for existing signal catalog..." +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 "Updating Vehicle node in signal catalog..." +if UPDATE_SIGNAL_CATALOG_STATUS=`aws iotfleetwise update-signal-catalog \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --name ${SIGNAL_CATALOG_NAME} \ + --description "Vehicle node" \ + --nodes-to-update "${VEHICLE_NODE}" 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 "Node exists and is in use, continuing" +fi + +echo "Updating OBD signals in signal catalog..." +if UPDATE_SIGNAL_CATALOG_STATUS=`aws iotfleetwise update-signal-catalog \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --name ${SIGNAL_CATALOG_NAME} \ + --description "OBD signals" \ + --nodes-to-update "${OBD_NODES}" 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 + +echo "Updating DBC signals in signal catalog..." +if UPDATE_SIGNAL_CATALOG_STATUS=`aws iotfleetwise update-signal-catalog \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --name ${SIGNAL_CATALOG_NAME} \ + --description "DBC signals" \ + --nodes-to-update "${DBC_NODES}" 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 + +echo "Creating model manifest..." +# Make a list of all node names: +NODE_LIST=`( echo ${DBC_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} \ + --name ${NAME}-model-manifest \ + --signal-catalog-arn ${SIGNAL_CATALOG_ARN} \ + --nodes "${NODE_LIST}" | jq -r .arn + +echo "Activating model manifest..." +MODEL_MANIFEST_ARN=`aws iotfleetwise update-model-manifest \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --name ${NAME}-model-manifest \ + --status ACTIVE | jq -r .arn` +echo ${MODEL_MANIFEST_ARN} + +echo "Creating decoder manifest with OBD signals..." +NETWORK_INTERFACES=`cat network-interfaces.json` +OBD_SIGNAL_DECODERS=`cat ../../cloud/obd-decoders.json` +DECODER_MANIFEST_ARN=`aws iotfleetwise create-decoder-manifest \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --name ${NAME}-decoder-manifest \ + --model-manifest-arn ${MODEL_MANIFEST_ARN} \ + --network-interfaces "${NETWORK_INTERFACES}" \ + --signal-decoders "${OBD_SIGNAL_DECODERS}" | jq -r .arn` +echo ${DECODER_MANIFEST_ARN} + +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 "Activating decoder manifest..." +aws iotfleetwise update-decoder-manifest \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --name ${NAME}-decoder-manifest \ + --status ACTIVE | jq -r .arn + +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} \ + --vehicle-name "${VEHICLE_NAME}" | jq -r .arn + +echo "Creating fleet..." +FLEET_ARN=`aws iotfleetwise create-fleet \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --fleet-id ${NAME}-fleet \ + --description "Description is required" \ + --signal-catalog-arn ${SIGNAL_CATALOG_ARN} | jq -r .arn` +echo ${FLEET_ARN} + +echo "Associating vehicle ${VEHICLE_NAME}..." +aws iotfleetwise associate-vehicle-fleet \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --fleet-id ${NAME}-fleet \ + --vehicle-name "${VEHICLE_NAME}" + +echo "Creating campaign from ${CAMPAIGN_FILE}..." +CAMPAIGN=`cat ${CAMPAIGN_FILE} \ + | jq .name=\"${NAME}-campaign\" \ + | jq .signalCatalogArn=\"${SIGNAL_CATALOG_ARN}\" \ + | jq .targetArn=\"${FLEET_ARN}\"` +aws iotfleetwise create-campaign \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --cli-input-json "${CAMPAIGN}" --data-destination-configs "[{\"timestreamConfig\":{\"timestreamTableArn\":\"${TIMESTREAM_TABLE_ARN}\",\"executionRoleArn\":\"${SERVICE_ROLE_ARN}\"}}]" | jq -r .arn + +echo "Waiting for campaign to become ready for approval..." +while true; do + sleep 5 + CAMPAIGN_STATUS=`aws iotfleetwise get-campaign \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --name ${NAME}-campaign | jq -r .status` + if [ "${CAMPAIGN_STATUS}" == "WAITING_FOR_APPROVAL" ]; then + break + fi +done + +echo "Approving campaign..." +aws iotfleetwise update-campaign \ + ${ENDPOINT_URL_OPTION} --region ${REGION} \ + --name ${NAME}-campaign \ + --action APPROVE | jq -r .arn + +echo "=========" +echo "Finished!" +echo "=========" diff --git a/tools/android-app/gradle.properties b/tools/android-app/gradle.properties new file mode 100644 index 00000000..a03b3548 --- /dev/null +++ b/tools/android-app/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true diff --git a/tools/android-app/gradle/wrapper/gradle-wrapper.jar b/tools/android-app/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..e708b1c0 Binary files /dev/null and b/tools/android-app/gradle/wrapper/gradle-wrapper.jar differ diff --git a/tools/android-app/gradle/wrapper/gradle-wrapper.properties b/tools/android-app/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..b5c1c9bc --- /dev/null +++ b/tools/android-app/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Apr 27 16:03:33 CEST 2023 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/tools/android-app/gradlew b/tools/android-app/gradlew new file mode 100755 index 00000000..4f906e0c --- /dev/null +++ b/tools/android-app/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/tools/android-app/gradlew.bat b/tools/android-app/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /dev/null +++ b/tools/android-app/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/tools/android-app/settings.gradle b/tools/android-app/settings.gradle new file mode 100644 index 00000000..d62ae53b --- /dev/null +++ b/tools/android-app/settings.gradle @@ -0,0 +1,16 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} +rootProject.name = "AWS IoT FleetWise Edge" +include ':app' diff --git a/tools/arm64.list b/tools/arm64.list deleted file mode 100644 index ad371fe2..00000000 --- a/tools/arm64.list +++ /dev/null @@ -1,7 +0,0 @@ -deb [arch=arm64] http://ports.ubuntu.com/ focal main restricted -deb [arch=arm64] http://ports.ubuntu.com/ focal-updates main restricted -deb [arch=arm64] http://ports.ubuntu.com/ focal universe -deb [arch=arm64] http://ports.ubuntu.com/ focal-updates universe -deb [arch=arm64] http://ports.ubuntu.com/ focal multiverse -deb [arch=arm64] http://ports.ubuntu.com/ focal-updates multiverse -deb [arch=arm64] http://ports.ubuntu.com/ focal-backports main restricted universe multiverse diff --git a/tools/armhf.list b/tools/armhf.list deleted file mode 100644 index b876ea34..00000000 --- a/tools/armhf.list +++ /dev/null @@ -1,7 +0,0 @@ -deb [arch=armhf] http://ports.ubuntu.com/ focal main restricted -deb [arch=armhf] http://ports.ubuntu.com/ focal-updates main restricted -deb [arch=armhf] http://ports.ubuntu.com/ focal universe -deb [arch=armhf] http://ports.ubuntu.com/ focal-updates universe -deb [arch=armhf] http://ports.ubuntu.com/ focal multiverse -deb [arch=armhf] http://ports.ubuntu.com/ focal-updates multiverse -deb [arch=armhf] http://ports.ubuntu.com/ focal-backports main restricted universe multiverse diff --git a/tools/build-dist.sh b/tools/build-dist.sh index 74a09a47..bbc8dead 100755 --- a/tools/build-dist.sh +++ b/tools/build-dist.sh @@ -5,19 +5,26 @@ set -euo pipefail if (($#<1)); then - echo "Error: Output binary not provided" >&2 + echo "Error: Output files not provided" >&2 exit -1 fi -OUTPUT_BINARY=`realpath $1` rm -rf build/dist mkdir build/dist cd build/dist -cp -r ${OUTPUT_BINARY} \ - ../../LICENSE \ + +for FILE in $@; do + SRC=`echo $FILE | cut -d ':' -f1` + DEST=`echo $FILE | cut -d ':' -f2` + mkdir -p $DEST + cp ../../$SRC $DEST +done + +cp -r ../../LICENSE \ ../../THIRD-PARTY-LICENSES \ ../../configuration \ ../../tools \ . +rm -rf tools/android-app tar -zcf ../aws-iot-fleetwise-edge.tar.gz * diff --git a/tools/build-fwe-cross-android.sh b/tools/build-fwe-cross-android.sh new file mode 100755 index 00000000..7b2b403f --- /dev/null +++ b/tools/build-fwe-cross-android.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +SCRIPT_DIR=$(dirname $(realpath "$0")) +source ${SCRIPT_DIR}/install-deps-versions.sh + +NATIVE_PREFIX="/usr/local/`gcc -dumpmachine`" + +parse_args() { + while [ "$#" -gt 0 ]; do + case $1 in + --native-prefix) + NATIVE_PREFIX="$2" + shift + ;; + --help) + echo "Usage: $0 [OPTION]" + echo " --native-prefix Native install prefix, default ${NATIVE_PREFIX}" + exit 0 + ;; + esac + shift + done +} + +parse_args "$@" + +export PATH=/usr/local/android_sdk/cmake/${VERSION_CMAKE}/bin:${NATIVE_PREFIX}/bin:${PATH} +mkdir -p build && cd build + +build() { + TARGET_ARCH="$1" + HOST_PLATFORM="$2" + + mkdir ${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_BUILD_EXECUTABLE=Off \ + -DFWE_BUILD_ANDROID_SHARED_LIBRARY=On \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_TESTING=Off \ + -DANDROID_ABI=${TARGET_ARCH} \ + -DANDROID_PLATFORM=android-${VERSION_ANDROID_API} \ + -DCMAKE_ANDROID_NDK=/usr/local/android_sdk/ndk/${VERSION_ANDROID_NDK} \ + -DCMAKE_TOOLCHAIN_FILE=/usr/local/android_sdk/ndk/${VERSION_ANDROID_NDK}/build/cmake/android.toolchain.cmake \ + -DANDROID_TOOLCHAIN=clang \ + -DCMAKE_FIND_ROOT_PATH=/usr/local/${HOST_PLATFORM} \ + ../.. + make -j`nproc` + cd .. +} + +build "armeabi-v7a" "armv7a-linux-androideabi" +build "arm64-v8a" "aarch64-linux-android" diff --git a/tools/code_check/compile_db_remove_test.py b/tools/code_check/compile_db_remove_test.py index 54503c41..0543e9b4 100755 --- a/tools/code_check/compile_db_remove_test.py +++ b/tools/code_check/compile_db_remove_test.py @@ -21,6 +21,9 @@ if "command" in block: if re.search(r".+iotcpp\/test\/include.+", block["command"]): continue + # If we keep -Werror, we will get compiler errors from clang that don't happen with GCC. + # Since clang is currently not one of our supported compilers, we will ignore the warnings. + block["command"] = block["command"].replace("-Werror", "") output_compile_commands.append(block) # re-write the db file for clang-tidy diff --git a/tools/container/Dockerfile b/tools/container/Dockerfile index 907f0d33..fe8404a5 100644 --- a/tools/container/Dockerfile +++ b/tools/container/Dockerfile @@ -11,7 +11,7 @@ RUN apt-get update \ ca-certificates \ iproute2 \ jq \ - && apt-get clean + && rm -rf /var/lib/apt/lists/* COPY ${TARGETPLATFORM}/src/executionmanagement/aws-iot-fleetwise-edge /usr/bin/ COPY tools/container/start-fwe.sh /usr/bin diff --git a/tools/install-deps-cross-android.sh b/tools/install-deps-cross-android.sh new file mode 100755 index 00000000..eb84b281 --- /dev/null +++ b/tools/install-deps-cross-android.sh @@ -0,0 +1,229 @@ +#!/bin/bash +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +SCRIPT_DIR=$(dirname $(realpath "$0")) +source ${SCRIPT_DIR}/install-deps-versions.sh + +USE_CACHE="true" +SDK_PREFIX="/usr/local/android_sdk" + +parse_args() { + while [ "$#" -gt 0 ]; do + case $1 in + --native-prefix) + NATIVE_PREFIX="$2" + USE_CACHE="false" + shift + ;; + --help) + echo "Usage: $0 [OPTION]" + echo " --native-prefix Native install prefix" + exit 0 + ;; + esac + shift + done +} + +parse_args "$@" + +apt update +apt install -y \ + unzip \ + git \ + wget \ + curl \ + default-jre \ + build-essential \ + file + +if [ ! -d ${SDK_PREFIX} ]; then + curl -o cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-${VERSION_ANDROID_CMDLINE_TOOLS}_latest.zip + unzip cmdline-tools.zip + rm cmdline-tools.zip + mkdir -p ${SDK_PREFIX}/cmdline-tools + mv cmdline-tools ${SDK_PREFIX}/cmdline-tools/latest + yes | ${SDK_PREFIX}/cmdline-tools/latest/bin/sdkmanager --licenses || true + ${SDK_PREFIX}/cmdline-tools/latest/bin/sdkmanager --install \ + "ndk;${VERSION_ANDROID_NDK}" \ + "cmake;${VERSION_CMAKE}" \ + "platforms;android-${VERSION_ANDROID_SDK}" \ + "platform-tools" \ + "emulator" \ + "build-tools;${VERSION_ANDROID_BUILD_TOOLS}" +fi +export PATH=${SDK_PREFIX}/cmake/${VERSION_CMAKE}/bin/:${PATH} + +if [ -z "${NATIVE_PREFIX+x}" ]; then + NATIVE_PREFIX="/usr/local/`gcc -dumpmachine`" +fi + +install_deps() { + TARGET_ARCH="$1" + HOST_PLATFORM="$2" + SSL_TARGET="$3" + INSTALL_PREFIX="/usr/local/${HOST_PLATFORM}" + + mkdir -p ${INSTALL_PREFIX} + mkdir -p ${NATIVE_PREFIX} + mkdir deps-cross-android && cd deps-cross-android + + # Boost + git clone https://github.com/moritz-wundke/Boost-for-Android.git + cd Boost-for-Android + git checkout 53e6c16ea80c7dcb2683fd548e0c7a09ddffbfc1 + ./build-android.sh \ + ${SDK_PREFIX}/ndk/${VERSION_ANDROID_NDK} \ + --boost=${VERSION_BOOST} \ + --with-libraries=system,thread,filesystem,chrono,date_time,atomic \ + --layout=system \ + --arch=${TARGET_ARCH} \ + --target-version=${VERSION_ANDROID_API} > /dev/null + mv build/out/${TARGET_ARCH}/lib ${INSTALL_PREFIX} + mv build/out/${TARGET_ARCH}/include ${INSTALL_PREFIX} + cd .. + + # Snappy + git clone -b ${VERSION_SNAPPY} https://github.com/google/snappy.git + cd snappy + mkdir build && cd build + cmake \ + -DSNAPPY_BUILD_TESTS=OFF \ + -DBUILD_SHARED_LIBS=OFF \ + -DSNAPPY_BUILD_BENCHMARKS=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DANDROID_ABI=${TARGET_ARCH} \ + -DANDROID_PLATFORM=android-${VERSION_ANDROID_API} \ + -DCMAKE_ANDROID_NDK=${SDK_PREFIX}/ndk/${VERSION_ANDROID_NDK} \ + -DCMAKE_TOOLCHAIN_FILE=${SDK_PREFIX}/ndk/${VERSION_ANDROID_NDK}/build/cmake/android.toolchain.cmake \ + -DANDROID_TOOLCHAIN=clang \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} \ + .. + make install -j`nproc` + cd ../.. + + # Protobuf + wget -q https://github.com/protocolbuffers/protobuf/releases/download/${VERSION_PROTOBUF_RELEASE}/protobuf-cpp-${VERSION_PROTOBUF}.tar.gz + tar -zxf protobuf-cpp-${VERSION_PROTOBUF}.tar.gz + cd protobuf-${VERSION_PROTOBUF} + if [ ! -f ${NATIVE_PREFIX}/bin/protoc ]; then + mkdir build_native && cd build_native + ../configure --prefix=${NATIVE_PREFIX} + make install -j`nproc` + cd .. + fi + mkdir build_target && cd build_target + NDK=${SDK_PREFIX}/ndk/${VERSION_ANDROID_NDK} \ + TOOLCHAIN=${NDK}/toolchains/llvm/prebuilt/linux-x86_64 \ + TARGET=${HOST_PLATFORM} \ + API=${VERSION_ANDROID_API} \ + AR=${TOOLCHAIN}/bin/llvm-ar \ + CC=${TOOLCHAIN}/bin/${TARGET}${API}-clang \ + AS=${CC} \ + CXX=${TOOLCHAIN}/bin/${TARGET}${API}-clang++ \ + LD=${TOOLCHAIN}/bin/ld \ + RANLIB=${TOOLCHAIN}/bin/llvm-ranlib \ + STRIP=${TOOLCHAIN}/bin/llvm-strip \ + ../configure \ + --host=${HOST_PLATFORM} \ + --prefix=${INSTALL_PREFIX} \ + "CFLAGS=-fPIC" "CXXFLAGS=-fPIC" + make install -j`nproc` + cd ../.. + + # JsonCpp + git clone -b ${VERSION_JSON_CPP} https://github.com/open-source-parsers/jsoncpp.git + cd jsoncpp + mkdir build && cd build + cmake \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_POSITION_INDEPENDENT_CODE=On \ + -DJSONCPP_WITH_TESTS=Off \ + -DJSONCPP_WITH_POST_BUILD_UNITTEST=Off \ + -DANDROID_ABI=${TARGET_ARCH} \ + -DANDROID_PLATFORM=android-${VERSION_ANDROID_API} \ + -DCMAKE_ANDROID_NDK=${SDK_PREFIX}/ndk/${VERSION_ANDROID_NDK} \ + -DCMAKE_TOOLCHAIN_FILE=${SDK_PREFIX}/ndk/${VERSION_ANDROID_NDK}/build/cmake/android.toolchain.cmake \ + -DANDROID_TOOLCHAIN=clang \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} \ + .. + make install -j`nproc` + cd ../.. + + # OpenSSL + wget -q https://www.openssl.org/source/openssl-${VERSION_OPENSSL}.tar.gz + tar -zxf openssl-${VERSION_OPENSSL}.tar.gz + cd openssl-${VERSION_OPENSSL} + ANDROID_NDK_HOME=${SDK_PREFIX}/ndk/${VERSION_ANDROID_NDK} \ + PATH=${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin:${PATH} \ + INSTALL_PREFIX=${INSTALL_PREFIX} SSL_TARGET=${SSL_TARGET} VERSION_ANDROID_API=${VERSION_ANDROID_API} \ + bash -c './Configure ${SSL_TARGET} -D__ANDROID_API__=${VERSION_ANDROID_API} --prefix=${INSTALL_PREFIX} no-shared \ + && make -j`nproc`' + make install > /dev/null + cd .. + + # curl + wget -q https://github.com/curl/curl/releases/download/${VERSION_CURL_RELEASE}/curl-${VERSION_CURL}.tar.gz + tar -zxf curl-${VERSION_CURL}.tar.gz + cd curl-${VERSION_CURL} + NDK=${SDK_PREFIX}/ndk/${VERSION_ANDROID_NDK} \ + TOOLCHAIN=${NDK}/toolchains/llvm/prebuilt/linux-x86_64 \ + TARGET=${HOST_PLATFORM} \ + API=${VERSION_ANDROID_API} \ + AR=${TOOLCHAIN}/bin/llvm-ar \ + CC=${TOOLCHAIN}/bin/${TARGET}${API}-clang \ + AS=${CC} \ + CXX=${TOOLCHAIN}/bin/${TARGET}${API}-clang++ \ + LD=${TOOLCHAIN}/bin/ld \ + RANLIB=${TOOLCHAIN}/bin/llvm-ranlib \ + STRIP=${TOOLCHAIN}/bin/llvm-strip \ + LDFLAGS="-static" \ + PKG_CONFIG="pkg-config --static" \ + ./configure \ + --disable-shared \ + --enable-static \ + --disable-ldap \ + --enable-ipv6 \ + --with-ssl=${INSTALL_PREFIX} \ + --disable-unix-sockets \ + --disable-rtsp \ + --host=${HOST_PLATFORM} \ + --prefix=${INSTALL_PREFIX} + make install -j`nproc` V=1 LDFLAGS="-static -L${INSTALL_PREFIX}/lib" + cd .. + + # AWS C++ SDK + git clone -b ${VERSION_AWS_SDK_CPP} --recursive https://github.com/aws/aws-sdk-cpp.git + cd aws-sdk-cpp + mkdir build && cd build + CFLAGS=-I${INSTALL_PREFIX}/include cmake \ + -DENABLE_TESTING=OFF \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_ONLY='s3-crt;iot' \ + -DAWS_CUSTOM_MEMORY_MANAGEMENT=ON \ + -DANDROID_ABI=${TARGET_ARCH} \ + -DANDROID_PLATFORM=android-${VERSION_ANDROID_API} \ + -DCMAKE_ANDROID_NDK=${SDK_PREFIX}/ndk/${VERSION_ANDROID_NDK} \ + -DCMAKE_TOOLCHAIN_FILE=${SDK_PREFIX}/ndk/${VERSION_ANDROID_NDK}/build/cmake/android.toolchain.cmake \ + -DANDROID_TOOLCHAIN=clang \ + -DCURL_LIBRARY=${INSTALL_PREFIX}/lib/libcurl.a \ + -DCMAKE_FIND_ROOT_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} \ + .. + make install -j`nproc` + cd ../.. + # pthread is directly linked somewhere, so just create a dummy .a file + ar -rc ${INSTALL_PREFIX}/lib/libpthread.a + + cd .. + 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" +fi diff --git a/tools/install-deps-cross-arm64.sh b/tools/install-deps-cross-arm64.sh index cea1fb93..f47f9a80 100755 --- a/tools/install-deps-cross-arm64.sh +++ b/tools/install-deps-cross-arm64.sh @@ -40,9 +40,11 @@ if [ "${ARCH}" == "arm64" ]; then exit -1 fi -cp ${SCRIPT_DIR}/arm64.list /etc/apt/sources.list.d/ -dpkg --add-architecture arm64 sed -i "s/deb http/deb [arch=${ARCH}] http/g" /etc/apt/sources.list +cp /etc/apt/sources.list /etc/apt/sources.list.d/arm64.list +sed -i "s/deb \[arch=${ARCH}\] http/deb [arch=arm64] http/g" /etc/apt/sources.list.d/arm64.list +sed -i "s#archive.ubuntu.com/ubuntu#ports.ubuntu.com/ubuntu-ports#g" /etc/apt/sources.list.d/arm64.list +dpkg --add-architecture arm64 apt update apt install -y \ build-essential \ diff --git a/tools/install-deps-cross-armhf.sh b/tools/install-deps-cross-armhf.sh index 0ae36ba3..850e56a7 100755 --- a/tools/install-deps-cross-armhf.sh +++ b/tools/install-deps-cross-armhf.sh @@ -40,9 +40,11 @@ if [ "${ARCH}" == "armhf" ]; then exit -1 fi -cp ${SCRIPT_DIR}/armhf.list /etc/apt/sources.list.d/ -dpkg --add-architecture armhf sed -i "s/deb http/deb [arch=${ARCH}] http/g" /etc/apt/sources.list +cp /etc/apt/sources.list /etc/apt/sources.list.d/armhf.list +sed -i "s/deb \[arch=${ARCH}\] http/deb [arch=armhf] http/g" /etc/apt/sources.list.d/armhf.list +sed -i "s#archive.ubuntu.com/ubuntu#ports.ubuntu.com/ubuntu-ports#g" /etc/apt/sources.list.d/armhf.list +dpkg --add-architecture armhf apt update apt install -y \ build-essential \ diff --git a/tools/install-deps-versions.sh b/tools/install-deps-versions.sh index e3887dcc..853f7ce1 100755 --- a/tools/install-deps-versions.sh +++ b/tools/install-deps-versions.sh @@ -1,10 +1,10 @@ #!/bin/bash export VERSION_JSON_CPP="1.9.5" -export VERSION_PROTOBUF="3.21.7" -export VERSION_PROTOBUF_RELEASE="v21.7" +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.46" +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" @@ -12,3 +12,12 @@ export VERSION_FAST_DDS="v2.3.4" export VERSION_FAST_DDS_GEN="v2.0.1" export VERSION_GOOGLE_TEST="release-1.10.0" export VERSION_GOOGLE_BENCHMARK="v1.6.1" +export VERSION_ANDROID_CMDLINE_TOOLS="9123335" +export VERSION_ANDROID_NDK="23.1.7779620" +export VERSION_ANDROID_API="21" +export VERSION_ANDROID_SDK="33" +export VERSION_ANDROID_BUILD_TOOLS="30.0.3" +export VERSION_CMAKE="3.22.1" # Provided by Ubuntu 20.04, version here used for Android build +export VERSION_SNAPPY="1.1.8" # Provided by Ubuntu 20.04, version here used for Android build +export VERSION_OPENSSL="1.1.1s" # Provided by Ubuntu 20.04, version here used for Android build +export VERSION_BOOST="1.78.0" # Provided by Ubuntu 20.04, version here used for Android build diff --git a/tools/provision.sh b/tools/provision.sh index a1c9ed9f..034f5598 100755 --- a/tools/provision.sh +++ b/tools/provision.sh @@ -14,6 +14,7 @@ CERT_OUT_FILE="certificate.pem" PRIVATE_KEY_OUT_FILE="private-key.key" ENDPOINT_URL_OUT_FILE="" VEHICLE_NAME_OUT_FILE="" +THING_POLICY_OUT_FILE="" ONLY_CLEAN_UP=false parse_args() { @@ -39,6 +40,10 @@ parse_args() { VEHICLE_NAME_OUT_FILE=$2 shift ;; + --thing-policy-outfile) + THING_POLICY_OUT_FILE=$2 + shift + ;; --endpoint-url) ENDPOINT_URL=$2 shift @@ -57,6 +62,7 @@ parse_args() { 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" @@ -203,3 +209,7 @@ fi if [ "${VEHICLE_NAME_OUT_FILE}" != "" ]; then echo -n $VEHICLE_NAME > ${VEHICLE_NAME_OUT_FILE} fi + +if [ "${THING_POLICY_OUT_FILE}" != "" ]; then + echo -n ${NAME}-IoT-Policy > ${THING_POLICY_OUT_FILE} +fi