Skip to content

Commit

Permalink
feat: Migrate Camera Management example to V3 (#214)
Browse files Browse the repository at this point in the history
* feat: Migrate Camera Management example to V3

closes #194

Signed-off-by: Leonard Goodell <leonard.goodell@intel.com>
Signed-off-by: Anthony Casagrande <anthony.j.casagrande@intel.com>
Signed-off-by: preethi-satishcandra <preethi.satishchandra@intel.com>
Co-authored-by: Anthony Casagrande <anthony.j.casagrande@intel.com>
Co-authored-by: preethi-satishcandra <preethi.satishchandra@intel.com>
  • Loading branch information
3 people authored May 26, 2023
1 parent c7fd6a8 commit 11045f6
Show file tree
Hide file tree
Showing 17 changed files with 294 additions and 477 deletions.
10 changes: 5 additions & 5 deletions application-services/custom/camera-management/Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (C) 2022 Intel Corporation
# Copyright (C) 2022-2023 Intel Corporation
# SPDX-License-Identifier: Apache-2.0

.PHONY: build-app test clean docker tidy run-app run-tests
Expand All @@ -11,10 +11,10 @@ APPVERSION=$(shell cat ./VERSION 2>/dev/null || echo 0.0.0)

# This pulls the version of the SDK from the go.mod file. If the SDK is the only required module,
# it must first remove the word 'required' so the offset of $2 is the same if there are multiple required modules
SDKVERSION=$(shell cat ./go.mod | grep 'github.com/edgexfoundry/app-functions-sdk-go/v2 v' | sed 's/require//g' | awk '{print $$2}')
SDKVERSION=$(shell cat ./go.mod | grep 'github.com/edgexfoundry/app-functions-sdk-go/v3 v' | sed 's/require//g' | awk '{print $$2}')

MICROSERVICE=app-camera-management
GOFLAGS=-ldflags "-X github.com/edgexfoundry/app-functions-sdk-go/v2/internal.SDKVersion=$(SDKVERSION) -X github.com/edgexfoundry/app-functions-sdk-go/v2/internal.ApplicationVersion=$(APPVERSION)"
GOFLAGS=-ldflags "-X github.com/edgexfoundry/app-functions-sdk-go/v3/internal.SDKVersion=$(SDKVERSION) -X github.com/edgexfoundry/app-functions-sdk-go/v3/internal.ApplicationVersion=$(APPVERSION)"

GIT_SHA=$(shell git rev-parse HEAD)

Expand All @@ -25,8 +25,8 @@ build-app: tidy
tidy:
go mod tidy

run-app:
EDGEX_SECURITY_SECRET_STORE=false ./$(MICROSERVICE)
run-app: build-app
EDGEX_SECURITY_SECRET_STORE=false ./$(MICROSERVICE) -cp -d

# TODO: Change the registries (edgexfoundry, nexus3.edgexfoundry.org:10004) below as needed.
# Leave them as is if service is to be upstreamed to EdgeX Foundry
Expand Down
62 changes: 43 additions & 19 deletions application-services/custom/camera-management/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ sudo apt install build-essential
```

3. Checkout the latest compatible release branch
> **Note**: The `levski` branch is the latest stable branch at the time of this writing.
> **Note**: The `minnesota` branch is the latest stable branch at the time of this writing.
```shell
git checkout levski
git checkout minnesota
```

4. Navigate to the `compose-builder` subdirectory:
Expand Down Expand Up @@ -157,35 +157,58 @@ make run-edge-video-analytics

> **Note**: Please follow the instructions for the [Edgex Onvif Camera device service][device-onvif-manage] in order to connect your Onvif cameras to EdgeX.

Option 1: Modify the [res/configuration.toml](res/configuration.toml) file
Option 1: Modify the [res/configuration.yaml](res/configuration.yaml) file

```toml
[Writable.InsecureSecrets.CameraCredentials]
path = "CameraCredentials"
[Writable.InsecureSecrets.CameraCredentials.Secrets]
username = "<username>"
password = "<password>"
```yaml
InsecureSecrets:
onvifCredentials:
SecretName: onvifAuth
SecretData:
username: "<username>"
password: "<password>"
```

Option 2: Export environment variable overrides
```shell
export WRITABLE_INSECURESECRETS_CAMERACREDENTIALS_SECRETS_USERNAME="<username>"
export WRITABLE_INSECURESECRETS_CAMERACREDENTIALS_SECRETS_PASSWORD="<password>"
export WRITABLE_INSECURESECRETS_ONVIFAUTH_SECRETDATA_USERNAME="<username>"
export WRITABLE_INSECURESECRETS_ONVIFAUTH_SECRETDATA_PASSWORD="<password>"
```

#### 3.2 Configure Default Pipeline
#### 3.2 (Optional) Configure USB Camera RTSP Credentials.
> **Note**: This step is only required if you have USB cameras.

> **Note**: Please follow the instructions for the [Edgex USB Camera device service][device-usb-manage] in order to connect your USB cameras to EdgeX.

Option 1: Modify the [res/configuration.yaml](res/configuration.yaml) file

```yaml
InsecureSecrets:
usbCredentials:
SecretName: rtspAuth
SecretData:
username: "<username>"
password: "<password>"
```

Option 2: Export environment variable overrides
```shell
export WRITABLE_INSECURESECRETS_RTSPAUTH_SECRETDATA_USERNAME="<username>"
export WRITABLE_INSECURESECRETS_RTSPAUTH_SECRETDATA_PASSWORD="<password>"
```

#### 3.3 Configure Default Pipeline
Initially, all new cameras added to the system will start the default analytics pipeline as defined in the configuration file below. The desired pipeline can be changed afterward or the feature can be disabled by setting the `DefaultPipelineName` and `DefaultPipelineVersion` to empty strings.

Modify the [res/configuration.toml](res/configuration.toml) file with the name and version of the default pipeline to use when a new device is added to the system.
Modify the [res/configuration.yaml](res/configuration.yaml) file with the name and version of the default pipeline to use when a new device is added to the system.

Note: These values can be left empty to disable the feature.
```toml
[AppCustom]
DefaultPipelineName = "object_detection" # Name of the default pipeline used when a new device is added to the system
DefaultPipelineVersion = "person" # Version of the default pipeline used when a new device is added to the system
```yaml
AppCustom:
DefaultPipelineName: object_detection # Name of the default pipeline used when a new device is added to the system; can be left blank to disable feature
DefaultPipelineVersion: person # Version of the default pipeline used when a new device is added to the system; can be left blank to disable feature
```

#### 3.3 Build and run
#### 3.4 Build and run
```shell
# First make sure you are at the root of this example app
cd edgex-examples/application-services/custom/camera-management
Expand Down Expand Up @@ -316,7 +339,8 @@ Open your browser to [http://localhost:4200](http://localhost:4200)

[edgex-compose]: https://github.com/edgexfoundry/edgex-compose
[device-onvif-camera]: https://github.com/edgexfoundry/device-onvif-camera
[device-onvif-manage]: https://github.com/edgexfoundry/device-onvif-camera/blob/levski/doc/guides/SimpleStartupGuide.md#manage-devices
[device-onvif-manage]: https://docs.edgexfoundry.org/latest/microservices/device/supported/device-onvif-camera/Walkthrough/deployment/#manage-devices
[device-usb-camera]: https://github.com/edgexfoundry/device-usb-camera
[device-usb-manage]: https://docs.edgexfoundry.org/latest/microservices/device/supported/device-usb-camera/Walkthrough/deployment/#manage-devices
[evam]: https://www.intel.com/content/www/us/en/developer/articles/technical/video-analytics-service.html
[device-mqtt]: https://github.com/edgexfoundry/device-mqtt-go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
"net/http"
"sync"

"github.com/edgexfoundry/app-functions-sdk-go/v2/pkg/interfaces"
"github.com/edgexfoundry/go-mod-core-contracts/v2/clients/logger"
"github.com/edgexfoundry/app-functions-sdk-go/v3/pkg/interfaces"
"github.com/edgexfoundry/go-mod-core-contracts/v3/clients/logger"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -67,7 +67,7 @@ func (app *CameraManagementApp) Run() error {
}
}

if err = app.service.MakeItRun(); err != nil {
if err = app.service.Run(); err != nil {
return errors.Wrap(err, "failed to run pipeline")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,24 @@ package appcamera
import (
"context"
"fmt"

"github.com/IOTechSystems/onvif/device"
"github.com/IOTechSystems/onvif/media"
"github.com/IOTechSystems/onvif/ptz"
"github.com/IOTechSystems/onvif/xsd/onvif"
"github.com/edgexfoundry/go-mod-core-contracts/v2/dtos"
dtosCommon "github.com/edgexfoundry/go-mod-core-contracts/v2/dtos/common"
"github.com/edgexfoundry/go-mod-core-contracts/v3/dtos"
dtosCommon "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/common"
"github.com/pkg/errors"
)

const (
relativeMoveCommand = "RelativeMove"
gotoPresetCommand = "GotoPreset"
relativeMoveCommand = "PTZRelativeMove"
gotoPresetCommand = "PTZGotoPreset"
streamUriCommand = "StreamUri"
profilesCommand = "Profiles"
getPresetsCommand = "GetPresets"
profilesCommand = "MediaProfiles"
getPresetsCommand = "PTZPresets"
getCapabilitiesCommand = "Capabilities"
getConfigurationsCommand = "GetConfigurations"
getConfigurationsCommand = "PTZConfigurations"
startStreamingCommand = "StartStreaming"
stopStreamingCommand = "StopStreaming"
usbStreamUriCommand = "StreamURI"
Expand Down Expand Up @@ -101,7 +102,7 @@ func (app *CameraManagementApp) getCameraFeatures(deviceName string) (CameraFeat

func (app *CameraManagementApp) getCapabilities(deviceName string) (device.GetCapabilitiesResponse, error) {
cmd := &device.GetCapabilities{
Category: onvif.CapabilityCategory("All"),
Category: []onvif.CapabilityCategory{onvif.CapabilityCategory("All")},
}

resp := device.GetCapabilitiesResponse{}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
//
// Copyright (C) 2022 Intel Corporation
// Copyright (C) 2022-2023 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0

package appcamera

import (
"github.com/edgexfoundry/go-mod-bootstrap/v2/config"
"github.com/edgexfoundry/go-mod-core-contracts/v2/errors"
"github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/secret"
"github.com/edgexfoundry/go-mod-bootstrap/v3/config"
"github.com/edgexfoundry/go-mod-core-contracts/v3/errors"
)

const (
CameraCredentials = "CameraCredentials"
UsernameKey = "username"
PasswordKey = "password"
rtspAuth = "rtspAuth"
onvifAuth = "onvifAuth"
)

// tryGetCredentials will attempt one time to get the camera credentials from the
// secret provider and return them, otherwise return an error.
func (app *CameraManagementApp) tryGetCredentials() (config.Credentials, errors.EdgeX) {
secretData, err := app.service.GetSecret(CameraCredentials, UsernameKey, PasswordKey)
func (app *CameraManagementApp) tryGetCredentials(secretName string) (config.Credentials, errors.EdgeX) {
secretData, err := app.service.SecretProvider().GetSecret(secretName, secret.UsernameKey, secret.PasswordKey)
if err != nil {
return config.Credentials{}, errors.NewCommonEdgeXWrapper(err)
}
return config.Credentials{
Username: secretData[UsernameKey],
Password: secretData[PasswordKey],
Username: secretData[secret.UsernameKey],
Password: secretData[secret.PasswordKey],
}, nil
}
36 changes: 20 additions & 16 deletions application-services/custom/camera-management/appcamera/evam.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import (
"net/url"
"path"

"github.com/edgexfoundry/go-mod-core-contracts/v2/common"
"github.com/edgexfoundry/go-mod-core-contracts/v3/common"

"github.com/IOTechSystems/onvif/media"
"github.com/edgexfoundry/app-functions-sdk-go/v2/pkg/interfaces"
"github.com/edgexfoundry/go-mod-core-contracts/v2/dtos"
"github.com/edgexfoundry/app-functions-sdk-go/v3/pkg/interfaces"
"github.com/edgexfoundry/go-mod-core-contracts/v3/dtos"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -111,15 +111,19 @@ func (app *CameraManagementApp) startPipeline(deviceName string, sr StartPipelin
}
app.lc.Infof("Received stream uri for the device %s: %s", deviceName, streamUri)

// set the secret name to be the onvif one by default
secretName := onvifAuth
// if device is usb camera, start streaming first
if sr.USB != nil {
_, err := app.startStreaming(deviceName, *sr.USB)
if err != nil {
return errors.Wrapf(err, "failed to start streaming usb camera %s", deviceName)
}
// for usb cameras, use the rtspAuth instead
secretName = rtspAuth
}

body, err := app.createPipelineRequestBody(streamUri, deviceName)
body, err := app.createPipelineRequestBody(streamUri, deviceName, secretName)
if err != nil {
return errors.Wrapf(err, "failed to create DLStreamer pipeline request body")
}
Expand All @@ -133,7 +137,7 @@ func (app *CameraManagementApp) startPipeline(deviceName string, sr StartPipelin
if err != nil {
return err
}
reqPath := path.Join("pipelines", info.Name, info.Version)
reqPath := path.Join("/pipelines", info.Name, info.Version)

if err = issuePostRequest(context.Background(), &res, baseUrl.String(), reqPath, body); err != nil {
err = errors.Wrap(err, "POST request to start EVAM pipeline failed")
Expand All @@ -160,7 +164,7 @@ func (app *CameraManagementApp) startPipeline(deviceName string, sr StartPipelin
func (app *CameraManagementApp) stopPipeline(deviceName string, id string) error {
var res interface{}

if err := issueDeleteRequest(context.Background(), &res, app.config.AppCustom.EvamBaseUrl, path.Join("pipelines", id)); err != nil {
if err := issueDeleteRequest(context.Background(), &res, app.config.AppCustom.EvamBaseUrl, path.Join("/pipelines", id)); err != nil {
return errors.Wrap(err, "DELETE request to stop EVAM pipeline failed")
}
app.lc.Infof("Successfully stopped EVAM pipeline for the device %s", deviceName)
Expand All @@ -173,14 +177,14 @@ func (app *CameraManagementApp) stopPipeline(deviceName string, id string) error
return nil
}

func (app *CameraManagementApp) createPipelineRequestBody(streamUri string, deviceName string) ([]byte, error) {
func (app *CameraManagementApp) createPipelineRequestBody(streamUri string, deviceName string, secretName string) ([]byte, error) {
uri, err := url.Parse(streamUri)
if err != nil {
return nil, err
}

if creds, err := app.tryGetCredentials(); err != nil {
app.lc.Warnf("Error retrieving %s secret from the SecretStore: %s", CameraCredentials, err.Error())
if creds, err := app.tryGetCredentials(secretName); err != nil {
app.lc.Warnf("Error retrieving %s secret from the SecretStore: %s", secretName, err.Error())
} else {
uri.User = url.UserPassword(creds.Username, creds.Password)
}
Expand Down Expand Up @@ -214,7 +218,7 @@ func (app *CameraManagementApp) createPipelineRequestBody(streamUri string, devi
func (app *CameraManagementApp) getPipelineStatus(deviceName string) (interface{}, error) {
if info, found := app.getPipelineInfo(deviceName); found {
var res interface{}
if err := issueGetRequest(context.Background(), &res, app.config.AppCustom.EvamBaseUrl, path.Join("pipelines", "status", info.Id)); err != nil {
if err := issueGetRequest(context.Background(), &res, app.config.AppCustom.EvamBaseUrl, path.Join("/pipelines", "status", info.Id)); err != nil {
return nil, errors.Wrap(err, "GET request to query EVAM pipeline status failed")
}
return res, nil
Expand Down Expand Up @@ -245,11 +249,11 @@ func (app *CameraManagementApp) processEdgeXDeviceSystemEvent(_ interfaces.AppFu
}

switch systemEvent.Action {
case common.DeviceSystemEventActionAdd:
case common.SystemEventActionAdd:
if err = app.startDefaultPipeline(device); err != nil {
return false, err
}
case common.DeviceSystemEventActionDelete:
case common.SystemEventActionDelete:
// stop any running pipelines for the deleted device
if info, found := app.getPipelineInfo(device.Name); found {
if err = app.stopPipeline(device.Name, info.Id); err != nil {
Expand Down Expand Up @@ -314,7 +318,7 @@ func (app *CameraManagementApp) startDefaultPipeline(device dtos.Device) error {
// insert them into the pipeline map.
func (app *CameraManagementApp) queryAllPipelineStatuses() error {
var statuses []PipelineStatus
if err := issueGetRequest(context.Background(), &statuses, app.config.AppCustom.EvamBaseUrl, path.Join("pipelines", "status")); err != nil {
if err := issueGetRequest(context.Background(), &statuses, app.config.AppCustom.EvamBaseUrl, path.Join("/pipelines", "status")); err != nil {
return errors.Wrap(err, "GET request to query EVAM pipeline statuses failed")
}

Expand All @@ -324,7 +328,7 @@ func (app *CameraManagementApp) queryAllPipelineStatuses() error {
}

var resp PipelineInformationResponse
if err := issueGetRequest(context.Background(), &resp, app.config.AppCustom.EvamBaseUrl, path.Join("pipelines", status.Id)); err != nil {
if err := issueGetRequest(context.Background(), &resp, app.config.AppCustom.EvamBaseUrl, path.Join("/pipelines", status.Id)); err != nil {
app.lc.Errorf("GET request to query EVAM pipeline %s info failed: %s", status.Id, err.Error())
continue
}
Expand Down Expand Up @@ -365,7 +369,7 @@ func (app *CameraManagementApp) getAllPipelineStatuses() (map[string]PipelineInf

// loop through the partially filled response map to fill in the missing data. we do not need to hold the lock here.
for camera, data := range response {
if err := issueGetRequest(context.Background(), &data.Status, app.config.AppCustom.EvamBaseUrl, path.Join("pipelines", "status", data.Info.Id)); err != nil {
if err := issueGetRequest(context.Background(), &data.Status, app.config.AppCustom.EvamBaseUrl, path.Join("/pipelines", "status", data.Info.Id)); err != nil {
return nil, errors.Wrap(err, "GET request to query EVAM pipeline failed")
}
// overwrite the changed result in the map
Expand All @@ -377,7 +381,7 @@ func (app *CameraManagementApp) getAllPipelineStatuses() (map[string]PipelineInf

func (app *CameraManagementApp) getPipelines() (interface{}, error) {
var res interface{}
if err := issueGetRequest(context.Background(), &res, app.config.AppCustom.EvamBaseUrl, "pipelines"); err != nil {
if err := issueGetRequest(context.Background(), &res, app.config.AppCustom.EvamBaseUrl, "/pipelines"); err != nil {
return nil, errors.Wrap(err, "GET request to query all EVAM pipelines failed")
}
return res, nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import (
"net/http"
"path"

dtosCommon "github.com/edgexfoundry/go-mod-core-contracts/v2/dtos/common"
dtosCommon "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/common"
"github.com/gorilla/mux"

"github.com/edgexfoundry/go-mod-core-contracts/v2/common"
"github.com/edgexfoundry/go-mod-core-contracts/v3/common"
"github.com/pkg/errors"
)

Expand Down
Loading

0 comments on commit 11045f6

Please sign in to comment.