diff --git a/.dockerignore b/.dockerignore index 21936581d..ce29af173 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,6 +6,5 @@ !discovery-handler-modules !discovery-handlers !discovery-utils -!samples !shared -!webhooks \ No newline at end of file +!webhooks diff --git a/.github/workflows/build-onvif-video-broker-container.yml b/.github/workflows/build-onvif-video-broker-container.yml deleted file mode 100644 index 7306b2c70..000000000 --- a/.github/workflows/build-onvif-video-broker-container.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Build ONVIF Broker (.NET) - -on: - push: - branches: [ main ] - paths: - - .github/workflows/build-onvif-video-broker-container.yml - - build/containers/Dockerfile.onvif-video-broker - - samples/brokers/onvif-video-broker/** - - version.txt - - build/samples.mk - - Makefile - pull_request: - branches: [ main ] - paths: - - .github/workflows/build-onvif-video-broker-container.yml - - build/containers/Dockerfile.onvif-video-broker - - samples/brokers/onvif-video-broker/** - - version.txt - - build/samples.mk - - Makefile - release: - types: - - published - -env: - AKRI_COMPONENT: onvif-video-broker - MAKEFILE_COMPONENT: onvif - -jobs: - build-broker: - runs-on: ubuntu-latest - timeout-minutes: 20 - - steps: - - name: Checkout the head commit of the branch - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: Get version.txt - id: version-string - run: | - echo "version=$(cat version.txt)" >> $GITHUB_OUTPUT - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GitHub Container Registry - if: github.event_name == 'push' || github.event_name == 'release' - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ secrets.crUsername }} - password: ${{ secrets.crPassword }} - - - name: Build and push - run: | - make onvif-video-broker PREFIX=ghcr.io/project-akri/akri $(${{ github.event_name != 'pull_request' }} && echo "PUSH=1") - diff --git a/.github/workflows/build-opcua-monitoring-broker-container.yml b/.github/workflows/build-opcua-monitoring-broker-container.yml deleted file mode 100644 index 7a74bd4f1..000000000 --- a/.github/workflows/build-opcua-monitoring-broker-container.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: Build OPC UA Monitoring Broker - -on: - push: - branches: [ main ] - paths: - - .github/workflows/build-opcua-monitoring-broker-container.yml - - build/containers/Dockerfile.opcua-monitoring-broker - - samples/brokers/opcua-monitoring-broker/** - - version.txt - pull_request: - branches: [ main ] - paths: - - .github/workflows/build-opcua-monitoring-broker-container.yml - - build/containers/Dockerfile.opcua-monitoring-broker - - samples/brokers/opcua-monitoring-broker/** - - version.txt - release: - types: - - published - -jobs: - - build-broker: - runs-on: ubuntu-latest - timeout-minutes: 20 - - steps: - - name: Checkout the head commit of the branch - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: Get version.txt - id: version-string - run: | - echo "version=$(cat version.txt)" >> $GITHUB_OUTPUT - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GitHub Container Registry - if: github.event_name == 'push' || github.event_name == 'release' - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ secrets.crUsername }} - password: ${{ secrets.crPassword }} - - - name: Docker meta - uses: docker/metadata-action@v5 - id: meta - with: - images: ghcr.io/project-akri/akri/opcua-monitoring-broker - labels: | - org.opencontainers.image.title=akri-opcua-monitoring-broker - org.opencontainers.image.description="Akri sample OPC UA monitoring broker" - tags: | - type=ref,event=pr - type=semver,pattern=v{{version}} - type=semver,pattern=v{{major}}.{{minor}} - type=raw,value=v${{steps.version-string.outputs.version}}-dev,enable=${{github.event_name != 'release'}} - - - name: Build and push - uses: docker/build-push-action@v5 - with: - context: . - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - file: build/containers/Dockerfile.opcua-monitoring-broker - platforms: linux/amd64,linux/arm64,linux/arm/v7 diff --git a/.github/workflows/build-python-app-containers.yml b/.github/workflows/build-python-app-containers.yml deleted file mode 100644 index 66ef1079a..000000000 --- a/.github/workflows/build-python-app-containers.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: Build Sample Apps Containers - -on: - push: - branches: [ main ] - paths: - - .github/workflows/build-python-app-containers.yml - - build/containers/Dockerfile.python-app - - samples/apps/** - - version.txt - pull_request: - branches: [ main ] - paths: - - .github/workflows/build-python-app-containers.yml - - build/containers/Dockerfile.python-app - - samples/apps/** - - version.txt - release: - types: - - published - -jobs: - - build-apps: - runs-on: ubuntu-latest - timeout-minutes: 20 - strategy: - matrix: - component: - - anomaly-detection-app - - video-streaming-app - - steps: - - name: Checkout the head commit of the branch - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: Get version.txt - id: version-string - run: | - echo "version=$(cat version.txt)" >> $GITHUB_OUTPUT - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GitHub Container Registry - if: github.event_name == 'push' || github.event_name == 'release' - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ secrets.crUsername }} - password: ${{ secrets.crPassword }} - - - name: Docker meta - uses: docker/metadata-action@v5 - id: meta - with: - images: ghcr.io/project-akri/akri/${{ matrix.component }} - labels: | - org.opencontainers.image.title=akri-${{matrix.component}} - org.opencontainers.image.description=Akri Sample App - tags: | - type=ref,event=pr - type=semver,pattern=v{{version}} - type=semver,pattern=v{{major}}.{{minor}} - type=raw,value=v${{steps.version-string.outputs.version}}-dev,enable=${{github.event_name != 'release'}} - - - name: Build and push - uses: docker/build-push-action@v5 - with: - context: . - push: ${{ github.event_name != 'pull_request' }} - build-args: | - APPLICATION=${{matrix.component}} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - file: build/containers/Dockerfile.python-app - # Don't build python sample apps for arm/v7 as many modules don't provide armv7 wheels. - platforms: linux/amd64,linux/arm64 \ No newline at end of file diff --git a/.github/workflows/build-rust-containers.yml b/.github/workflows/build-rust-containers.yml index 0d9d49351..6925d2c09 100644 --- a/.github/workflows/build-rust-containers.yml +++ b/.github/workflows/build-rust-containers.yml @@ -114,7 +114,6 @@ jobs: - label: udev-discovery-handler - label: opcua-discovery-handler - label: onvif-discovery-handler - - label: udev-video-broker steps: - name: Checkout the head commit of the branch diff --git a/Cargo.lock b/Cargo.lock index 472c45669..5c415fa9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,7 +50,7 @@ dependencies = [ "mime", "percent-encoding 2.3.2", "pin-project-lite", - "rand 0.9.2", + "rand 0.9.4", "sha1 0.10.6", "smallvec", "tokio", @@ -215,7 +215,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "agent" -version = "0.13.21" +version = "0.13.22" dependencies = [ "akri-debug-echo", "akri-discovery-utils", @@ -239,7 +239,7 @@ dependencies = [ "mock_instant", "mockall", "mockall_double", - "prometheus 0.13.4", + "prometheus", "prost", "serde", "serde_derive", @@ -279,7 +279,7 @@ dependencies = [ [[package]] name = "akri-debug-echo" -version = "0.13.21" +version = "0.13.22" dependencies = [ "akri-discovery-utils", "akri-shared", @@ -297,7 +297,7 @@ dependencies = [ [[package]] name = "akri-discovery-utils" -version = "0.13.21" +version = "0.13.22" dependencies = [ "akri-shared", "anyhow", @@ -319,7 +319,7 @@ dependencies = [ [[package]] name = "akri-onvif" -version = "0.13.21" +version = "0.13.22" dependencies = [ "akri-discovery-utils", "anyhow", @@ -348,7 +348,7 @@ dependencies = [ [[package]] name = "akri-opcua" -version = "0.13.21" +version = "0.13.22" dependencies = [ "akri-discovery-utils", "anyhow", @@ -368,7 +368,7 @@ dependencies = [ [[package]] name = "akri-shared" -version = "0.13.21" +version = "0.13.22" dependencies = [ "anyhow", "async-trait", @@ -378,7 +378,7 @@ dependencies = [ "kube", "log", "mockall", - "prometheus 0.13.4", + "prometheus", "rand 0.8.5", "schemars", "serde", @@ -393,7 +393,7 @@ dependencies = [ [[package]] name = "akri-udev" -version = "0.13.21" +version = "0.13.22" dependencies = [ "akri-discovery-utils", "anyhow", @@ -912,7 +912,7 @@ dependencies = [ [[package]] name = "controller" -version = "0.13.21" +version = "0.13.22" dependencies = [ "akri-shared", "anyhow", @@ -925,7 +925,7 @@ dependencies = [ "lazy_static", "log", "mockall", - "prometheus 0.13.4", + "prometheus", "serde_json", "tokio", ] @@ -1063,7 +1063,7 @@ checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "debug-echo-discovery-handler" -version = "0.13.21" +version = "0.13.22" dependencies = [ "akri-debug-echo", "akri-discovery-utils", @@ -1694,17 +1694,16 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "c2b52f86d1d4bc0d6b4e6826d960b1b333217e07d36b882dca570a5e1c48895b" dependencies = [ "http 1.4.0", "hyper 1.9.0", "hyper-util", "log", - "rustls 0.23.37", + "rustls 0.23.38", "rustls-native-certs", - "rustls-pki-types", "tokio", "tokio-rustls 0.26.4", "tower-service", @@ -2091,7 +2090,7 @@ dependencies = [ "k8s-openapi", "kube-core", "pem", - "rustls 0.23.37", + "rustls 0.23.38", "rustls-pemfile 2.2.0", "secrecy", "serde", @@ -2152,7 +2151,7 @@ dependencies = [ "json-patch", "k8s-openapi", "kube-client", - "parking_lot 0.12.5", + "parking_lot", "pin-project", "serde", "serde_json", @@ -2192,9 +2191,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.184" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libudev-sys" @@ -2416,7 +2415,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "onvif-discovery-handler" -version = "0.13.21" +version = "0.13.22" dependencies = [ "akri-discovery-utils", "akri-onvif", @@ -2451,7 +2450,7 @@ dependencies = [ "log", "openssl", "openssl-sys", - "parking_lot 0.12.5", + "parking_lot", "regex", "serde", "serde_derive", @@ -2465,7 +2464,7 @@ dependencies = [ [[package]] name = "opcua-discovery-handler" -version = "0.13.21" +version = "0.13.22" dependencies = [ "akri-discovery-utils", "akri-opcua", @@ -2488,9 +2487,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.76" +version = "0.10.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +checksum = "bfe4646e360ec77dff7dde40ed3d6c5fee52d156ef4a62f53973d38294dad87f" dependencies = [ "bitflags 2.11.0", "cfg-if", @@ -2520,9 +2519,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.112" +version = "0.9.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +checksum = "ad2f2c0eba47118757e4c6d2bff2838f3e0523380021356e7875e858372ce644" dependencies = [ "cc", "libc", @@ -2545,17 +2544,6 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - [[package]] name = "parking_lot" version = "0.12.5" @@ -2563,21 +2551,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", - "parking_lot_core 0.9.12", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", + "parking_lot_core", ] [[package]] @@ -2588,7 +2562,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] @@ -2719,9 +2693,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "polling" @@ -2821,20 +2795,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "procfs" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8809e0c18450a2db0f236d2a44ec0b4c1412d0eb936233579f0990faa5d5cd" -dependencies = [ - "bitflags 1.3.2", - "byteorder", - "flate2", - "hex", - "lazy_static", - "libc", -] - [[package]] name = "procfs" version = "0.16.0" @@ -2858,23 +2818,6 @@ dependencies = [ "hex", ] -[[package]] -name = "prometheus" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5986aa8d62380092d2f50f8b1cdba9cb9b6731ffd4b25b51fd126b6c3e05b99c" -dependencies = [ - "cfg-if", - "fnv", - "lazy_static", - "libc", - "memchr", - "parking_lot 0.11.2", - "procfs 0.9.1", - "protobuf", - "thiserror", -] - [[package]] name = "prometheus" version = "0.13.4" @@ -2886,8 +2829,8 @@ dependencies = [ "lazy_static", "libc", "memchr", - "parking_lot 0.12.5", - "procfs 0.16.0", + "parking_lot", + "procfs", "protobuf", "thiserror", ] @@ -2991,9 +2934,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -3037,15 +2980,6 @@ dependencies = [ "getrandom 0.3.4", ] -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.5.18" @@ -3141,15 +3075,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rscam" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89056084211cd54924fedf2e2199b906409d1f795cfd8e7e3271061742457018" -dependencies = [ - "libc", -] - [[package]] name = "rustc_version" version = "0.4.1" @@ -3199,9 +3124,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ "log", "once_cell", @@ -3773,7 +3698,7 @@ dependencies = [ "bytes", "libc", "mio", - "parking_lot 0.12.5", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2 0.6.3", @@ -3829,7 +3754,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.37", + "rustls 0.23.38", "tokio", ] @@ -4052,7 +3977,7 @@ dependencies = [ [[package]] name = "udev-discovery-handler" -version = "0.13.21" +version = "0.13.22" dependencies = [ "akri-discovery-utils", "akri-udev", @@ -4061,23 +3986,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "udev-video-broker" -version = "0.13.21" -dependencies = [ - "akri-shared", - "env_logger", - "lazy_static", - "log", - "prometheus 0.12.0", - "prost", - "regex", - "rscam", - "tokio", - "tonic", - "tonic-build", -] - [[package]] name = "unicase" version = "2.9.0" @@ -4371,7 +4279,7 @@ dependencies = [ [[package]] name = "webhook-configuration" -version = "0.13.21" +version = "0.13.22" dependencies = [ "actix-rt", "actix-web", @@ -4385,28 +4293,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-core" version = "0.62.2" diff --git a/Cargo.toml b/Cargo.toml index b8718f475..ced721756 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,6 @@ members = [ "shared", "agent", "controller", - "samples/brokers/udev-video-broker", "webhooks/validating/configuration", "discovery-utils", "discovery-handlers/debug-echo", @@ -23,7 +22,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.13.21" +version = "0.13.22" edition = "2024" license = "Apache-2.0" homepage = "https://docs.akri.sh/" diff --git a/Makefile b/Makefile index a856d5013..f5406d353 100644 --- a/Makefile +++ b/Makefile @@ -57,10 +57,9 @@ include build/intermediate-containers.mk # Akri container defines include build/akri-containers.mk -include build/samples.mk .PHONY: all -all: akri samples opencv-base +all: akri opencv-base .PHONY: push push: PUSH = 1 @@ -68,4 +67,4 @@ push: all .PHONY: load load: LOAD = 1 -load: all \ No newline at end of file +load: all diff --git a/akri.sln b/akri.sln deleted file mode 100644 index fcf810308..000000000 --- a/akri.sln +++ /dev/null @@ -1,39 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.2.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5D20AA90-6969-D8BD-9DCD-8634F4692FDA}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "brokers", "brokers", "{DF34A9C8-87A9-9338-5075-D37782952607}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "onvif-video-broker", "samples\brokers\onvif-video-broker\onvif-video-broker.csproj", "{4904093D-5FA2-6F06-0FCE-F3647411CD0E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "opcua-monitoring-broker", "samples\brokers\opcua-monitoring-broker\opcua-monitoring-broker.csproj", "{1E5A3E34-E860-DD44-E465-DC138A0529C9}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {4904093D-5FA2-6F06-0FCE-F3647411CD0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4904093D-5FA2-6F06-0FCE-F3647411CD0E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4904093D-5FA2-6F06-0FCE-F3647411CD0E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4904093D-5FA2-6F06-0FCE-F3647411CD0E}.Release|Any CPU.Build.0 = Release|Any CPU - {1E5A3E34-E860-DD44-E465-DC138A0529C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1E5A3E34-E860-DD44-E465-DC138A0529C9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1E5A3E34-E860-DD44-E465-DC138A0529C9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1E5A3E34-E860-DD44-E465-DC138A0529C9}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {DF34A9C8-87A9-9338-5075-D37782952607} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA} - {4904093D-5FA2-6F06-0FCE-F3647411CD0E} = {DF34A9C8-87A9-9338-5075-D37782952607} - {1E5A3E34-E860-DD44-E465-DC138A0529C9} = {DF34A9C8-87A9-9338-5075-D37782952607} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {25B40193-6807-4C63-A79D-001205059134} - EndGlobalSection -EndGlobal diff --git a/build/containers/Dockerfile.onvif-video-broker b/build/containers/Dockerfile.onvif-video-broker deleted file mode 100644 index a14502942..000000000 --- a/build/containers/Dockerfile.onvif-video-broker +++ /dev/null @@ -1,33 +0,0 @@ -ARG BUILD_PLATFORM_TAG=3.1-bullseye -ARG OUTPUT_PLATFORM_TAG=3.1-bullseye-slim -ARG DOTNET_PUBLISH_RUNTIME=linux-x64 - -FROM mcr.microsoft.com/dotnet/sdk:${BUILD_PLATFORM_TAG} AS build -ARG BUILD_PLATFORM_TAG -RUN echo "Build base image: mcr.microsoft.com/dotnet/sdk:${BUILD_PLATFORM_TAG}" - -WORKDIR /src -COPY ["samples/brokers/onvif-video-broker/onvif-video-broker.csproj", "."] -RUN find . && dotnet restore "onvif-video-broker.csproj" -COPY ["samples/brokers/onvif-video-broker", "."] -RUN find . && dotnet build "onvif-video-broker.csproj" -c Release -o /app/build - -FROM build AS publish -ARG DOTNET_PUBLISH_RUNTIME -RUN echo "Publishing to: ${DOTNET_PUBLISH_RUNTIME}" && \ - dotnet publish -r ${DOTNET_PUBLISH_RUNTIME} "onvif-video-broker.csproj" -c Release -o /app/publish - -FROM ghcr.io/project-akri/akri/opencvsharp-build:${OUTPUT_PLATFORM_TAG} AS final -ARG OUTPUT_PLATFORM_TAG -RUN echo "Output base image: ghcr.io/project-akri/akri/opencvsharp-build:${OUTPUT_PLATFORM_TAG}" - -WORKDIR /app -COPY --from=publish /app/publish . - -# Link the container to the Akri repository -LABEL org.opencontainers.image.source https://github.com/project-akri/akri - -EXPOSE 8083 -ENV ASPNETCORE_URLS=http://*:8083 - -CMD dotnet onvif-video-broker.dll \ No newline at end of file diff --git a/build/containers/Dockerfile.opcua-monitoring-broker b/build/containers/Dockerfile.opcua-monitoring-broker deleted file mode 100644 index 40a2e2e08..000000000 --- a/build/containers/Dockerfile.opcua-monitoring-broker +++ /dev/null @@ -1,24 +0,0 @@ -FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/dotnet/sdk:6.0-bookworm-slim AS build - -WORKDIR /src -COPY ["samples/brokers/opcua-monitoring-broker/opcua-monitoring-broker.csproj", "."] -RUN dotnet restore "opcua-monitoring-broker.csproj" -COPY ["samples/brokers/opcua-monitoring-broker", "."] - -ARG TARGETARCH -RUN DOTNET_PUBLISH_RUNTIME="linux-$(echo ${TARGETARCH} | sed 's/amd/x/')"; \ - echo "Publishing to: ${DOTNET_PUBLISH_RUNTIME}" && \ - dotnet publish -r ${DOTNET_PUBLISH_RUNTIME} "opcua-monitoring-broker.csproj" -c Release -o /app/publish - -FROM mcr.microsoft.com/dotnet/aspnet:6.0-bookworm-slim AS final - -WORKDIR /app -COPY --from=build /app/publish . - -# Link the container to the Akri repository -LABEL org.opencontainers.image.source https://github.com/project-akri/akri - -EXPOSE 8083 -ENV ASPNETCORE_URLS=http://*:8083 - -CMD dotnet opcua-monitoring-broker.dll \ No newline at end of file diff --git a/build/containers/Dockerfile.python-app b/build/containers/Dockerfile.python-app deleted file mode 100644 index daf2e357b..000000000 --- a/build/containers/Dockerfile.python-app +++ /dev/null @@ -1,31 +0,0 @@ -ARG PYTHON_VERSION=3.11 - -FROM python:${PYTHON_VERSION}-slim-bookworm - -ARG DEBIAN_FRONTEND=noninteractive - - -RUN echo "Creating container based on debian:bullseye-slim for ${TARGETPLATFORM}" && \ - apt-get update && \ - apt-get install -y --no-install-recommends \ - protobuf-compiler \ - libprotoc-dev && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - - -WORKDIR /app - -ARG APPLICATION - -COPY ./samples/apps/${APPLICATION}/requirements.txt . - -RUN pip install --no-cache-dir -r requirements.txt - -COPY ./samples/apps/${APPLICATION} . - -# OCI label -LABEL org.opencontainers.image.source="https://github.com/project-akri/akri" - -# Default command -ENTRYPOINT [ "python3", "app.py" ] \ No newline at end of file diff --git a/build/intermediate-containers.mk b/build/intermediate-containers.mk index 466daafe3..a47c1d0c4 100644 --- a/build/intermediate-containers.mk +++ b/build/intermediate-containers.mk @@ -1,4 +1,4 @@ -BUILD_OPENCV_BASE_VERSION = 0.1.1 +BUILD_OPENCV_BASE_VERSION = 0.1.2 # # diff --git a/build/samples.mk b/build/samples.mk deleted file mode 100644 index 645e5fd41..000000000 --- a/build/samples.mk +++ /dev/null @@ -1,36 +0,0 @@ -USE_OPENCV_BASE_VERSION = 0.0.11 - -.PHONY: samples -samples: opcua-monitoring-broker onvif-video-broker anomaly-detection-app video-streaming-app akri-udev-video-broker - -%-app: - docker buildx build $(COMMON_DOCKER_BUILD_ARGS) --build-arg APPLICATION=$@ --tag "$(PREFIX)/$@:$(LABEL_PREFIX)" --file $(DOCKERFILE_DIR)/Dockerfile.python-app . - -opcua-monitoring-broker: - docker buildx build $(COMMON_DOCKER_BUILD_ARGS) --tag "$(PREFIX)/opcua-monitoring-broker:$(LABEL_PREFIX)" --file $(DOCKERFILE_DIR)/Dockerfile.opcua-monitoring-broker . - -# Still use old-ish style for onvif-video-broker as app uses .NET 3.1 that doesn't have multi-arch manifest -onvif-video-broker: onvif-video-broker-multiarch - -onvif-video-broker-multiarch: onvif-video-broker-amd64 onvif-video-broker-arm64 onvif-video-broker-arm32 -ifeq (1, $(PUSH)) - docker buildx imagetools create --tag "$(PREFIX)/onvif-video-broker:$(LABEL_PREFIX)" -endif - -ONVIF_BUILDX_PUSH_OUTPUT = type=image,name=$(PREFIX)/onvif-video-broker,push-by-digest=true,name-canonical=true,push=true -ONVIF_BUILDX_ARGS = $(if $(LOAD), --load --tag $(PREFIX)/onvif-video-broker:$(LABEL_PREFIX)) $(if $(PUSH), --output $(ONVIF_BUILDX_PUSH_OUTPUT)) -f $(DOCKERFILE_DIR)/Dockerfile.onvif-video-broker - -onvif-video-broker-amd64: -ifneq (,or(findstring(amd64,$(PLATFORMS)), findstring(x86_64,$(PLATFORMS)))) - docker buildx build $(ONVIF_BUILDX_ARGS) $(if $(PUSH), --iidfile onvif-video-broker.sha-amd64) --build-arg OUTPUT_PLATFORM_TAG=$(USE_OPENCV_BASE_VERSION)-$(AMD64_SUFFIX) --build-arg DOTNET_PUBLISH_RUNTIME=linux-x64 . -endif - -onvif-video-broker-arm32: -ifneq (,findstring(arm/v7,$(PLATFORMS))) - docker buildx build $(ONVIF_BUILDX_ARGS) $(if $(PUSH), --iidfile onvif-video-broker.sha-arm32) --build-arg OUTPUT_PLATFORM_TAG=$(USE_OPENCV_BASE_VERSION)-$(ARM32V7_SUFFIX) --build-arg DOTNET_PUBLISH_RUNTIME=linux-arm . -endif - -onvif-video-broker-arm64: -ifneq (,or(findstring(aarch64,$(PLATFORMS)),findstring(arm64,$(PLATFORMS)))) - docker buildx build $(ONVIF_BUILDX_ARGS) $(if $(PUSH), --iidfile onvif-video-broker.sha-arm64) --build-arg OUTPUT_PLATFORM_TAG=$(USE_OPENCV_BASE_VERSION)-$(ARM64V8_SUFFIX) --build-arg DOTNET_PUBLISH_RUNTIME=linux-arm64 . -endif diff --git a/deployment/helm/Chart.yaml b/deployment/helm/Chart.yaml index 8b0da43b9..3c13b4c83 100644 --- a/deployment/helm/Chart.yaml +++ b/deployment/helm/Chart.yaml @@ -16,9 +16,9 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.13.21 +version: 0.13.22 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 0.13.21 +appVersion: 0.13.22 diff --git a/samples/apps/anomaly-detection-app/README.md b/samples/apps/anomaly-detection-app/README.md deleted file mode 100644 index e00bdea90..000000000 --- a/samples/apps/anomaly-detection-app/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Anomaly Detection Application -A sample anomaly detection web application for Akri's [OPC UA Thermometer demo](https://docs.akri.sh/demos/opc-thermometer-demo). - -Gets temperature values from a set of gRPC servers. It then determines whether this value is an outlier to the dataset -using the Local Outlier Factor strategy. The dataset is simply a csv with the numbers between 70-80 repeated several -times; therefore, any value significantly outside this range will be seen as an outlier. The web application serves as a -log, displaying all the temperature values and the address of the OPC UA Server that sent the values. It shows anomaly -values in red. - -## Dependencies -> Note: using a virtual environment is recommended with pip - -Install pip: -``` -sudo apt-get install -y python3-pip -``` -Navigate to this directory and use pip to install all dependencies in `requirements.txt`. -``` -pip install -r requirements.txt -``` - -To clean up, simply run `pip uninstall -r requirements.txt -y`. - -## Generating Protobuf Code -Set the path of the `opcua_node.proto` file and generate using `grpc-tools.protoc`. `grpc-tools` should've been installed in the previous step. The following assumes the `akri` repository is in the `$HOME` directory. -``` -export SRC_DIR=../../../samples/brokers/opcua-monitoring-broker -python3 -m grpc_tools.protoc -I=$SRC_DIR --python_out=. --grpc_python_out=. $SRC_DIR/opcua_node.proto -``` - -## Running -When running, `CONFIGURATION_NAME`, `${CONFIGURATION_NAME}_SVC_SERVICE_HOST` and -`${CONFIGURATION_NAME}_SVC_SERVICE_PORT_GRPC` environment variables must be specified. The application will call the -`GetValue` service on the endpoint `${CONFIGURATION_NAME}_SVC_SERVICE_HOST:${CONFIGURATION_NAME}_SVC_SERVICE_PORT_GRPC` -where the gRPC servers should be running. - -For example, if the servers are running at `localhost:80`, run the following: -```sh -CONFIGURATION_NAME="akri-opcua" AKRI_OPCUA_SVC_SERVICE_HOST=localhost AKRI_OPCUA_SVC_SERVICE_PORT_GRPC=80 python3 ./app.py -``` \ No newline at end of file diff --git a/samples/apps/anomaly-detection-app/app.py b/samples/apps/anomaly-detection-app/app.py deleted file mode 100644 index 82336646d..000000000 --- a/samples/apps/anomaly-detection-app/app.py +++ /dev/null @@ -1,124 +0,0 @@ -# Flask app that acts as a sample application for the OPC UA Monitoring Brokers. -# It periodically gets values from the brokers via grpc, detects whether they are anomaly values by comparing -# them to the training data set (data.csv) using Local Outlier Factor, and displays a log of the values on a -# web server, showing anomalies in red text. - -from datetime import datetime -from flask import Flask, render_template, make_response -from numpy import genfromtxt -from sklearn.neighbors import LocalOutlierFactor -from time import sleep -import grpc -import logging -import numpy as np -import opcua_node_pb2 -import opcua_node_pb2_grpc -import os -import threading -import traceback - - -app = Flask(__name__) - -# Log of all values reported by OPC UA servers and the time they were reported -values_log = [] - -# Interval for getting new values -fetch_interval_seconds = 1 - -@app.route('/') -# Home page for anomaly detection results -def index(): - return render_template('index.html') - -# Endpoint for grabbing current log of values -@app.route('/values_log') -def values_log_state(): - global values_log - return ";".join(values_log) - -# Gets the url of the service the OPC UA monitoring brokers are serving values on -def get_grpc_url(): - if 'CONFIGURATION_NAME' in os.environ: - configuration_name = os.environ['CONFIGURATION_NAME'] - short_env_var_prefix = (configuration_name + - '-').upper().replace('-', '_') - # For every k8s service, an env var is set in every node with its ports. The - # format is _SERVICE_PORT_. Here, we will query - # these values on the streaming app node, to get the exposed port number ... but - # we will assume that the ports are named 'grpc', if the name changes, THIS CODE - # WILL BREAK. - env_var_prefix = short_env_var_prefix + 'SVC_SERVICE_' - # instance services are using the same port by default - grpc_port = os.environ[env_var_prefix + 'PORT_GRPC'] - opcua_brokers_service = "{0}:{1}".format( - os.environ[env_var_prefix + 'HOST'], grpc_port) - return opcua_brokers_service - else: - raise Exception( - "CONFIGURATION_NAME not loaded as environment variable") - -# Creates log entry that reports the opc ua server, value, current time, and whether the value is an anomaly -def make_log_entry(server, value, is_anomaly): - anomaly_character = "Y" if is_anomaly else "N" - return "{0}, {1}, {2}{3}".format(server, value, datetime.now(), anomaly_character) - -# Periodically gets the latest value from the OPC UA monitoring brokers' grpc servers -def continuously_get_values(): - global values_log - url = get_grpc_url() - data = get_data_from_csv() - logging.info("Starting to call GetValue on endpoint %s", url) - while True: - try: - channel = grpc.insecure_channel(url, options=( - ('grpc.use_local_subchannel_pool', 1),)) - stub = opcua_node_pb2_grpc.OpcuaNodeStub(channel) - value_response = stub.GetValue(opcua_node_pb2.ValueRequest()) - channel.close() - if test_new_value(data, value_response.value) == -1: - values_log.append(make_log_entry( - value_response.opcua_server, value_response.value, True)) - logging.info("Latest anomaly added to log is {0}".format( - values_log[len(values_log) - 1])) - else: - # Check if server previously had anomaly and remove it if back to normal - values_log.append(make_log_entry( - value_response.opcua_server, value_response.value, False)) - logging.info("Latest normal value added to log is {0}".format( - values_log[len(values_log) - 1])) - sleep(fetch_interval_seconds) - except: - logging.info("[%s] Exception %s" % (url, traceback.format_exc())) - sleep(fetch_interval_seconds) - -# Uses Local Outlier Factor to determine whether the new value is an outlier to the dataset -# Returns -1 if the value is an outlier and 1 if it is a conforming value -def test_new_value(data, new_value): - extended_data = np.append(data, new_value) - reshaped_data = np.reshape(extended_data, (-1, 1)) - outlier_prediction_list = LocalOutlierFactor(n_neighbors=2, - contamination=0.1, - novelty=True).fit(reshaped_data).predict() - return outlier_prediction_list[len(outlier_prediction_list) - 1] - -# Get training data from csv -def get_data_from_csv(): - data = genfromtxt('data.csv', delimiter=',') - print("data is", data) - return data - -# Run webserver -def run_webserver(): - app.run(host='0.0.0.0', threaded=True) - -if __name__ == "__main__": - # Set up logging - format = "%(asctime)s: %(message)s" - logging.basicConfig(format=format, level=logging.INFO, datefmt="%H:%M:%S") - logging.info("Starting web server thread)") - webserver_thread = threading.Thread(target=run_webserver) - webserver_thread.setDaemon(True) - webserver_thread.start() - logging.info("Starting anomaly list updating thread") - continuously_get_values() diff --git a/samples/apps/anomaly-detection-app/data.csv b/samples/apps/anomaly-detection-app/data.csv deleted file mode 100644 index 2b1f33ad2..000000000 --- a/samples/apps/anomaly-detection-app/data.csv +++ /dev/null @@ -1 +0,0 @@ -72, 79, 71, 74, 71, 77, 77, 77, 80, 76, 73, 71, 77, 70, 76, 76, 79, 70, 77, 74, 73, 79, 71, 75, 70, 70, 70, 80, 78, 70, 76, 80, 73, 76, 70, 78, 73, 77, 77, 78, 73, 75, 73, 80, 73, 77, 74, 70, 76, 78, 80, 71, 72, 80, 74, 71, 75, 78, 76, 78, 80, 73, 74, 74, 79, 77, 78, 76, 79, 70, 77, 73, 76, 76, 80, 72, 75, 78, 80, 75, 71, 77, 80, 78, 71, 72, 78, 76, 75, 77, 70, 77, 70, 74, 79, 79, 79, 76, 80, 72 \ No newline at end of file diff --git a/samples/apps/anomaly-detection-app/generategrpc.sh b/samples/apps/anomaly-detection-app/generategrpc.sh deleted file mode 100755 index 73ffb5d3b..000000000 --- a/samples/apps/anomaly-detection-app/generategrpc.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -# based on https://grpc.io/docs/tutorials/basic/python/ -ABS_PATH=$(dirname $(readlink -e ../../../samples/brokers/opcua-monitoring-broker/opcua_node.proto)) -echo "absolute path to proto file is $ABS_PATH" -python3 -m grpc_tools.protoc -I./ --python_out=. --grpc_python_out=. opcua_node.proto --proto_path=$ABS_PATH diff --git a/samples/apps/anomaly-detection-app/opcua_node_pb2.py b/samples/apps/anomaly-detection-app/opcua_node_pb2.py deleted file mode 100644 index 339ced8ee..000000000 --- a/samples/apps/anomaly-detection-app/opcua_node_pb2.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: opcua_node.proto -"""Generated protocol buffer code.""" -from google.protobuf.internal import builder as _builder -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10opcua_node.proto\x12\tOpcuaNode\"\x0e\n\x0cValueRequest\"4\n\rValueResponse\x12\r\n\x05value\x18\x01 \x01(\x05\x12\x14\n\x0copcua_server\x18\x02 \x01(\t2J\n\tOpcuaNode\x12=\n\x08GetValue\x12\x17.OpcuaNode.ValueRequest\x1a\x18.OpcuaNode.ValueResponseB\x0c\xaa\x02\tOpcuaNodeb\x06proto3') - -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'opcua_node_pb2', globals()) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'\252\002\tOpcuaNode' - _VALUEREQUEST._serialized_start=31 - _VALUEREQUEST._serialized_end=45 - _VALUERESPONSE._serialized_start=47 - _VALUERESPONSE._serialized_end=99 - _OPCUANODE._serialized_start=101 - _OPCUANODE._serialized_end=175 -# @@protoc_insertion_point(module_scope) diff --git a/samples/apps/anomaly-detection-app/opcua_node_pb2_grpc.py b/samples/apps/anomaly-detection-app/opcua_node_pb2_grpc.py deleted file mode 100644 index 5e04f706c..000000000 --- a/samples/apps/anomaly-detection-app/opcua_node_pb2_grpc.py +++ /dev/null @@ -1,66 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -import opcua_node_pb2 as opcua__node__pb2 - - -class OpcuaNodeStub(object): - """Missing associated documentation comment in .proto file.""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.GetValue = channel.unary_unary( - '/OpcuaNode.OpcuaNode/GetValue', - request_serializer=opcua__node__pb2.ValueRequest.SerializeToString, - response_deserializer=opcua__node__pb2.ValueResponse.FromString, - ) - - -class OpcuaNodeServicer(object): - """Missing associated documentation comment in .proto file.""" - - def GetValue(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_OpcuaNodeServicer_to_server(servicer, server): - rpc_method_handlers = { - 'GetValue': grpc.unary_unary_rpc_method_handler( - servicer.GetValue, - request_deserializer=opcua__node__pb2.ValueRequest.FromString, - response_serializer=opcua__node__pb2.ValueResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'OpcuaNode.OpcuaNode', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class OpcuaNode(object): - """Missing associated documentation comment in .proto file.""" - - @staticmethod - def GetValue(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/OpcuaNode.OpcuaNode/GetValue', - opcua__node__pb2.ValueRequest.SerializeToString, - opcua__node__pb2.ValueResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/samples/apps/anomaly-detection-app/requirements.txt b/samples/apps/anomaly-detection-app/requirements.txt deleted file mode 100644 index eade653af..000000000 --- a/samples/apps/anomaly-detection-app/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -grpcio==1.53.0 -grpcio-tools==1.53.0 -Flask==2.2.5 -numpy -protobuf==4.21.6 -scikit_learn \ No newline at end of file diff --git a/samples/apps/anomaly-detection-app/templates/index.html b/samples/apps/anomaly-detection-app/templates/index.html deleted file mode 100644 index 936711a55..000000000 --- a/samples/apps/anomaly-detection-app/templates/index.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - Akri OPC UA Node value Detection - - - -
-

Akri OPC UA Node Value Anomaly Detection

-
-
    -
  • No values
  • -
-
-
- - - - \ No newline at end of file diff --git a/samples/apps/video-streaming-app/README.md b/samples/apps/video-streaming-app/README.md deleted file mode 100644 index 1dbefcd1d..000000000 --- a/samples/apps/video-streaming-app/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Video Streaming Application -## Overview -This application serves as an example streaming service for the [ONVIF broker](../../brokers/onvif-video-broker) and -[USB camera broker](../../brokers/udev-video-broker). It is used in Akri's [end to end -demo](https://docs.akri.sh/demos/usb-camera-demo). Both brokers act as gRPC services that sit on port 8083. The -streaming application creates gRPC clients to connect to the services and repeatedly calls `get_frame` to get the -images. It uses Flask to implement streaming. -## Limitations -This app streams images in mjpeg (Motion JPEG) format, since all browsers natively support mjpeg. This means this -application will only work on cameras that support MJPG or JPEG. The onvif-video-broker connects to the RTSP stream of -the cameras, which supports JPEG; however, not all usb cameras support MJPG/JPEG. To check that your camera supports -MJPG/JPEG, observe the output of `sudo v4l2-ctl --list-formats` on the associated node. - -## Dependencies -> Note: using a virtual environment is recommended with pip - -Install pip: -``` -sudo apt-get install -y python3-pip -``` -Navigate to this directory and use pip to install all dependencies in `requirements.txt`. -``` -pip install -r requirements.txt -``` - -To clean up, simply run `pip uninstall -r requirements.txt -y`. - -## Generating Protobuf Code -Generate using `grpc-tools.protoc`. `grpc-tools` should've been installed in the previous step. -``` -python3 -m grpc_tools.protoc -I./ --python_out=. --grpc_python_out=. camera.proto -``` - -## Running -The streaming application works in two modes. -1. Explicitly target a set of cameras by setting `CAMERA_COUNT`, a service to target all cameras (`CAMERAS_SOURCE_SVC`), - and services for each individual camera (`CAMERA1_SOURCE_SVC` to `CAMERA${CAMERA_COUNT}_SOURCE_SVC`) -```sh -CAMERA_COUNT="2" CAMERAS_SOURCE_SVC=10.2.2.2 CAMERA1_SOURCE_SVC=10.1.2.3 CAMERA2_SOURCE_SVC=10.2.3.4 python3 ./app.py -``` -2. Target all services of an Akri Configuration. The application will query for services prefixed with the Configuration - name. -```sh -CONFIGURATION_NAME="akri-udev" python3 ./app.py -``` \ No newline at end of file diff --git a/samples/apps/video-streaming-app/app.py b/samples/apps/video-streaming-app/app.py deleted file mode 100644 index 23371a4c7..000000000 --- a/samples/apps/video-streaming-app/app.py +++ /dev/null @@ -1,240 +0,0 @@ -import sys -import os - -from time import sleep - -import grpc - -import camera_pb2 -import camera_pb2_grpc - -import threading -import logging -from concurrent import futures -import queue -import traceback - -from flask import Flask, render_template, Response - -from kubernetes import client, config -import re - -class CameraFeed: - def __init__(self, url): - global global_stop_event - - self.url = url - self.queue = queue.Queue(1) - self.thread = None - self.stop_event = global_stop_event - - def __eq__(self, other): - if other is None: - return False - return self.url == other.url - - def start_handler(self): - self.thread = threading.Thread(target=self.get_frames) - self.thread.start() - - def wait_handler(self): - if self.thread is not None: - self.thread.join() - - # Generator function for video streaming. - def generator_func(self): - while not self.stop_event.wait(0.01): - frame = self.queue.get(True, None) - yield (b'--frame\r\nContent-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') - - # Loops, creating gRPC client and grabbing frame from camera serving specified url. - def get_frames(self): - logging.info("Starting get_frames(%s)" % self.url) - while not self.stop_event.wait(0.01): - try: - client_channel = grpc.insecure_channel(self.url, options=( - ('grpc.use_local_subchannel_pool', 1),)) - camera_stub = camera_pb2_grpc.CameraStub(client_channel) - frame = camera_stub.GetFrame(camera_pb2.NotifyRequest()) - frame = frame.frame - client_channel.close() - - frame_received = False - # prevent stale data - if (len(frame) > 0): - if (self.queue.full()): - try: - self.queue.get(False) - except: - pass - self.queue.put(frame, False) - frame_received = True - - if (frame_received): - sleep(1) - - except: - logging.info("[%s] Exception %s" % (self.url, traceback.format_exc())) - sleep(1) - -class CameraDisplay: - def __init__(self): - self.main_camera = None - self.small_cameras = [] - self.mutex = threading.Lock() - - def __eq__(self, other): - return self.main_camera == other.main_camera and self.small_cameras == other.small_cameras - - def start_handlers(self): - if self.main_camera is not None: - self.main_camera.start_handler() - for small_camera in self.small_cameras: - small_camera.start_handler() - - def wait_handlers(self): - global global_stop_event - - global_stop_event.set() - if self.main_camera is not None: - self.main_camera.wait_handler() - for small_camera in self.small_cameras: - small_camera.wait_handler() - global_stop_event.clear() - - def merge(self, other): - self.mutex.acquire() - try: - self.wait_handlers() - - self.main_camera = other.main_camera - self.small_cameras = other.small_cameras - - self.start_handlers() - finally: - self.mutex.release() - - def count(self): - self.mutex.acquire() - result = len(self.small_cameras) - if self.main_camera is not None: - result += 1 - self.mutex.release() - return result - - def hash_code(self): - self.mutex.acquire() - cameras = ",".join([camera.url for camera in self.small_cameras]) - if self.main_camera is not None: - cameras = "{0}+{1}".format(self.main_camera.url, cameras) - self.mutex.release() - return cameras - - def stream_frames(self, camera_id): - selected_camera = None - camera_id = int(camera_id) - - self.mutex.acquire() - if camera_id == 0: - selected_camera = self.main_camera - elif camera_id - 1 < len(self.small_cameras): - selected_camera = self.small_cameras[camera_id - 1] - self.mutex.release() - - if selected_camera is None: - return Response(None, 500) - else: - return Response(selected_camera.generator_func(), mimetype='multipart/x-mixed-replace; boundary=frame') - -def get_camera_display(configuration_name): - camera_display = CameraDisplay() - - config.load_incluster_config() - coreV1Api = client.CoreV1Api() - - # TODO use labels instead once available - instance_service_name_regex = re.compile( - configuration_name + "-[\da-f]{6}-svc") - - ret = coreV1Api.list_service_for_all_namespaces(watch=False) - for svc in ret.items: - if svc.metadata.name == configuration_name + "-svc": - grpc_ports = list( - filter(lambda port: port.name == "grpc", svc.spec.ports)) - if (len(grpc_ports) == 1): - url = "{0}:{1}".format(svc.spec.cluster_ip, grpc_ports[0].port) - camera_display.main_camera = CameraFeed(url) - elif instance_service_name_regex.match(svc.metadata.name): - grpc_ports = list( - filter(lambda port: port.name == "grpc", svc.spec.ports)) - if (len(grpc_ports) == 1): - url = "{0}:{1}".format(svc.spec.cluster_ip, grpc_ports[0].port) - camera_display.small_cameras.append(CameraFeed(url)) - - camera_display.small_cameras.sort(key=lambda camera: camera.url) - - return camera_display - -def run_webserver(): - app.run(host='0.0.0.0', threaded=True) - -def refresh_cameras(): - global global_camera_display - while True: - sleep(1) - camera_display = get_camera_display(os.environ['CONFIGURATION_NAME']) - if camera_display != global_camera_display: - global_camera_display.merge(camera_display) - -global_stop_event = threading.Event() -global_camera_display = CameraDisplay() - -app = Flask(__name__) - -# Home page for video streaming. -@app.route('/') -def index(): - global global_camera_display - return render_template('index.html', camera_count=global_camera_display.count(), camera_list=global_camera_display.hash_code()) - -# Returns the current list of cameras to allow for refresh -@app.route('/camera_list') -def camera_list(): - global global_camera_display - logging.info("Expected cameras: %s" % global_camera_display.hash_code()) - return global_camera_display.hash_code() - -# Gets frame feed for specified camera. -@app.route('/camera_frame_feed/') -def camera_frame_feed(camera_id=0): - global global_camera_display - return global_camera_display.stream_frames(camera_id) - -print("Starting...", flush=True) -logging.basicConfig(format="%(asctime)s: %(message)s", level=logging.INFO, datefmt="%H:%M:%S") - -if 'CONFIGURATION_NAME' in os.environ: - # Expecting source service ports to be named grpc - - configuration_name = os.environ['CONFIGURATION_NAME'] - camera_display = get_camera_display(configuration_name) - global_camera_display.merge(camera_display) - - refresh_thread = threading.Thread(target=refresh_cameras) - refresh_thread.start() -else: - camera_count = int(os.environ['CAMERA_COUNT']) - main_camera_url = "{0}:80".format(os.environ['CAMERAS_SOURCE_SVC']) - global_camera_display.main_camera = CameraFeed(main_camera_url) - for camera_id in range(1, camera_count + 1): - url = "{0}:80".format( - os.environ['CAMERA{0}_SOURCE_SVC'.format(camera_id)]) - global_camera_display.small_cameras.append(CameraFeed(url)) - global_camera_display.start_handlers() - -webserver_thread = threading.Thread(target=run_webserver) -webserver_thread.start() - -print("Started", flush=True) -webserver_thread.join() -print("Done", flush=True) diff --git a/samples/apps/video-streaming-app/camera.proto b/samples/apps/video-streaming-app/camera.proto deleted file mode 100644 index 251ed0468..000000000 --- a/samples/apps/video-streaming-app/camera.proto +++ /dev/null @@ -1,18 +0,0 @@ -syntax = "proto3"; - -option csharp_namespace = "Camera"; - -package camera; - -service Camera { - rpc GetFrame (NotifyRequest) returns (NotifyResponse); -} - -message NotifyRequest { -} - -message NotifyResponse { - bytes frame = 1; - string camera = 2; -} - diff --git a/samples/apps/video-streaming-app/camera_pb2.py b/samples/apps/video-streaming-app/camera_pb2.py deleted file mode 100644 index 2874f7af1..000000000 --- a/samples/apps/video-streaming-app/camera_pb2.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: camera.proto -"""Generated protocol buffer code.""" -from google.protobuf.internal import builder as _builder -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0c\x63\x61mera.proto\x12\x06\x63\x61mera\"\x0f\n\rNotifyRequest\"/\n\x0eNotifyResponse\x12\r\n\x05\x66rame\x18\x01 \x01(\x0c\x12\x0e\n\x06\x63\x61mera\x18\x02 \x01(\t2C\n\x06\x43\x61mera\x12\x39\n\x08GetFrame\x12\x15.camera.NotifyRequest\x1a\x16.camera.NotifyResponseB\t\xaa\x02\x06\x43\x61merab\x06proto3') - -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'camera_pb2', globals()) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'\252\002\006Camera' - _NOTIFYREQUEST._serialized_start=24 - _NOTIFYREQUEST._serialized_end=39 - _NOTIFYRESPONSE._serialized_start=41 - _NOTIFYRESPONSE._serialized_end=88 - _CAMERA._serialized_start=90 - _CAMERA._serialized_end=157 -# @@protoc_insertion_point(module_scope) diff --git a/samples/apps/video-streaming-app/camera_pb2_grpc.py b/samples/apps/video-streaming-app/camera_pb2_grpc.py deleted file mode 100644 index 991a1e96f..000000000 --- a/samples/apps/video-streaming-app/camera_pb2_grpc.py +++ /dev/null @@ -1,66 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -import camera_pb2 as camera__pb2 - - -class CameraStub(object): - """Missing associated documentation comment in .proto file.""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.GetFrame = channel.unary_unary( - '/camera.Camera/GetFrame', - request_serializer=camera__pb2.NotifyRequest.SerializeToString, - response_deserializer=camera__pb2.NotifyResponse.FromString, - ) - - -class CameraServicer(object): - """Missing associated documentation comment in .proto file.""" - - def GetFrame(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_CameraServicer_to_server(servicer, server): - rpc_method_handlers = { - 'GetFrame': grpc.unary_unary_rpc_method_handler( - servicer.GetFrame, - request_deserializer=camera__pb2.NotifyRequest.FromString, - response_serializer=camera__pb2.NotifyResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'camera.Camera', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class Camera(object): - """Missing associated documentation comment in .proto file.""" - - @staticmethod - def GetFrame(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/camera.Camera/GetFrame', - camera__pb2.NotifyRequest.SerializeToString, - camera__pb2.NotifyResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/samples/apps/video-streaming-app/generategrpc.sh b/samples/apps/video-streaming-app/generategrpc.sh deleted file mode 100755 index b6c9d19b0..000000000 --- a/samples/apps/video-streaming-app/generategrpc.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# based on https://grpc.io/docs/tutorials/basic/python/ -python3 -m grpc_tools.protoc -I./ --python_out=. --grpc_python_out=. camera.proto diff --git a/samples/apps/video-streaming-app/requirements.txt b/samples/apps/video-streaming-app/requirements.txt deleted file mode 100644 index 94b625591..000000000 --- a/samples/apps/video-streaming-app/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -Flask==2.2.5 -grpcio==1.53.0 -grpcio-tools==1.53.0 -kubernetes==19.15.0 -protobuf==4.21.6 diff --git a/samples/apps/video-streaming-app/templates/index.html b/samples/apps/video-streaming-app/templates/index.html deleted file mode 100644 index dde641b9e..000000000 --- a/samples/apps/video-streaming-app/templates/index.html +++ /dev/null @@ -1,35 +0,0 @@ - - - Akri Demo - - -
-

Akri Demo

- {%if camera_count > 0 %} -
- -
- {%endif%} -
    - {%for camera_id in range(1, camera_count)%} -
  • - -
  • - {%endfor%} -
-
- - - diff --git a/samples/brokers/onvif-video-broker/Akri.cs b/samples/brokers/onvif-video-broker/Akri.cs deleted file mode 100644 index 2be51ee1a..000000000 --- a/samples/brokers/onvif-video-broker/Akri.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Text.RegularExpressions; -using System.Xml; -using System.Xml.XPath; - -namespace Akri -{ - public static class Akri - { - private static string PostSoapRequest(String requestUri, String action, String soapMessage) - { - var request = (HttpWebRequest) WebRequest.CreateDefault(new Uri(requestUri)); - request.ContentType = "application/soap+xml; charset=utf-8"; - request.Method = HttpMethod.Post.ToString(); - request.Headers.Add("SOAPAction", action); - using (var stream = new StreamWriter(request.GetRequestStream(), Encoding.UTF8)) - { - stream.Write(soapMessage); - } - - Console.WriteLine($"[Akri] ONVIF request {requestUri} {action}"); - using (WebResponse requestResponse = request.GetResponse()) - { - using (StreamReader responseReader = new StreamReader(requestResponse.GetResponseStream())) - { - return responseReader.ReadToEnd(); - } - } - } - - private const String MEDIA_WSDL = "http://www.onvif.org/ver10/media/wsdl"; - private const String DEVICE_WSDL = "http://www.onvif.org/ver10/device/wsdl"; - private const String GET_SERVICE_SOAP = @""; - private const String GET_PROFILES_SOAP_TEMPLATE = @" - - {0} - - - - - "; - private const String GET_STREAMING_URI_SOAP_TEMPLATE = @" - - {0} - - - - - RTP-Unicast - - RTSP - - - {1} - - - "; - - // Regular expression pattern of environment variables that hold OPC UA DiscoveryURL - // The pattern is ONVIF_DEVICE_SERVICE_URL_ followed by 6 digit digest. e.g. - // ONVIF_DEVICE_SERVICE_URL_123456, ONVIF_DEVICE_SERVICE_URL_ABCDEF - private const string OnvifDeviceServiceUrlLabelPattern = "^ONVIF_DEVICE_SERVICE_URL_[A-F0-9]{6,6}$"; - private const string OnvifDeviceUuidLabelPattern = "^ONVIF_DEVICE_UUID_[A-F0-9]{6,6}$"; - - private static string GetMediaUrl(String device_service_url) - { - var servicesResult = PostSoapRequest( - device_service_url, - String.Format("{0}/{1}", DEVICE_WSDL, "GetService"), - GET_SERVICE_SOAP - ); - var document = new XPathDocument(new XmlTextReader(new StringReader(servicesResult))); - var navigator = document.CreateNavigator(); - var xpath = String.Format("//*[local-name()='GetServicesResponse']/*[local-name()='Service' and *[local-name()='Namespace']/text() ='{0}']/*[local-name()='XAddr']/text()", MEDIA_WSDL); - var media_url = navigator.SelectSingleNode(xpath).ToString(); - Console.WriteLine($"[Akri] ONVIF media url {media_url}"); - return media_url; - } - - private static string GetProfile(String media_url, UsernameToken usernameToken) - { - var soapSecurityHeader = usernameToken.ToXml(); - var soapMessage = String.Format(GET_PROFILES_SOAP_TEMPLATE, soapSecurityHeader); - var servicesResult = PostSoapRequest( - media_url, - String.Format("{0}/{1}", MEDIA_WSDL, "GetProfiles"), - soapMessage - ); - var document = new XPathDocument(new XmlTextReader(new StringReader(servicesResult))); - var navigator = document.CreateNavigator(); - var xpath = String.Format("//*[local-name()='GetProfilesResponse']/*[local-name()='Profiles']/@token"); - var profileNodesIterator = navigator.Select(xpath); - var profiles = (from XPathNavigator @group in profileNodesIterator select @group.Value).ToList(); - profiles.Sort(); - foreach (var p in profiles) { - Console.WriteLine($"[Akri] ONVIF profile list contains: {p}"); - } - // randomly choose first profile - var profile = profiles.First(); - Console.WriteLine($"[Akri] ONVIF profile list {profile}"); - return profile; - } - - private static string GetStreamingUri(String media_url, String profile_token, UsernameToken usernameToken) - { - var soapSecurityHeader = usernameToken.ToXml(); - var soapMessage = String.Format(GET_STREAMING_URI_SOAP_TEMPLATE, soapSecurityHeader, profile_token); - var servicesResult = PostSoapRequest( - media_url, - String.Format("{0}/{1}", MEDIA_WSDL, "GetStreamUri"), - soapMessage - ); - var document = new XPathDocument(new XmlTextReader(new StringReader(servicesResult))); - var navigator = document.CreateNavigator(); - var xpath = String.Format("//*[local-name()='GetStreamUriResponse']/*[local-name()='MediaUri']/*[local-name()='Uri']/text()"); - var profileNodesIterator = navigator.Select(xpath); - var streaming_uri_list = (from XPathNavigator @group in profileNodesIterator select @group.Value).ToList(); - foreach (var u in streaming_uri_list) { - Console.WriteLine($"[Akri] ONVIF streaming uri list contains: {u}"); - } - // randomly choose first profile - var streaming_uri = streaming_uri_list.First(); - Console.WriteLine($"[Akri] ONVIF streaming uri {streaming_uri}"); - - const string rtspPrefix = "rtsp://"; - if (streaming_uri.StartsWith(rtspPrefix)) { - if (!String.IsNullOrEmpty(usernameToken.Username)) { - var password = usernameToken.Password ?? ""; - var credential_string = String.Format("{0}:{1}@", usernameToken.Username, password); - streaming_uri = streaming_uri.Substring(rtspPrefix.Length); - streaming_uri = String.Format("{0}{1}{2}", rtspPrefix, credential_string, streaming_uri); - } - } - return streaming_uri; - } - - private static List GetDeviceServiceUrls() - { - var values = new List(); - foreach (DictionaryEntry de in Environment.GetEnvironmentVariables()) - { - if (Regex.IsMatch(de.Key.ToString(), OnvifDeviceServiceUrlLabelPattern)) - { - values.Add(de.Value.ToString()); - } - } - return values; - } - - private static List GetDeviceUuids() - { - var values = new List(); - foreach (DictionaryEntry de in Environment.GetEnvironmentVariables()) - { - if (Regex.IsMatch(de.Key.ToString(), OnvifDeviceUuidLabelPattern)) - { - values.Add(de.Value.ToString()); - } - } - return values; - } - - public static string GetRtspUrl() - { - var device_uuids = GetDeviceUuids(); - var device_uuid = (device_uuids.Count != 0) ? device_uuids[0] : ""; - Credential credential = null; - if (!string.IsNullOrEmpty(device_uuid)) - { - var credentialDirectory = Environment.GetEnvironmentVariable("CREDENTIAL_DIRECTORY"); - var credentialConfigMapDirectory = Environment.GetEnvironmentVariable("CREDENTIAL_CONFIGMAP_DIRECTORY"); - var credentialStore = new CredentialStore(credentialDirectory, credentialConfigMapDirectory); - credential = credentialStore.Get(device_uuid); - } - var userNameToken = new UsernameToken(credential?.Username, credential?.Password); - - // Get the first found Onvif device service url and use it - var device_service_urls = GetDeviceServiceUrls(); - var device_service_url = (device_service_urls.Count != 0) ? device_service_urls[0] : ""; - if (string.IsNullOrEmpty(device_service_url)) - { - throw new ArgumentNullException("ONVIF_DEVICE_SERVICE_URL undefined"); - } - - var media_url = GetMediaUrl(device_service_url); - var profile = GetProfile(media_url, userNameToken); - var streaming_url = GetStreamingUri(media_url, profile, userNameToken); - return streaming_url; - } - } -} diff --git a/samples/brokers/onvif-video-broker/CredentialStore.cs b/samples/brokers/onvif-video-broker/CredentialStore.cs deleted file mode 100644 index 46d83b46b..000000000 --- a/samples/brokers/onvif-video-broker/CredentialStore.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.Json; - -namespace Akri -{ - class Credential { - public string Username { get; set; } - public string Password { get; set; } - } - - class CredentialData { - public string Username { get; set; } - public string Password { get; set; } - public bool Base64encoded { get; set; } - } - - class CredentialRefData { - public string Username_ref { get; set; } - public string Password_ref { get; set; } - } - - class CredentialStore { - private readonly string secretDirectory; - private readonly string configMapDirectory; - - public CredentialStore(string secretDirectory, string configMapDirectory) { - this.secretDirectory = secretDirectory; - this.configMapDirectory = configMapDirectory; - } - - public Credential Get(string id) { - if (String.IsNullOrEmpty(this.secretDirectory)) { - return null; - } - - Credential defaultCredential = null; - // get credential from secrets - var (credential, isDefault) = GetCredentialFromSecret(id); - if ((credential != null) && !isDefault) { - return credential; - } - defaultCredential ??= credential; - - // get credential from credential ref list - (credential, isDefault) = GetCredentialFromCredentialRefList(id); - if ((credential != null) && !isDefault) { - return credential; - } - defaultCredential ??= credential; - - // get credential from credential list - (credential, isDefault) = GetCredentialFromCredentialList(id); - if ((credential != null) && !isDefault) { - return credential; - } - defaultCredential ??= credential; - - return defaultCredential; - } - - private (Credential, bool) GetCredentialFromCredentialRefList(string id) { - var credentialRefList = GetListContent>("device_credential_ref_list"); - if (credentialRefList == null) { - return (null, false); - } - - var allRefDictionary = new Dictionary(); - foreach (var refList in credentialRefList) { - var refEntries = GetListContent>(refList); - if (refEntries != null) { - refEntries.ToList().ForEach(x => allRefDictionary[x.Key] = x.Value); - } - } - - var isDefault = false; - if (!allRefDictionary.TryGetValue(id, out CredentialRefData credentialRefData)) { - if (!allRefDictionary.TryGetValue("default", out credentialRefData)) { - return (null, false); - } - isDefault = true; - } - Credential credential = null; - string username = ReadStringFromFile(this.secretDirectory, credentialRefData.Username_ref); - if (!String.IsNullOrEmpty(username)) { - string password = ReadStringFromFile(this.secretDirectory, credentialRefData.Password_ref); - credential = new Credential() { - Username = username, - Password = password - }; - } - - return (credential, isDefault); - } - - private (Credential, bool) GetCredentialFromCredentialList(string id) { - var credentialList = GetListContent>("device_credential_list"); - if (credentialList == null) { - return (null, false); - } - - var allCredentialDictionary = new Dictionary(); - foreach (var refList in credentialList) { - var credentialRef = ReadStringFromFile(this.secretDirectory, refList); - if (!String.IsNullOrEmpty(credentialRef)) { - var result = Deserialize>(credentialRef); - if (result != null) { - result.ToList().ForEach(x => allCredentialDictionary[x.Key] = x.Value); - } - } - } - - var isDefault = false; - if (!allCredentialDictionary.TryGetValue(id, out CredentialData credentialData)) { - if (!allCredentialDictionary.TryGetValue("default", out credentialData)) { - return (null, false); - } - isDefault = true; - } - var decodedPassword = credentialData.Password; - if (credentialData.Base64encoded) { - byte[] data = Convert.FromBase64String(credentialData.Password); - decodedPassword = System.Text.Encoding.UTF8.GetString(data); - } - - Credential credential = new Credential() { - Username = credentialData.Username, - Password = decodedPassword - }; - - return (credential, isDefault); - } - - private (Credential, bool) GetCredentialFromSecret(string id) { - // Secret uses underscore as key name, replace all dashes with underscore - id = id.Replace('-', '_'); - var credential = GetCredentialFromSecretById(id); - if (credential != null) { - return (credential, false); - } - var defaultCredential = GetCredentialFromSecretById("default"); - return (defaultCredential, true); - } - - private Credential GetCredentialFromSecretById(string id) { - string usernameFilename = String.Format("username_{0}", id); - string username = ReadStringFromFile(this.secretDirectory, usernameFilename); - if (String.IsNullOrEmpty(username)) { - return null; - } - string passwordFilename = String.Format("password_{0}", id); - string password = ReadStringFromFile(this.secretDirectory, passwordFilename); - return new Credential() { - Username = username, - Password = password - }; - } - - private TValue GetListContent(string listName) { - string listContent = null; - if (!String.IsNullOrEmpty(this.configMapDirectory)) { - listContent = ReadStringFromFile(this.configMapDirectory, listName); - } - if (String.IsNullOrEmpty(listContent)) { - listContent = ReadStringFromFile(this.secretDirectory, listName); - } - if (String.IsNullOrEmpty(listContent)) { - return default(TValue); - } - - return Deserialize(listContent); - } - - private string ReadStringFromFile(string path, string filename) { - string fileFullPath = Path.Combine(path, filename); - try { - return File.ReadAllText(fileFullPath); - } catch (Exception) { - return null; - } - } - - private TValue Deserialize(string jsonString) - { - try { - var options = new JsonSerializerOptions() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - return JsonSerializer.Deserialize(jsonString, options); - } catch (Exception) { - return default(TValue); - } - } - } -} diff --git a/samples/brokers/onvif-video-broker/Program.cs b/samples/brokers/onvif-video-broker/Program.cs deleted file mode 100644 index 18cb510f1..000000000 --- a/samples/brokers/onvif-video-broker/Program.cs +++ /dev/null @@ -1,179 +0,0 @@ -using Camera; -using Grpc.Core; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; -using OpenCvSharp; -using Prometheus; -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; - -namespace FrameServer -{ - public class CameraService : Camera.Camera.CameraBase - { - public override Task GetFrame( - NotifyRequest request, ServerCallContext context) - { - // Mask credential information in Program.RtspUrl to prevent the credential info shown in console output - var rtspUrlWithMaskedCredential = RtspUrlHelper.GetMaskedCredentialUrl(Program.RtspUrl); - byte[] frame = null; - lock (Program.Frames) - { - if (Program.Frames.Any()) - { - frame = Program.Frames.Pop(); - Program.JobsInQueue.Set(Program.Frames.Count); - } - - if (frame == null) - { - Console.WriteLine("No frame available for {0}", rtspUrlWithMaskedCredential); - } - else - { - Console.WriteLine("Sending frame for {0}, Q size: {1}", rtspUrlWithMaskedCredential, Program.Frames.Count); - } - } - - return Task.FromResult(new NotifyResponse - { - Camera = Program.RtspUrl, - Frame = (frame == null ? Google.Protobuf.ByteString.Empty : Google.Protobuf.ByteString.CopyFrom(frame)) - }); - } - } - - // based on https://stackoverflow.com/questions/14101310/limit-the-size-of-a-generic-collection - public class LimitedSizeStack : LinkedList - { - private readonly int _maxSize; - public LimitedSizeStack(int maxSize) - { - _maxSize = maxSize; - } - - public void Push(T item) - { - this.AddFirst(item); - - if (this.Count > _maxSize) - this.RemoveLast(); - } - - public T Pop() - { - var item = this.First.Value; - this.RemoveFirst(); - return item; - } - } - - public static class RtspUrlHelper { - public static string GetMaskedCredentialUrl(string rtspUrl) { - const string rtspPrefix = "rtsp://"; - var maskedRtspUrl = rtspUrl; - if (rtspUrl.StartsWith(rtspPrefix)) { - var atPos = rtspUrl.IndexOf('@', rtspPrefix.Length); - if (atPos != -1) { - maskedRtspUrl = rtspUrl.Substring(atPos); - maskedRtspUrl = String.Format("{0}----:----{1}", rtspPrefix, maskedRtspUrl); - } - } - return maskedRtspUrl; - } - } - - class Program - { - public static Task FrameTask; - public static string RtspUrl; - public static LimitedSizeStack Frames; - - static void Main(string[] args) - { - var frameBufferSizeSetting = Environment.GetEnvironmentVariable("FRAME_BUFFER_SIZE"); - int frameBufferSize = - string.IsNullOrEmpty(frameBufferSizeSetting) ? 2 : int.Parse(frameBufferSizeSetting); - Frames = new LimitedSizeStack(frameBufferSize); - if (Frames == null) { - throw new ArgumentNullException("Unable to create Frames"); - } - - RtspUrl = Environment.GetEnvironmentVariable("RTSP_URL"); - if (string.IsNullOrEmpty(RtspUrl)) { - RtspUrl = Akri.Akri.GetRtspUrl(); - } - if (string.IsNullOrEmpty(RtspUrl)) - { - throw new ArgumentNullException("Unable to find RTSP URL"); - } - - CamerasCounter.Inc(); - - FrameTask = Task.Run(() => Process(RtspUrl)); - - var metricServer = new KestrelMetricServer(port: 8080); - metricServer.Start(); - - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - - public static readonly Gauge JobsInQueue = Metrics.CreateGauge( - "cached_frames", - "Number of cached camera frames."); - - private static readonly Counter CamerasCounter = Metrics.CreateCounter( - "cameras", - "Number of connected cameras."); - - private static readonly Counter CameraDisconnectCounter = Metrics.CreateCounter( - "camera_disconnects", - "Number of times camera connection had to be restablished."); - - static void Process(string videoPath) - { - // Mask credential information in videoPath to prevent the credential info shown in console output - var videoPathWithMaskedCredential = RtspUrlHelper.GetMaskedCredentialUrl(videoPath); - Console.WriteLine($"[VideoProcessor] Processing RTSP stream: {videoPathWithMaskedCredential}"); - - while (true) - { - var capture = new VideoCapture(videoPath); - Console.WriteLine("Ready " + capture.IsOpened()); - - using (var image = new Mat()) // Frame image buffer - { - // Loop while we can read an image (aka: image.Empty is not true) - while (capture.Read(image) && !image.Empty()) - { - lock (Frames) - { - var imageBytes = image.ToBytes(); - Frames.Push(imageBytes); - JobsInQueue.Set(Frames.Count); - Console.WriteLine("Adding frame from {0}, Q size: {1}, frame size: {2}", videoPathWithMaskedCredential, Program.Frames.Count, imageBytes.Length); - } - } - } - - CameraDisconnectCounter.Inc(); - Console.WriteLine($"[VideoProcessor] Reopening"); - } - } - } -} - diff --git a/samples/brokers/onvif-video-broker/Properties/launchSettings.json b/samples/brokers/onvif-video-broker/Properties/launchSettings.json deleted file mode 100644 index 53ea44dc2..000000000 --- a/samples/brokers/onvif-video-broker/Properties/launchSettings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "profiles": { - "onvif-video-broker": { - "commandName": "Project", - "launchBrowser": false, - "applicationUrl": "https://localhost:5001", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/samples/brokers/onvif-video-broker/README.md b/samples/brokers/onvif-video-broker/README.md deleted file mode 100644 index f328684ca..000000000 --- a/samples/brokers/onvif-video-broker/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# ONVIF Video Broker -Sample broker for for Akri's [ONVIF Configuration](https://docs.akri.sh/discovery-handlers/onvif). Pulls video frames -from the rtsp stream of the ONVIF camera at `ONVIF_DEVICE_SERVICE_URL`. Then, it serves these frames over a gRPC -interface. - -## Running -1. Install .NET according to [.NET instructions](https://docs.microsoft.com/dotnet/install/linux-ubuntu) -1. Install [opencvsharp](https://github.com/shimat/opencvsharp), the OpenCV wrapper for .NET -1. Build - ```sh - cd ./samples/brokers/onvif-video-broker - dotnet build - ``` -1. Run the broker, passing in the ONVIF service URL for the camera it should pull frames from. - ```sh - ONVIF_DEVICE_SERVICE_URL_ABCDEF=http://10.1.2.3:1000/onvif/device_service dotnet run - ``` \ No newline at end of file diff --git a/samples/brokers/onvif-video-broker/Startup.cs b/samples/brokers/onvif-video-broker/Startup.cs deleted file mode 100644 index 4eb5544e7..000000000 --- a/samples/brokers/onvif-video-broker/Startup.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Prometheus; - -namespace FrameServer -{ - public class Startup - { - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) - { - services.AddGrpc(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseRouting(); - app.UseHttpMetrics(); - - app.UseEndpoints(endpoints => - { - endpoints.MapGrpcService(); - - endpoints.MapMetrics(); - - endpoints.MapGet("/", async context => - { - await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); - }); - }); - } - } -} - diff --git a/samples/brokers/onvif-video-broker/UsernameToken.cs b/samples/brokers/onvif-video-broker/UsernameToken.cs deleted file mode 100644 index 1cba08074..000000000 --- a/samples/brokers/onvif-video-broker/UsernameToken.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Text; -using System.Security.Cryptography; - -namespace Akri -{ - class UsernameToken { - public string Username { get; } - public string Password { get; } - - public UsernameToken(string username, string password) - { - this.Username = username; - this.Password = password; - } - - public string ToXml() { - if (this.Username is null) { - return ""; - } - var password = this.Password?? ""; - - var nonce = CalculateNonce(); - var created = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); - var passwordDigest = CalculatePasswordDigest(nonce, created, password); - return String.Format(SOAP_SECURITY_HEADER_TEMPLATE, this.Username, passwordDigest, nonce, created); - } - - private const String SOAP_SECURITY_HEADER_TEMPLATE = @" - - {0} - {1} - {2} - {3} - - "; - - private string CalculateNonce() - { - var buffer = new byte[16]; - using (var r = RandomNumberGenerator.Create()) - { - r.GetBytes(buffer); - } - return Convert.ToBase64String(buffer); - } - - private string CalculatePasswordDigest(string nonceStr, string created, string password) - { - var nonce = Convert.FromBase64String(nonceStr); - var createdBytes = Encoding.UTF8.GetBytes(created); - var passwordBytes = Encoding.UTF8.GetBytes(password); - var combined = new byte[createdBytes.Length + nonce.Length + passwordBytes.Length]; - Buffer.BlockCopy(nonce, 0, combined, 0, nonce.Length); - Buffer.BlockCopy(createdBytes, 0, combined, nonce.Length, createdBytes.Length); - Buffer.BlockCopy(passwordBytes, 0, combined, nonce.Length + createdBytes.Length, passwordBytes.Length); - - return Convert.ToBase64String(SHA1.Create().ComputeHash(combined)); - } - } -} diff --git a/samples/brokers/onvif-video-broker/appsettings.Development.json b/samples/brokers/onvif-video-broker/appsettings.Development.json deleted file mode 100644 index eec2e4c53..000000000 --- a/samples/brokers/onvif-video-broker/appsettings.Development.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Grpc": "Information", - "Microsoft": "Information" - } - } -} diff --git a/samples/brokers/onvif-video-broker/appsettings.json b/samples/brokers/onvif-video-broker/appsettings.json deleted file mode 100644 index f5ef230f6..000000000 --- a/samples/brokers/onvif-video-broker/appsettings.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "AllowedHosts": "*", - "Kestrel": { - "EndpointDefaults": { - "Protocols": "Http2" - } - } -} diff --git a/samples/brokers/onvif-video-broker/camera.proto b/samples/brokers/onvif-video-broker/camera.proto deleted file mode 100644 index e3b7c4474..000000000 --- a/samples/brokers/onvif-video-broker/camera.proto +++ /dev/null @@ -1,19 +0,0 @@ -syntax = "proto3"; - -option csharp_namespace = "Camera"; - -package camera; - -service Camera { - rpc GetFrame (NotifyRequest) returns (NotifyResponse); -} - -message NotifyRequest { -} - -message NotifyResponse { - bytes frame = 1; - string camera = 2; -} - - diff --git a/samples/brokers/onvif-video-broker/onvif-video-broker.csproj b/samples/brokers/onvif-video-broker/onvif-video-broker.csproj deleted file mode 100644 index 60cbd8ab4..000000000 --- a/samples/brokers/onvif-video-broker/onvif-video-broker.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - Exe - netcoreapp3.1 - Linux - . - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - diff --git a/samples/brokers/opcua-monitoring-broker/Program.cs b/samples/brokers/opcua-monitoring-broker/Program.cs deleted file mode 100644 index 68bb500a8..000000000 --- a/samples/brokers/opcua-monitoring-broker/Program.cs +++ /dev/null @@ -1,467 +0,0 @@ -/* ======================================================================== - * Copyright (c) 2005-2019 The OPC Foundation, Inc. All rights reserved. - * - * OPC Foundation MIT License 1.00 - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - * - * The complete license agreement can be found here: - * http://opcfoundation.org/License/MIT/1.00/ - * ======================================================================*/ - -using Opc.Ua; -using Opc.Ua.Client; -using Opc.Ua.Configuration; -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Security.Cryptography.X509Certificates; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; -using Grpc.Core; -using OpcuaNode; - -namespace OpcuaNodeMonitoring -{ - - public enum ExitCode : int - { - Ok = 0, - ErrorCreateApplication = 0x11, - ErrorDiscoverEndpoints = 0x12, - ErrorCreateSession = 0x13, - ErrorBrowseNamespace = 0x14, - ErrorCreateSubscription = 0x15, - ErrorMonitoredItem = 0x16, - ErrorAddSubscription = 0x17, - ErrorRunning = 0x18, - ErrorNoKeepAlive = 0x30, - }; - - public class Program - { - // Regular expression pattern of environment variables that hold OPC UA DiscoveryURL - // The pattern is OPCUA_DISCOVERY_URL_ followed by 6 digit digest. e.g. - // OPCUA_DISCOVERY_URL_123456, OPCUA_DISCOVERY_URL_ABCDEF - public const string OpcuaDiscoveryUrlLabelPattern = "OPCUA_DISCOVERY_URL_[A-F0-9]{6,6}$"; - // Name of environment variable that holds the identifier for the OPC UA Node to monitor - public const string IdentifierLabel = "IDENTIFIER"; - // Name of environment variable that holds the amespaceIndex for the OPC UA Node to monitor - public const string NamespaceIndexLabel = "NAMESPACE_INDEX"; - // DiscoveryURL of the server with which this OPC UA Client creates a secure connection - public static string OpcuaServerDiscoveryURL; - // NodeId for the OPC UA Node to monitor (See https://reference.opcfoundation.org/v104/Core/docs/Part3/8.2.1/). - // NamespaceIndex and Identifier are passed to the broker as environment variables. - // Identifier's identifierType must be a String. TODO: Support other identifierTypes - public static NodeId MonitoredNodeId; - - static int Main(string[] args) - { - Console.WriteLine(".NET Core OPC UA Console Client Start"); - - // Get the first found OPC UA Server DiscoveryURL and store it as a global variable - var serverDiscoveryUrls = GetServerDiscoveryUrls(); - OpcuaServerDiscoveryURL = (serverDiscoveryUrls.Count != 0) ? serverDiscoveryUrls[0] : ""; - if (string.IsNullOrEmpty(OpcuaServerDiscoveryURL)) - { - throw new ArgumentNullException(OpcuaDiscoveryUrlLabelPattern, "Unable to get OPC UA endpoint in environment variable"); - } - string OpcuaNodeIdentifier = Environment.GetEnvironmentVariable(IdentifierLabel); - if (string.IsNullOrEmpty(OpcuaNodeIdentifier)) - { - throw new ArgumentNullException(IdentifierLabel, "Unable to get OPC UA endpoint in environment variable"); - } - ushort OpcuaNamespaceIndex = ushort.Parse(Environment.GetEnvironmentVariable(NamespaceIndexLabel)); - MonitoredNodeId = new NodeId(OpcuaNodeIdentifier, OpcuaNamespaceIndex); - // Require certificate validation instead of automatically accepting certificates from server. - MonitoringClient client = new MonitoringClient(OpcuaServerDiscoveryURL); - Task HostBuilderTask = Task.Run(() => CreateHostBuilder(args).Build().Run()); - client.Run(); - return (int)client.ExitCodeValue; - } - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - - private static List GetServerDiscoveryUrls() - { - var values = new List(); - foreach (DictionaryEntry de in Environment.GetEnvironmentVariables()) - { - if (Regex.IsMatch(de.Key.ToString(), OpcuaDiscoveryUrlLabelPattern)) - { - values.Add(de.Value.ToString()); - } - } - return values; - } - } - - // gRPC Server which serves the latest value of the monitored Node. It assumes the value is integer type. - // TODO: support other value types. - public class OpcuaNodeService : OpcuaNode.OpcuaNode.OpcuaNodeBase - { - // Returns latest value of monitored Node - public override Task GetValue(ValueRequest request, ServerCallContext context) - { - int value; - if (MonitoringClient.LatestValue.HasValue) - { - value = MonitoringClient.LatestValue.Value; - Console.WriteLine("Sending a value of {0} for server at DiscoveryURL {1}", value, Program.OpcuaServerDiscoveryURL); - } - else - { - Console.WriteLine("No value available for server at DiscoveryURL {0}, sending value of 0", Program.OpcuaServerDiscoveryURL); - value = 0; - } - return Task.FromResult(new ValueResponse - { - Value = value, - OpcuaServer = Program.OpcuaServerDiscoveryURL - }); - } - } - - // OPC UA client that connects to the OPC UA server at the DiscoveryURL passed to it as an environment variable - // It uses the credentials mounted - public class MonitoringClient - { - // Timeout for reconnecting - readonly TimeSpan ReconnectPeriod = TimeSpan.FromSeconds(10); - // Run client indefinitely - readonly TimeSpan ClientRunTime = Timeout.InfiniteTimeSpan; - // The OPC UA session - Session Session; - SessionReconnectHandler ReconnectHandler; - // DiscoveryURL for OPC UA server - string DiscoveryURL; - // Value for automatically accepting the server's credentials - // May want to add option to configure it in the future - static readonly bool AutoAccept = false; - // Object for locking when modifying LatestValue - static object LatestValueLock = new object(); - // Expected location of credentials mounted as secrets - private const String ClientPKIPath = "/etc/opcua-certs/client-pki/"; - // Holds the ExitCode - public ExitCode ExitCodeValue { get; private set; } - private static int? _latestValue; - // Latest value of Node to which the the client is subscribed - public static int? LatestValue - { - get { lock (LatestValueLock) { return _latestValue; } } - private set { lock (LatestValueLock) { _latestValue = value; } } - } - - // OPC UA client that subscribes to a Node and stores it's latest value - public MonitoringClient(string _discoveryURL) - { - DiscoveryURL = _discoveryURL; - } - - // Get the subject name of the certificate mounted in the pod, else return substitute name and later use no security. - private X509Certificate2 GetCertificate(string certificateStorePath) - { - DirectoryInfo certificateStore = new DirectoryInfo(certificateStorePath); - FileInfo[] files = certificateStore.GetFiles("*.der"); - if (files.Length == 0) - { - throw new FileNotFoundException("No certificate mounted at path {0}", certificateStorePath); - } - else if (files.Length > 1) - { - Console.WriteLine("Error: found more than one der certificate in folder {0}. Using first one: {1}", certificateStorePath, files[0]); - } - FileInfo clientCertificate = files[0]; - X509Certificate2 certificate = new X509Certificate2(clientCertificate.FullName); - return certificate; - } - - // Builds an ApplicationConfiguration for the OPC UA Client, using the credentials mounted as secrets - // at `clientPKIPath,` which should already have the OPC UA Server's CA in the trusted folder. - // If no credentials were mounted, an insecure connection is made with the OPC UA Server (SecurityPolicy = None). - private ApplicationConfiguration CreateApplicationConfiguration() - { - CertificateIdentifier certificateIdentifier; - string clientCertificateFolder = ClientPKIPath + "own/certs/"; - try - { - X509Certificate2 certificate = GetCertificate(clientCertificateFolder); - certificateIdentifier = new CertificateIdentifier - { - StoreType = "Directory", - StorePath = ClientPKIPath + "own", - Certificate = certificate, - }; - } - catch (Exception e) - { - Console.WriteLine("Exception {0} thrown when trying to use application certificate mounted at {1}. Using no security.", e, clientCertificateFolder); - certificateIdentifier = new CertificateIdentifier(); - } - - ApplicationConfiguration config = new ApplicationConfiguration() - { - // Application name doesn't matter for certificate validation - ApplicationName = "AkriOPCUABroker", - ApplicationType = ApplicationType.Client, - // If ApplicationUri is not specified, one is automatically created with the format "urn::" - SecurityConfiguration = new SecurityConfiguration - { - ApplicationCertificate = certificateIdentifier, - TrustedIssuerCertificates = new CertificateTrustList - { - StoreType = "Directory", - StorePath = ClientPKIPath + "issuer", - }, - TrustedPeerCertificates = new CertificateTrustList - { - StoreType = "Directory", - StorePath = ClientPKIPath + "trusted", - }, - RejectedCertificateStore = new CertificateTrustList - { - StoreType = "Directory", - StorePath = ClientPKIPath + "rejected", - }, - NonceLength = 32, - AutoAcceptUntrustedCertificates = AutoAccept - }, - TransportConfigurations = new TransportConfigurationCollection(), - TransportQuotas = new TransportQuotas { OperationTimeout = (int)TimeSpan.FromMinutes(10).TotalMilliseconds }, - ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = (int)TimeSpan.FromMinutes(1).TotalMilliseconds } - }; - - return config; - } - - public void Run() - { - try - { - CreateAndRunClient().Wait(); - } - catch (Exception ex) - { - Console.WriteLine("Exception: {0}", ex.Message); - return; - } - - ManualResetEvent quitEvent = new ManualResetEvent(false); - Console.CancelKeyPress += (sender, eArgs) => - { - quitEvent.Set(); - eArgs.Cancel = true; - }; - - // wait for timeout or Ctrl-C - quitEvent.WaitOne(ClientRunTime); - - // return error conditions - if (Session.KeepAliveStopped) - { - ExitCodeValue = ExitCode.ErrorNoKeepAlive; - return; - } - - ExitCodeValue = ExitCode.Ok; - } - - private async Task CreateAndRunClient() - { - Console.WriteLine("1 - Create an Application Configuration."); - ExitCodeValue = ExitCode.ErrorCreateApplication; - ApplicationConfiguration config = this.CreateApplicationConfiguration(); - await config.Validate(ApplicationType.Client); - bool haveAppCertificate = config.SecurityConfiguration.ApplicationCertificate.Certificate != null; - ApplicationInstance application = new ApplicationInstance(config); - - if (haveAppCertificate) - { - config.ApplicationUri = X509Utils.GetApplicationUriFromCertificate(config.SecurityConfiguration.ApplicationCertificate.Certificate); - config.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_CertificateValidation); - } - else - { - // Check if any certificates were mounted and secure connection was desired - if (Directory.Exists(ClientPKIPath)) - { - throw new ArgumentException("Application certificates passed as secrets could not be used. Make sure application subject name is AkriClient, the certs are in der format, and the private key in pfx format"); - } - Console.WriteLine("Application certificates not mounted, using unsecure connection with Security Policy None"); - } - Console.WriteLine("Client is using a certificate with subject " + config.SecurityConfiguration.ApplicationCertificate.SubjectName); - Console.WriteLine("2 - Discover endpoints of {0}.", DiscoveryURL); - ExitCodeValue = ExitCode.ErrorDiscoverEndpoints; - EndpointDescription selectedEndpoint = CoreClientUtils.SelectEndpoint(DiscoveryURL, haveAppCertificate, (int)TimeSpan.FromSeconds(15).TotalMilliseconds); - Console.WriteLine(" Selected endpoint uses: {0}", - selectedEndpoint.SecurityPolicyUri.Substring(selectedEndpoint.SecurityPolicyUri.LastIndexOf('#') + 1)); - - Console.WriteLine("3 - Create a session with OPC UA server."); - ExitCodeValue = ExitCode.ErrorCreateSession; - var endpointConfiguration = EndpointConfiguration.Create(config); - var endpoint = new ConfiguredEndpoint(null, selectedEndpoint, endpointConfiguration); - Session = await Session.Create(config, endpoint, false, "Akri Client", (uint)TimeSpan.FromSeconds(60).TotalMilliseconds, new UserIdentity(new AnonymousIdentityToken()), null); - - // Register keep alive handler to monitor the status of the session - Session.KeepAlive += Client_KeepAlive; - - Console.WriteLine("4 - Browse the OPC UA server namespace."); - DoBrowse(); - - Console.WriteLine("5 - Create a subscription with publishing interval of 1 second."); - ExitCodeValue = ExitCode.ErrorCreateSubscription; - var subscription = new Subscription(Session.DefaultSubscription) { PublishingInterval = (int)TimeSpan.FromSeconds(1).TotalMilliseconds }; - - Console.WriteLine("6 - Add node {0} to the subscription.", Program.MonitoredNodeId.Identifier); - ExitCodeValue = ExitCode.ErrorMonitoredItem; - var monitoredNode = new MonitoredItem(subscription.DefaultItem) - { - DisplayName = Program.MonitoredNodeId.Identifier.ToString(), - StartNodeId = Program.MonitoredNodeId, - }; - monitoredNode.Notification += OnNotification; - subscription.AddItem(monitoredNode); - - Console.WriteLine("7 - Add the subscription to the session."); - // TODO: Find way to detect if passed improper NodeID, since if NodeID is not valid, no error is thrown by server - ExitCodeValue = ExitCode.ErrorAddSubscription; - Session.AddSubscription(subscription); - subscription.Create(); - - Console.WriteLine("8 - Running...Press Ctrl-C to exit..."); - ExitCodeValue = ExitCode.ErrorRunning; - } - - // Browse server address space - private void DoBrowse() - { - // TODO: Possibly extend this to search for NodeID of a Node given a display name - ExitCodeValue = ExitCode.ErrorBrowseNamespace; - ReferenceDescriptionCollection references; - Byte[] continuationPoint; - - references = Session.FetchReferences(ObjectIds.ObjectsFolder); - - Session.Browse( - null, - null, - ObjectIds.ObjectsFolder, - 0u, - BrowseDirection.Forward, - ReferenceTypeIds.HierarchicalReferences, - true, - (uint)NodeClass.Variable | (uint)NodeClass.Object | (uint)NodeClass.Method, - out continuationPoint, - out references); - - Console.WriteLine(" DisplayName, BrowseName, NodeClass"); - foreach (var rd in references) - { - Console.WriteLine(" {0}, {1}, {2} ", rd.DisplayName, rd.BrowseName, rd.NodeClass); - ReferenceDescriptionCollection nextRefs; - byte[] nextCp; - Session.Browse( - null, - null, - ExpandedNodeId.ToNodeId(rd.NodeId, Session.NamespaceUris), - 0u, - BrowseDirection.Forward, - ReferenceTypeIds.HierarchicalReferences, - true, - (uint)NodeClass.Variable | (uint)NodeClass.Object | (uint)NodeClass.Method, - out nextCp, - out nextRefs); - - foreach (var nextRd in nextRefs) - { - Console.WriteLine(" + {0}, {1}, {2} next", nextRd.DisplayName, nextRd.BrowseName, nextRd.NodeClass); - } - } - } - - // Monitor the health of the session and reconnect if needed - private void Client_KeepAlive(Session sender, KeepAliveEventArgs e) - { - if (e.Status != null && ServiceResult.IsNotGood(e.Status)) - { - Console.WriteLine("{0} {1}/{2}", e.Status, sender.OutstandingRequestCount, sender.DefunctRequestCount); - - if (ReconnectHandler == null) - { - Console.WriteLine("--- RECONNECTING ---"); - ReconnectHandler = new SessionReconnectHandler(); - ReconnectHandler.BeginReconnect(sender, ReconnectPeriod.Milliseconds, Client_ReconnectComplete); - } - } - } - - private void Client_ReconnectComplete(object sender, EventArgs e) - { - // ignore callbacks from discarded objects. - if (!Object.ReferenceEquals(sender, ReconnectHandler)) - { - return; - } - - Session = ReconnectHandler.Session; - ReconnectHandler.Dispose(); - ReconnectHandler = null; - - Console.WriteLine("--- RECONNECTED ---"); - } - - // Change latestValue when receive an updated value from the server - private void OnNotification(MonitoredItem item, MonitoredItemNotificationEventArgs e) - { - foreach (var value in item.DequeueValues()) - { - Console.WriteLine("{0}: {1}, {2}, {3}", item.DisplayName, value.Value, value.SourceTimestamp, value.StatusCode); - // OPC PLC server demo uses uint so need extra casting - MonitoringClient.LatestValue = (int)(uint)value.Value; - } - } - - private static void CertificateValidator_CertificateValidation(CertificateValidator validator, CertificateValidationEventArgs e) - { - if (e.Error.StatusCode == StatusCodes.BadCertificateUntrusted) - { - e.Accept = AutoAccept; - if (AutoAccept) - { - Console.WriteLine("Accepted Certificate: {0}", e.Certificate.Subject); - } - else - { - Console.WriteLine("Rejected Certificate: {0}", e.Certificate.Subject); - } - } - } - - } -} diff --git a/samples/brokers/opcua-monitoring-broker/Properties/launchSettings.json b/samples/brokers/opcua-monitoring-broker/Properties/launchSettings.json deleted file mode 100644 index 4175219a1..000000000 --- a/samples/brokers/opcua-monitoring-broker/Properties/launchSettings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "profiles": { - "OpcuaNodeMonitoring": { - "commandName": "Project", - "launchBrowser": false, - "applicationUrl": "http://localhost:5001", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/samples/brokers/opcua-monitoring-broker/README.md b/samples/brokers/opcua-monitoring-broker/README.md deleted file mode 100644 index bec652b1f..000000000 --- a/samples/brokers/opcua-monitoring-broker/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# OPC UA Monitoring Broker -Sample broker for for Akri's [OPC UA Thermometer demo](https://docs.akri.sh/demos/opc-thermometer-demo). Contains an OPC -UA Client that will subscribe to an OPC UA Server Node with a specific identifier and namespace index. It then serves the -value of the Node (or Variable) over gRPC for an [anomaly detection web application](../../apps/anomaly-detection-app) -to consume. - -## Running -1. Install .NET according to [.NET instructions](https://docs.microsoft.com/dotnet/install/linux-ubuntu) -1. Build -```sh -cd ./samples/brokers/opcua-monitoring-broker -dotnet build -``` -1. Run, passing in the OPC UA Discovery URL for the OPC UA Server it should connect to and the identifier and namespace - index of the OPC UA Node to monitor. -```sh -IDENTIFIER="Thermometer_Temperature" NAMESPACE_INDEX="2" OPCUA_DISCOVERY_URL_ABCDEF="opc.tcp://10.2.3.4:4556/Some/Path" dotnet run -``` diff --git a/samples/brokers/opcua-monitoring-broker/Startup.cs b/samples/brokers/opcua-monitoring-broker/Startup.cs deleted file mode 100644 index ed1c7d5b6..000000000 --- a/samples/brokers/opcua-monitoring-broker/Startup.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace OpcuaNodeMonitoring -{ - public class Startup - { - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) - { - services.AddGrpc(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapGrpcService(); - - endpoints.MapGet("/", async context => - { - await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); - }); - }); - } - } -} diff --git a/samples/brokers/opcua-monitoring-broker/appsettings.Development.json b/samples/brokers/opcua-monitoring-broker/appsettings.Development.json deleted file mode 100644 index eec2e4c53..000000000 --- a/samples/brokers/opcua-monitoring-broker/appsettings.Development.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Grpc": "Information", - "Microsoft": "Information" - } - } -} diff --git a/samples/brokers/opcua-monitoring-broker/appsettings.json b/samples/brokers/opcua-monitoring-broker/appsettings.json deleted file mode 100644 index f5ef230f6..000000000 --- a/samples/brokers/opcua-monitoring-broker/appsettings.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "AllowedHosts": "*", - "Kestrel": { - "EndpointDefaults": { - "Protocols": "Http2" - } - } -} diff --git a/samples/brokers/opcua-monitoring-broker/opcua-monitoring-broker.csproj b/samples/brokers/opcua-monitoring-broker/opcua-monitoring-broker.csproj deleted file mode 100644 index 3e51b6c15..000000000 --- a/samples/brokers/opcua-monitoring-broker/opcua-monitoring-broker.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - net6.0 - - - - - - - - - - - - - diff --git a/samples/brokers/opcua-monitoring-broker/opcua_node.proto b/samples/brokers/opcua-monitoring-broker/opcua_node.proto deleted file mode 100644 index 3e681c01d..000000000 --- a/samples/brokers/opcua-monitoring-broker/opcua_node.proto +++ /dev/null @@ -1,21 +0,0 @@ -/* - * This file is used to generate gRPC code for sending and requesting the value - * of an OPC UA Variable (with a specific NodeID). If this file is changed, the - * gRPC code needs to be regenerated for the anomaly-detection-app (by running - * ./generategrpc.sh from within the akri/samples/apps/anomaly-detection-app - * directory) and all other sample applications that use this proto file. - */ -syntax = "proto3"; - -option csharp_namespace = "OpcuaNode"; - -package OpcuaNode; - -service OpcuaNode { rpc GetValue(ValueRequest) returns (ValueResponse); } - -message ValueRequest {} - -message ValueResponse { - int32 value = 1; - string opcua_server = 2; -} \ No newline at end of file diff --git a/samples/brokers/udev-video-broker/Cargo.toml b/samples/brokers/udev-video-broker/Cargo.toml deleted file mode 100644 index ea2e9b02d..000000000 --- a/samples/brokers/udev-video-broker/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "udev-video-broker" -authors.workspace = true -version.workspace = true -edition.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true -rust-version.workspace = true - -[dependencies] -akri-shared = { path = "../../../shared" } -env_logger = "0.11.8" -lazy_static = "1.4" -log = "0.4.3" -prometheus = { version = "0.12.0", features = ["process"] } -prost = "0.12" -regex = "1" -tokio = { version = "1.0.1", features = ["time", "fs", "macros", "signal"] } -tonic = "0.10" -rscam = "0.5.5" - -[build-dependencies] -tonic-build = "0.10" diff --git a/samples/brokers/udev-video-broker/README.md b/samples/brokers/udev-video-broker/README.md deleted file mode 100644 index 186aae4fa..000000000 --- a/samples/brokers/udev-video-broker/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Udev USB Video Broker -Sample broker for for Akri's [end to end demo](https://docs.akri.sh/demos/usb-camera-demo). It pulls video frames from -the USB camera with device node specified by the environment variable named with `UDEV_DEVNODE_<6 digit digest>`. Then, it serves these frames over a gRPC interface. The [streaming -application](../../apps/video-streaming-app) provides an example streaming service that displays frames served by this -broker. - -## Running -1. Install Rust and udev dependencies - ```sh - ./build/setup.sh - ``` -1. Build and run, connecting to the USB camera at `/dev/video0` - ```sh - cd akri/samples/brokers/udev-video-broker - UDEV_DEVNODE_123456=/dev/video0 cargo run - ``` \ No newline at end of file diff --git a/samples/brokers/udev-video-broker/build.rs b/samples/brokers/udev-video-broker/build.rs deleted file mode 100644 index 178dc01d7..000000000 --- a/samples/brokers/udev-video-broker/build.rs +++ /dev/null @@ -1,7 +0,0 @@ -fn main() { - tonic_build::configure() - .build_client(true) - .out_dir("./src/util") - .compile(&["./proto/camera.proto"], &["./proto"]) - .expect("failed to compile protos"); -} diff --git a/samples/brokers/udev-video-broker/proto/camera.proto b/samples/brokers/udev-video-broker/proto/camera.proto deleted file mode 100644 index 97454c089..000000000 --- a/samples/brokers/udev-video-broker/proto/camera.proto +++ /dev/null @@ -1,18 +0,0 @@ -syntax = "proto3"; - -option csharp_namespace = "Camera"; - -package camera; - -service Camera { - rpc GetFrame (NotifyRequest) returns (NotifyResponse); -} - -message NotifyRequest { -} - -message NotifyResponse { - bytes frame = 1; - string camera = 2; -} - diff --git a/samples/brokers/udev-video-broker/src/main.rs b/samples/brokers/udev-video-broker/src/main.rs deleted file mode 100644 index 8dedcfe2d..000000000 --- a/samples/brokers/udev-video-broker/src/main.rs +++ /dev/null @@ -1,101 +0,0 @@ -mod util; -#[macro_use] -extern crate lazy_static; -use akri_shared::{ - akri::{API_NAMESPACE, metrics::run_metrics_server}, - os::env_var::{ActualEnvVarQuery, EnvVarQuery}, -}; -use log::{info, trace}; -use prometheus::IntCounter; -use regex::Regex; -use tokio::signal; -use util::{camera_capturer, camera_service}; - -lazy_static! { - pub static ref FRAME_COUNT_METRIC: IntCounter = - prometheus::register_int_counter!("akri_frame_count", "Akri Frame Count") - .expect("akri_frame_count cannot be created"); -} - -/// regular expression pattern of devnode environment variable id -pub const UDEV_DEVNODE_LABEL_ID_PATTERN: &str = "UDEV_DEVNODE_[A-F0-9]{6,6}$"; - -#[tokio::main] -async fn main() -> Result<(), Box> { - println!("{API_NAMESPACE} udev_broker ... env_logger::init"); - env_logger::try_init().unwrap(); - println!("{API_NAMESPACE} udev_broker ... env_logger::init finished"); - info!("{API_NAMESPACE} Udev Broker logging started"); - - tokio::spawn(async move { - run_metrics_server().await.unwrap(); - }); - - let env_var_query = ActualEnvVarQuery {}; - let devnode = get_video_devnode(&env_var_query); - - let camera_capturer = camera_capturer::build_and_start_camera_capturer(&devnode); - camera_service::serve(&devnode, camera_capturer) - .await - .unwrap(); - - trace!("Waiting for ctrl C shutdown signal"); - // Wait for exit signal - signal::ctrl_c().await?; - - trace!("Udev broker ending"); - Ok(()) -} - -/// This gets video devnode from environment variable else panics. -fn get_video_devnode(env_var_query: &impl EnvVarQuery) -> String { - trace!("get_video_devnode - getting devnode"); - - // query UDEV_DEVNODE_LABEL_ID prefix and use the first one found as device_devnode - lazy_static! { - static ref RE: Regex = Regex::new(UDEV_DEVNODE_LABEL_ID_PATTERN).unwrap(); - } - let device_devnodes = env_var_query - .get_env_vars() - .iter() - .filter_map(|(n, v)| { - if RE.is_match(n) { - Some(v.clone()) - } else { - None - } - }) - .collect::>(); - let device_devnode = device_devnodes - .first() - .expect("devnode not set in environment variable"); - - trace!("get_video_devnode - found devnode {device_devnode}"); - device_devnode.to_string() -} - -#[cfg(test)] -mod tests { - use super::*; - use akri_shared::os::env_var::MockEnvVarQuery; - - #[test] - fn test_get_devnode() { - let _ = env_logger::builder().is_test(true).try_init(); - - let mut mock_query = MockEnvVarQuery::new(); - const MOCK_DEVICE_PATH: &str = "/dev/video0"; - const MOCK_DEVICE_ENV_VAR_NAME: &str = "UDEV_DEVNODE_123456"; - mock_query - .expect_get_env_vars() - .times(1) - .returning(move || { - vec![( - MOCK_DEVICE_ENV_VAR_NAME.to_string(), - MOCK_DEVICE_PATH.to_string(), - )] - }); - - assert_eq!(MOCK_DEVICE_PATH.to_string(), get_video_devnode(&mock_query)); - } -} diff --git a/samples/brokers/udev-video-broker/src/util/camera.rs b/samples/brokers/udev-video-broker/src/util/camera.rs deleted file mode 100644 index a1ee7fe5d..000000000 --- a/samples/brokers/udev-video-broker/src/util/camera.rs +++ /dev/null @@ -1,279 +0,0 @@ -// This file is @generated by prost-build. -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct NotifyRequest {} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct NotifyResponse { - #[prost(bytes = "vec", tag = "1")] - pub frame: ::prost::alloc::vec::Vec, - #[prost(string, tag = "2")] - pub camera: ::prost::alloc::string::String, -} -/// Generated client implementations. -pub mod camera_client { - #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] - use tonic::codegen::http::Uri; - use tonic::codegen::*; - #[derive(Debug, Clone)] - pub struct CameraClient { - inner: tonic::client::Grpc, - } - impl CameraClient { - /// Attempt to create a new client by connecting to a given endpoint. - pub async fn connect(dst: D) -> Result - where - D: TryInto, - D::Error: Into, - { - let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; - Ok(Self::new(conn)) - } - } - impl CameraClient - where - T: tonic::client::GrpcService, - T::Error: Into, - T::ResponseBody: Body + Send + 'static, - ::Error: Into + Send, - { - pub fn new(inner: T) -> Self { - let inner = tonic::client::Grpc::new(inner); - Self { inner } - } - pub fn with_origin(inner: T, origin: Uri) -> Self { - let inner = tonic::client::Grpc::with_origin(inner, origin); - Self { inner } - } - pub fn with_interceptor( - inner: T, - interceptor: F, - ) -> CameraClient> - where - F: tonic::service::Interceptor, - T::ResponseBody: Default, - T: tonic::codegen::Service< - http::Request, - Response = http::Response< - >::ResponseBody, - >, - >, - >>::Error: - Into + Send + Sync, - { - CameraClient::new(InterceptedService::new(inner, interceptor)) - } - /// Compress requests with the given encoding. - /// - /// This requires the server to support it otherwise it might respond with an - /// error. - #[must_use] - pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { - self.inner = self.inner.send_compressed(encoding); - self - } - /// Enable decompressing responses. - #[must_use] - pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { - self.inner = self.inner.accept_compressed(encoding); - self - } - /// Limits the maximum size of a decoded message. - /// - /// Default: `4MB` - #[must_use] - pub fn max_decoding_message_size(mut self, limit: usize) -> Self { - self.inner = self.inner.max_decoding_message_size(limit); - self - } - /// Limits the maximum size of an encoded message. - /// - /// Default: `usize::MAX` - #[must_use] - pub fn max_encoding_message_size(mut self, limit: usize) -> Self { - self.inner = self.inner.max_encoding_message_size(limit); - self - } - pub async fn get_frame( - &mut self, - request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { - self.inner.ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/camera.Camera/GetFrame"); - let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("camera.Camera", "GetFrame")); - self.inner.unary(req, path, codec).await - } - } -} -/// Generated server implementations. -pub mod camera_server { - #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] - use tonic::codegen::*; - /// Generated trait containing gRPC methods that should be implemented for use with CameraServer. - #[async_trait] - pub trait Camera: Send + Sync + 'static { - async fn get_frame( - &self, - request: tonic::Request, - ) -> std::result::Result, tonic::Status>; - } - #[derive(Debug)] - pub struct CameraServer { - inner: _Inner, - accept_compression_encodings: EnabledCompressionEncodings, - send_compression_encodings: EnabledCompressionEncodings, - max_decoding_message_size: Option, - max_encoding_message_size: Option, - } - struct _Inner(Arc); - impl CameraServer { - pub fn new(inner: T) -> Self { - Self::from_arc(Arc::new(inner)) - } - pub fn from_arc(inner: Arc) -> Self { - let inner = _Inner(inner); - Self { - inner, - accept_compression_encodings: Default::default(), - send_compression_encodings: Default::default(), - max_decoding_message_size: None, - max_encoding_message_size: None, - } - } - pub fn with_interceptor(inner: T, interceptor: F) -> InterceptedService - where - F: tonic::service::Interceptor, - { - InterceptedService::new(Self::new(inner), interceptor) - } - /// Enable decompressing requests with the given encoding. - #[must_use] - pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { - self.accept_compression_encodings.enable(encoding); - self - } - /// Compress responses with the given encoding, if the client supports it. - #[must_use] - pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { - self.send_compression_encodings.enable(encoding); - self - } - /// Limits the maximum size of a decoded message. - /// - /// Default: `4MB` - #[must_use] - pub fn max_decoding_message_size(mut self, limit: usize) -> Self { - self.max_decoding_message_size = Some(limit); - self - } - /// Limits the maximum size of an encoded message. - /// - /// Default: `usize::MAX` - #[must_use] - pub fn max_encoding_message_size(mut self, limit: usize) -> Self { - self.max_encoding_message_size = Some(limit); - self - } - } - impl tonic::codegen::Service> for CameraServer - where - T: Camera, - B: Body + Send + 'static, - B::Error: Into + Send + 'static, - { - type Response = http::Response; - type Error = std::convert::Infallible; - type Future = BoxFuture; - fn poll_ready( - &mut self, - _cx: &mut Context<'_>, - ) -> Poll> { - Poll::Ready(Ok(())) - } - fn call(&mut self, req: http::Request) -> Self::Future { - let inner = self.inner.clone(); - match req.uri().path() { - "/camera.Camera/GetFrame" => { - #[allow(non_camel_case_types)] - struct GetFrameSvc(pub Arc); - impl tonic::server::UnaryService for GetFrameSvc { - type Response = super::NotifyResponse; - type Future = BoxFuture, tonic::Status>; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = - async move { ::get_frame(&inner, request).await }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let inner = inner.0; - let method = GetFrameSvc(inner); - let codec = tonic::codec::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - _ => Box::pin(async move { - Ok(http::Response::builder() - .status(200) - .header("grpc-status", "12") - .header("content-type", "application/grpc") - .body(empty_body()) - .unwrap()) - }), - } - } - } - impl Clone for CameraServer { - fn clone(&self) -> Self { - let inner = self.inner.clone(); - Self { - inner, - accept_compression_encodings: self.accept_compression_encodings, - send_compression_encodings: self.send_compression_encodings, - max_decoding_message_size: self.max_decoding_message_size, - max_encoding_message_size: self.max_encoding_message_size, - } - } - } - impl Clone for _Inner { - fn clone(&self) -> Self { - Self(Arc::clone(&self.0)) - } - } - impl std::fmt::Debug for _Inner { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self.0) - } - } - impl tonic::server::NamedService for CameraServer { - const NAME: &'static str = "camera.Camera"; - } -} diff --git a/samples/brokers/udev-video-broker/src/util/camera_capturer.rs b/samples/brokers/udev-video-broker/src/util/camera_capturer.rs deleted file mode 100644 index 43a7469bd..000000000 --- a/samples/brokers/udev-video-broker/src/util/camera_capturer.rs +++ /dev/null @@ -1,583 +0,0 @@ -use akri_shared::os::env_var::{ActualEnvVarQuery, EnvVarQuery}; -use log::trace; -use rscam::Camera as RsCamera; -use rscam::Config; - -/// Frames per second environment variable id -const FRAMES_PER_SECOND: &str = "FRAMES_PER_SECOND"; -/// Resolution width environment variable id -const RESOLUTION_WIDTH: &str = "RESOLUTION_WIDTH"; -/// Resolution height environment variable id -const RESOLUTION_HEIGHT: &str = "RESOLUTION_HEIGHT"; -/// Image format environment variable id -const FORMAT: &str = "FORMAT"; -/// Default is 1 fps -const DEFAULT_FRAMES_PER_SECOND: u32 = 10; -/// Default resolution width, which is also the default for rscam. -const DEFAULT_RESOLUTION_WIDTH: u32 = 640; -/// Default resolution height, which is also the default for rscam. -const DEFAULT_RESOLUTION_HEIGHT: u32 = 480; -/// Default format, which is also the default for rscam. -const DEFAULT_FORMAT: &str = "MJPG"; - -pub type Resolution = (u32, u32); -pub type Interval = (u32, u32); - -/// This builds a rscamera from a specified devnode. Then, it gets desired format, resolution, and interval/fps settings from environment variables. -/// If the environment variables are not set, it will try to use default settings. If the camera does not support the defaults, the first supported setting will be used. -/// Finally, its starts the camera capturer with the selected settings and returns this camera. -pub fn build_and_start_camera_capturer(devnode: &str) -> RsCamera { - trace!("build_and_start_camera_capturer - entered"); - let mut camera_capturer = RsCamera::new(devnode).unwrap(); - let env_var_query = ActualEnvVarQuery {}; - // Get camera formats and convert them from [u8] to String so can compare them with env and default format - let format_options: Vec = camera_capturer - .formats() - .map(|wformat| { - std::str::from_utf8(&wformat.unwrap().format) - .unwrap() - .to_string() - }) - .collect(); - let format_string = get_format(&env_var_query, format_options); - let format = &format_string.as_bytes(); - let resolution_info = camera_capturer.resolutions(format).unwrap(); - let resolution = get_resolution(&env_var_query, resolution_info); - let interval_info = camera_capturer.intervals(format, resolution).unwrap(); - let interval = get_interval(&env_var_query, interval_info); - trace!("build_and_start_camera_capturer - before starting camera"); - camera_capturer - .start(&Config { - interval, - resolution, - format, - ..Default::default() - }) - .unwrap(); - trace!("build_and_start_camera_capturer - after starting camera"); - camera_capturer -} - -/// This gets the image format from an environment variable. If not set, it will use default. If default is not supported, uses first supported format. -fn get_format(env_var_query: &impl EnvVarQuery, format_options: Vec) -> String { - let format_to_find = match env_var_query.get_env_var(FORMAT) { - Ok(format) => format, - Err(_) => { - trace!("get_format - format not set ... trying to use {DEFAULT_FORMAT:?}"); - DEFAULT_FORMAT.to_string() - } - }; - - if !format_options.contains(&format_to_find) { - if !format_options.contains(&DEFAULT_FORMAT.to_string()) { - trace!( - "get_format - camera does not support {:?} format, using {:?} format", - DEFAULT_FORMAT, format_options[0] - ); - format_options[0].clone() - } else { - trace!("get_format - using default {DEFAULT_FORMAT:?} format"); - DEFAULT_FORMAT.to_string() - } - } else { - trace!("get_format - using {format_to_find:?} format"); - format_to_find - } -} - -/// This gets the desired interval/frames per second from an environment variable. If not set, it will use default. If default is not supported, uses first supported interval. -fn get_interval(env_var_query: &impl EnvVarQuery, interval_info: rscam::IntervalInfo) -> Interval { - let fps_to_validate = match env_var_query.get_env_var(FRAMES_PER_SECOND) { - Ok(res) => res.parse().unwrap(), - Err(_) => { - trace!("main - frames per second not set ... trying to use 10"); - DEFAULT_FRAMES_PER_SECOND - } - }; - let interval_to_validate = (1, fps_to_validate); - - let interval_options = get_interval_options(interval_info); - - // If the camera does not support env var interval or default, use first interval option - if !interval_options.contains(&interval_to_validate) { - trace!( - "get_interval - camera does not support {:?} interval, using {:?} interval", - interval_to_validate, interval_options[0] - ); - interval_options[0] - } else { - trace!("get_interval - using {interval_to_validate:?} interval"); - interval_to_validate - } -} - -/// This gets the intervals supported by the camera -fn get_interval_options(interval_info: rscam::IntervalInfo) -> Vec { - match interval_info { - rscam::IntervalInfo::Discretes(interval_options) => interval_options, - rscam::IntervalInfo::Stepwise { min, max, step } => { - let mut interval_options: Vec<(u32, u32)> = Vec::new(); - let width_step = step.0; - let height_step = step.1; - let min_width = min.0; - let min_height = min.1; - let max_height = max.1; - let steps = (max_height - min_height) / height_step; - for step_num in 0..steps { - let curr_width = min_width + step_num * width_step; - let curr_height = min_height + step_num * height_step; - interval_options.push((curr_width, curr_height)); - } - interval_options - } - } -} - -/// This calls a function to get the desired resolution from an environment variable. If not set, it will use default. If default is not supported, uses first supported resolution. -fn get_resolution( - env_var_query: &impl EnvVarQuery, - resolution_info: rscam::ResolutionInfo, -) -> Resolution { - let env_var_resolution = get_env_var_resolution(env_var_query); - - let resolution_to_validate = match env_var_resolution { - Some(res) => res, - None => (DEFAULT_RESOLUTION_WIDTH, DEFAULT_RESOLUTION_HEIGHT), - }; - - let resolution_options = get_resolution_options(resolution_info); - - // If the camera does not support env var resolution or default, use first resolution - if !resolution_options.contains(&resolution_to_validate) { - trace!( - "get_resolution - camera does not support {:?} resolution, using {:?} resolution", - resolution_to_validate, resolution_options[0] - ); - resolution_options[0] - } else { - trace!("get_resolution - using resolution {resolution_to_validate:?}"); - resolution_to_validate - } -} - -/// This gets the desired resolution from an environment variable else returns None. -fn get_env_var_resolution(env_var_query: &impl EnvVarQuery) -> Option { - let width = match env_var_query.get_env_var(RESOLUTION_WIDTH) { - Ok(res) => res.parse().unwrap(), - Err(_) => { - trace!("get_env_var_resolution - resolution width not set"); - return None; - } - }; - let height = match env_var_query.get_env_var(RESOLUTION_HEIGHT) { - Ok(res) => res.parse().unwrap(), - Err(_) => { - trace!("get_env_var_resolution - resolution height not set"); - return None; - } - }; - Some((width, height)) -} - -/// This gets the resolutions supported by the camera. -fn get_resolution_options(resolution_info: rscam::ResolutionInfo) -> Vec { - match resolution_info { - rscam::ResolutionInfo::Discretes(resolution_options) => resolution_options, - rscam::ResolutionInfo::Stepwise { min, max, step } => { - let mut resolution_options: Vec<(u32, u32)> = Vec::new(); - let width_step = step.0; - let height_step = step.1; - let min_width = min.0; - let min_height = min.1; - let max_width = max.0; - let steps = (max_width - min_width) / width_step; - for step_num in 0..steps { - let curr_width = min_width + step_num * width_step; - let curr_height = min_height + step_num * height_step; - resolution_options.push((curr_width, curr_height)); - } - resolution_options - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use akri_shared::os::env_var::MockEnvVarQuery; - use std::env::VarError; - - #[test] - fn test_get_format() { - let _ = env_logger::builder().is_test(true).try_init(); - - let mut mock_query = MockEnvVarQuery::new(); - const MOCK_FORMAT: &str = "YUYV"; - let mut format_options: Vec = - vec!["OTHER".to_string(), "YUYV".to_string(), "MJPG".to_string()]; - - // Test when env var set and camera supports that format - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == FORMAT) - .returning(move |_| Ok(MOCK_FORMAT.to_string())); - assert_eq!( - "YUYV".to_string(), - get_format(&mock_query, format_options.clone()) - ); - - // Test when env var not set but camera supports default - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == FORMAT) - .returning(move |_| Err(VarError::NotPresent)); - - assert_eq!( - "MJPG".to_string(), - get_format(&mock_query, format_options.clone()) - ); - - // Test when env var not set and camera does not support default - format_options.pop(); - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == FORMAT) - .returning(move |_| Err(VarError::NotPresent)); - assert_eq!("OTHER".to_string(), get_format(&mock_query, format_options)); - - // Test when env var set and camera does not support format nor the default one - let minimal_format_options: Vec = vec!["OTHER".to_string(), "BLAH".to_string()]; - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == FORMAT) - .returning(move |_| Ok(MOCK_FORMAT.to_string())); - // Should choose first one - assert_eq!( - "OTHER".to_string(), - get_format(&mock_query, minimal_format_options) - ); - } - - #[test] - fn test_get_interval_stepwise() { - let _ = env_logger::builder().is_test(true).try_init(); - - let mut mock_query = MockEnvVarQuery::new(); - const MOCK_INTERVAL: &str = "3"; - - // Test when env var set and camera supports that interval - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == FRAMES_PER_SECOND) - .returning(move |_| Ok(MOCK_INTERVAL.to_string())); - assert_eq!( - (1, 3), - get_interval( - &mock_query, - rscam::IntervalInfo::Stepwise { - min: (1, 1), - max: (1, 30), - step: (0, 2), - } - ) - ); - - // Test when env var not set but camera supports default - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == FRAMES_PER_SECOND) - .returning(move |_| Err(VarError::NotPresent)); - - assert_eq!( - (1, 10), - get_interval( - &mock_query, - rscam::IntervalInfo::Stepwise { - min: (1, 1), - max: (1, 30), - step: (0, 9), - } - ) - ); - - // Test when env var not set and camera does not support default - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == FRAMES_PER_SECOND) - .returning(move |_| Err(VarError::NotPresent)); - - assert_eq!( - // returns slowest interval - (1, 1), - get_interval( - &mock_query, - rscam::IntervalInfo::Stepwise { - min: (1, 1), - max: (1, 30), - step: (0, 2), - } - ) - ); - // Test when env var set and camera does not support that interval nor the default one - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == FRAMES_PER_SECOND) - .returning(move |_| Ok(MOCK_INTERVAL.to_string())); - assert_eq!( - (1, 1), - get_interval( - &mock_query, - rscam::IntervalInfo::Stepwise { - min: (1, 1), - max: (1, 30), - step: (0, 5), - } - ) - ); - } - - #[test] - fn test_get_interval_discrete() { - let _ = env_logger::builder().is_test(true).try_init(); - - let mut mock_query = MockEnvVarQuery::new(); - const MOCK_INTERVAL: &str = "3"; - // Test when env var set and camera supports that interval - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == FRAMES_PER_SECOND) - .returning(move |_| Ok(MOCK_INTERVAL.to_string())); - assert_eq!( - (1, 3), - get_interval( - &mock_query, - rscam::IntervalInfo::Discretes(vec![(1, 1), (1, 3), (1, 5)]) - ) - ); - - // Test when env var not set but camera supports default - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == FRAMES_PER_SECOND) - .returning(move |_| Err(VarError::NotPresent)); - - assert_eq!( - (1, 10), - get_interval( - &mock_query, - rscam::IntervalInfo::Discretes(vec![(1, 1), (1, 3), (1, 10)]) - ) - ); - - // Test when env var not set and camera does not support default - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == FRAMES_PER_SECOND) - .returning(move |_| Err(VarError::NotPresent)); - - assert_eq!( - // returns slowest interval - (1, 1), - get_interval( - &mock_query, - rscam::IntervalInfo::Discretes(vec![(1, 1), (1, 3), (1, 5)]) - ) - ); - - // Test when env var set and camera does not support that interval nor the default one - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == FRAMES_PER_SECOND) - .returning(move |_| Ok(MOCK_INTERVAL.to_string())); - assert_eq!( - (1, 1), - get_interval( - &mock_query, - rscam::IntervalInfo::Discretes(vec![(1, 1), (1, 2), (1, 5)]) - ) - ); - } - - #[test] - fn test_get_resolution_stepwise() { - let _ = env_logger::builder().is_test(true).try_init(); - - let mut mock_query = MockEnvVarQuery::new(); - const MOCK_RESOLUTION_WIDTH: &str = "424"; - const MOCK_RESOLUTION_HEIGHT: &str = "240"; - - // Test when env var set and camera supports that interval - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == RESOLUTION_WIDTH) - .returning(move |_| Ok(MOCK_RESOLUTION_WIDTH.to_string())); - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == RESOLUTION_HEIGHT) - .returning(move |_| Ok(MOCK_RESOLUTION_HEIGHT.to_string())); - assert_eq!( - (424, 240), - get_resolution( - &mock_query, - rscam::ResolutionInfo::Stepwise { - min: (224, 140), - max: (1280, 800), - step: (200, 100), - } - ) - ); - - // Test when env var not set but camera supports default - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == RESOLUTION_WIDTH) - .returning(move |_| Err(VarError::NotPresent)); - assert_eq!( - (DEFAULT_RESOLUTION_WIDTH, DEFAULT_RESOLUTION_HEIGHT), // (640, 480) - get_resolution( - &mock_query, - rscam::ResolutionInfo::Stepwise { - min: (440, 280), - max: (1280, 800), - step: (200, 200), - } - ) - ); - - // Test when env var not set and camera does not support default - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == RESOLUTION_WIDTH) - .returning(move |_| Err(VarError::NotPresent)); - assert_eq!( - (160, 120), - get_resolution( - &mock_query, - rscam::ResolutionInfo::Stepwise { - min: (160, 120), - max: (1280, 800), - step: (100, 100), - } - ) - ); - - // Test when env var set and camera does not support that interval nor the default one - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == RESOLUTION_WIDTH) - .returning(move |_| Ok(MOCK_RESOLUTION_WIDTH.to_string())); - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == RESOLUTION_HEIGHT) - .returning(move |_| Ok(MOCK_RESOLUTION_HEIGHT.to_string())); - assert_eq!( - (160, 120), - get_resolution( - &mock_query, - rscam::ResolutionInfo::Stepwise { - min: (160, 120), - max: (1280, 800), - step: (100, 100), - } - ) - ); - } - - #[test] - fn test_get_resolution_discrete() { - let _ = env_logger::builder().is_test(true).try_init(); - - let mut mock_query = MockEnvVarQuery::new(); - const MOCK_RESOLUTION_WIDTH: &str = "424"; - const MOCK_RESOLUTION_HEIGHT: &str = "240"; - - // Test when env var set and camera supports that interval - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == RESOLUTION_WIDTH) - .returning(move |_| Ok(MOCK_RESOLUTION_WIDTH.to_string())); - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == RESOLUTION_HEIGHT) - .returning(move |_| Ok(MOCK_RESOLUTION_HEIGHT.to_string())); - assert_eq!( - (424, 240), - get_resolution( - &mock_query, - rscam::ResolutionInfo::Discretes(vec!((200, 100), (424, 240), (1000, 800))) - ) - ); - - // Test when env var not set but camera supports default - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == RESOLUTION_WIDTH) - .returning(move |_| Err(VarError::NotPresent)); - assert_eq!( - (DEFAULT_RESOLUTION_WIDTH, DEFAULT_RESOLUTION_HEIGHT), // (640, 480) - get_resolution( - &mock_query, - rscam::ResolutionInfo::Discretes(vec!( - (200, 100), - (424, 240), - (640, 480), - (1000, 800) - )) - ) - ); - - // Test when env var not set and camera does not support default - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == RESOLUTION_WIDTH) - .returning(move |_| Err(VarError::NotPresent)); - assert_eq!( - (200, 100), - get_resolution( - &mock_query, - rscam::ResolutionInfo::Discretes(vec!((200, 100), (450, 240), (1000, 800))) - ) - ); - - // Test when env var set and camera does not support that interval nor the default one - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == RESOLUTION_WIDTH) - .returning(move |_| Ok(MOCK_RESOLUTION_WIDTH.to_string())); - mock_query - .expect_get_env_var() - .times(1) - .withf(move |name: &str| name == RESOLUTION_HEIGHT) - .returning(move |_| Ok(MOCK_RESOLUTION_HEIGHT.to_string())); - assert_eq!( - (200, 100), - get_resolution( - &mock_query, - rscam::ResolutionInfo::Discretes(vec!((200, 100), (500, 250), (1000, 800))) - ) - ); - } -} diff --git a/samples/brokers/udev-video-broker/src/util/camera_service.rs b/samples/brokers/udev-video-broker/src/util/camera_service.rs deleted file mode 100644 index 0bb059a6c..000000000 --- a/samples/brokers/udev-video-broker/src/util/camera_service.rs +++ /dev/null @@ -1,110 +0,0 @@ -use super::super::FRAME_COUNT_METRIC; -use super::camera::{ - NotifyRequest, NotifyResponse, - camera_client::CameraClient, - camera_server::{Camera, CameraServer}, -}; -use log::{info, trace}; -use rscam::Camera as RsCamera; -use std::{ - net::SocketAddr, - time::{Duration, SystemTime, UNIX_EPOCH}, -}; - -pub const CAMERA_SERVICE_SERVER_ADDRESS: &str = "0.0.0.0"; -pub const CAMERA_SERVICE_TEST_LOCALHOST: &str = "127.0.0.1"; -pub const CAMERA_SERVICE_PORT: &str = "8083"; - -/// gRPC service that serves frames from camera at `devnode` on request. -pub struct CameraService { - /// v4l2 wrapper for grabbing frames from udev camera - camera_capturer: RsCamera, - /// device node of camera (ie /dev/video0) - devnode: String, -} - -#[tonic::async_trait] -impl Camera for CameraService { - /// This gets a frame from the RsCamera and returns it. - async fn get_frame( - &self, - _request: tonic::Request, - ) -> Result, tonic::Status> { - trace!("CameraService.get_frame grpc request"); - FRAME_COUNT_METRIC.inc(); - Ok(tonic::Response::new(NotifyResponse { - frame: { - let frame = self.camera_capturer.capture().unwrap(); - (frame[..]).to_vec() - }, - camera: self.devnode.clone(), - })) - } -} - -/// This creates camera server -pub async fn serve(devnode: &str, camera_capturer: RsCamera) -> Result<(), String> { - info!("Entered serve for camera service"); - let camera_service = CameraService { - camera_capturer, - devnode: devnode.to_string(), - }; - let service = CameraServer::new(camera_service); - - let addr_str = format!("{CAMERA_SERVICE_SERVER_ADDRESS}:{CAMERA_SERVICE_PORT}"); - let addr: SocketAddr = match addr_str.parse() { - Ok(sock) => sock, - Err(e) => { - return Err(format!("Unable to parse socket: {e:?}")); - } - }; - - tokio::task::spawn(async move { - trace!("Entered Server::builder task (addr: {})", &addr); - tonic::transport::Server::builder() - .add_service(service) - .serve(addr) - .await - .expect("couldn't build server"); - trace!("Exit Server::builder task"); - }); - - trace!("Wait for server to start up by polling its existence"); - // Test that server is running, trying for at most 10 seconds - // Similar to grpc.timeout, which is yet to be implemented for tonic - // See issue: https://github.com/hyperium/tonic/issues/75 - let mut connected = false; - let start = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards") - .as_secs(); - let start_plus_10 = start + 10; - - while (SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards") - .as_secs() - < start_plus_10) - && !connected - { - let client_addr_str = - format!("http://{CAMERA_SERVICE_TEST_LOCALHOST}:{CAMERA_SERVICE_PORT}"); - connected = match CameraClient::connect(client_addr_str).await { - Ok(_) => { - trace!("Connected to server, stop polling"); - true - } - Err(e) => { - trace!("Unable to connect to server, continue polling: {e:?}"); - tokio::time::sleep(Duration::from_secs(1)).await; - false - } - }; - } - - if !connected { - Err(format!("Could not connect to Camera server {}", &addr_str)) - } else { - Ok(()) - } -} diff --git a/samples/brokers/udev-video-broker/src/util/mod.rs b/samples/brokers/udev-video-broker/src/util/mod.rs deleted file mode 100644 index 76e7c1931..000000000 --- a/samples/brokers/udev-video-broker/src/util/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod camera; -pub mod camera_capturer; -pub mod camera_service; diff --git a/version.sh b/version.sh index 2c63995e9..fc204888d 100755 --- a/version.sh +++ b/version.sh @@ -129,7 +129,7 @@ if [ "$CHECK" == "1" ]; then check_file_version "$BASEDIR/Cargo.toml" "$TOML_VERSION_PATTERN" "$TOML_VERSION" if [ "$?" -eq "1" ]; then exit 1; fi - CARGO_LOCK_PROJECTS="controller akri-shared agent controller webhook-configuration akri-discovery-utils akri-debug-echo akri-udev akri-onvif akri-opcua debug-echo-discovery-handler onvif-discovery-handler udev-discovery-handler opcua-discovery-handler" + CARGO_LOCK_PROJECTS="controller akri-shared agent controller webhook-configuration udev-video-broker akri-discovery-utils akri-debug-echo akri-udev akri-onvif akri-opcua debug-echo-discovery-handler onvif-discovery-handler udev-discovery-handler opcua-discovery-handler" CARGO_LOCK_VERSION="\"$(echo $VERSION)\"" for CARGO_LOCK_PROJECT in $CARGO_LOCK_PROJECTS do diff --git a/version.txt b/version.txt index b965ab81c..51252bbc2 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.13.21 +0.13.22