Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Update Camera Management example on minnesota-dev branch #213

Merged
merged 2 commits into from
May 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions application-services/custom/camera-management/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright (c) 2022 Intel Corporation
# Copyright (c) 2022-2023 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -15,14 +15,14 @@
#

#build stage
ARG BASE=golang:1.17-alpine3.15
ARG BASE=golang:1.18-alpine3.16
FROM ${BASE} AS builder

ARG ALPINE_PKG_BASE="make git gcc libc-dev libsodium-dev zeromq-dev"
ARG ALPINE_PKG_EXTRA=""

LABEL license='SPDX-License-Identifier: Apache-2.0' \
copyright='Copyright (c) 2022: Intel'
copyright='Copyright (c) 2023: Intel'
#disabled for now due to issues with the nl.alpinelinux.org mirror, Edgex is working on a fix for this.
#RUN sed -e 's/dl-cdn[.]alpinelinux.org/nl.alpinelinux.org/g' -i~ /etc/apk/repositories
RUN apk add --update --no-cache ${ALPINE_PKG_BASE} ${ALPINE_PKG_EXTRA}
Expand All @@ -37,7 +37,7 @@ ARG MAKE="make build-app"
RUN $MAKE

#final stage
FROM alpine:3.15
FROM alpine:3.16
LABEL license='SPDX-License-Identifier: Apache-2.0' \
copyright='Copyright (c) 2022: Intel'
lenny-goodell marked this conversation as resolved.
Show resolved Hide resolved
LABEL Name=app-camera-management Version=${VERSION}
Expand Down
47 changes: 42 additions & 5 deletions application-services/custom/camera-management/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Camera Management Example App Service
Use the Camera Management Example application service to auto discover and connect to nearby ONVIF and USB based cameras. This application will also control cameras via commands, create inference pipelines for the camera video streams and publish inference results to MQTT broker.

This app uses [EdgeX compose][edgex-compose], [Edgex Onvif Camera device service][device-onvif-camera], [Edgex USB Camera device service][device-usb-camera] and [Edge Video Analytics Microservice][evam].
This app uses [EdgeX compose][edgex-compose], [Edgex Onvif Camera device service][device-onvif-camera],
[Edgex USB Camera device service][device-usb-camera], [Edgex MQTT device service][device-mqtt] and [Edge Video Analytics Microservice][evam].

A brief video demonstration of building and using the example app service can be found [here](https://www.youtube.com/watch?v=vZqd3j2Zn2Y).

Expand Down Expand Up @@ -107,13 +108,25 @@ sudo apt install build-essential

b. Under the `ports` section, find the entry for port 8554 and change the host_ip from `127.0.0.1` to either `0.0.0.0` or the ip address you put in the previous step.

6. Run the following `make` command to run the edgex core services along with the Onvif and Usb device services.
6. Run the following `make` command to generate the edgex core services along with MQTT, Onvif and Usb device services.

> **Note**: The `ds-onvif-camera` parameter can be omitted if no Onvif cameras are present, or the `ds-usb-camera` parameter can be omitted if no usb cameras are present.
```shell
make run no-secty ds-onvif-camera ds-usb-camera
make gen no-secty ds-mqtt mqtt-broker ds-onvif-camera ds-usb-camera
```

7. Configure [device-mqtt] service to send [Edge Video Analytics Microservice][evam] inference results into Edgex via MQTT

a. Copy the entire [evam-mqtt-edgex](edge-video-analytics/evam-mqtt-edgex) folder into `edgex-compose/compose-builder` directory.

b. Copy and paste [docker-compose.override.yml](edge-video-analytics/evam-mqtt-edgex/docker-compose.override.yml) from the above copied folder into edgex-compose/compose-builder directory.
Insert full path of `edgex-compose/compose-builder` directory under volumes in this `docker-compose.override.yml`.
> **Note**: Please note that both the services in this file need the full path to be inserted for their volumes.

8. Run the following command to start all the Edgex services.
```shell
docker compose -f docker-compose.yml -f docker-compose.override.yml up -d
```

### 2. Start [Edge Video Analytics Microservice][evam] running for inference.

Expand Down Expand Up @@ -142,7 +155,7 @@ make run-edge-video-analytics
> **Note**: This step is only required if you have Onvif cameras. Currently, this example app is limited to supporting
> only 1 username/password combination for all Onvif cameras.

> **Note:** Please follow the instructions for the [Edgex Onvif Camera device service][device-onvif-camera] in order to connect your Onvif cameras to EdgeX.
> **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

Expand All @@ -160,7 +173,19 @@ make run-edge-video-analytics
export WRITABLE_INSECURESECRETS_CAMERACREDENTIALS_SECRETS_PASSWORD="<password>"
```

#### 3.2 Build and run
#### 3.2 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.

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
```

#### 3.3 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 @@ -256,6 +281,16 @@ The API log shows the status of the 5 most recent calls and commands that the ma

![inference events](./images/inference-events.png)

### Inference results in Edgex

To view inference results in Edgex, open Edgex UI [http://localhost:4000](http://localhost:4000), click on the `DataCenter`
tab and view data streaming under `Event Data Stream`by clicking on the `Start` button.

![inference events](./images/inference-edgex.png)

### Next steps
A custom app service can be used to analyze this inference data and take action based on the analysis.

## Additional Development

> **Warning**: The following steps are only useful for developers who wish to make modifications to the code
Expand All @@ -281,5 +316,7 @@ 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-usb-camera]: https://github.com/edgexfoundry/device-usb-camera
[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
27 changes: 23 additions & 4 deletions application-services/custom/camera-management/appcamera/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
package appcamera

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/pkg/errors"
"net/http"
"sync"
)

type CameraManagementApp struct {
Expand Down Expand Up @@ -43,12 +44,30 @@ func (app *CameraManagementApp) Run() error {
return err
}

if err := app.queryAllPipelineStatuses(); err != nil {
// Subscribe to events.
err := app.service.SetDefaultFunctionsPipeline(
app.processEdgeXDeviceSystemEvent)
if err != nil {
return errors.Wrap(err, "failed to set default pipeline to processEdgeXEvent")
}

if err = app.queryAllPipelineStatuses(); err != nil {
// do not exit, just log
app.lc.Errorf("Unable to query EVAM pipeline statuses. Is EVAM running? %s", err.Error())
}

if err := app.service.MakeItRun(); err != nil {
devices, err := app.getAllDevices()
if err != nil {
app.lc.Errorf("no devices found: %s", err.Error())
} else {
for _, device := range devices {
if err = app.startDefaultPipeline(device); err != nil {
app.lc.Errorf("Error starting default pipeline for %s, %v", device.Name, err)
}
}
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ type CustomConfig struct {
EvamBaseUrl string
MqttAddress string
MqttTopic string
DefaultPipelineName string
DefaultPipelineVersion string
}

// ServiceConfig a struct that wraps CustomConfig which holds the values for driver configuration
Expand Down
96 changes: 94 additions & 2 deletions application-services/custom/camera-management/appcamera/evam.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/IOTechSystems/onvif/media"
"github.com/pkg/errors"
"net/url"
"path"

"github.com/edgexfoundry/go-mod-core-contracts/v2/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/pkg/errors"
)

const (
Expand Down Expand Up @@ -218,6 +223,93 @@ func (app *CameraManagementApp) getPipelineStatus(deviceName string) (interface{
return nil, nil
}

// processEdgeXDeviceSystemEvent is the function that is called when an EdgeX Device System Event is received
func (app *CameraManagementApp) processEdgeXDeviceSystemEvent(_ interfaces.AppFunctionContext, data interface{}) (bool, interface{}) {
if data == nil {
return false, fmt.Errorf("processEdgeXDeviceSystemEvent: was called without any data")
}

systemEvent, ok := data.(dtos.SystemEvent)
if !ok {
return false, fmt.Errorf("type received %T is not a SystemEvent", data)
}

if systemEvent.Type != common.DeviceSystemEventType {
return false, fmt.Errorf("system event type is not " + common.DeviceSystemEventType)
}

device := dtos.Device{}
err := systemEvent.DecodeDetails(&device)
if err != nil {
return false, fmt.Errorf("failed to decode device details: %v", err)
}

switch systemEvent.Action {
case common.DeviceSystemEventActionAdd:
if err = app.startDefaultPipeline(device); err != nil {
return false, err
}
case common.DeviceSystemEventActionDelete:
// 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 {
return false, fmt.Errorf("error stopping pipleline for device %s, %v", device.Name, err)
}
}
default:
app.lc.Debugf("System event action %s is not handled", systemEvent.Action)
}

return false, nil
}

func (app *CameraManagementApp) startDefaultPipeline(device dtos.Device) error {
pipelineRunning := app.isPipelineRunning(device.Name)

if pipelineRunning {
app.lc.Debugf("pipeline is already running for device %s", device.Name)
return nil
}

app.lc.Debugf("pipeline is not running for device %s", device.Name)

if app.config.AppCustom.DefaultPipelineName == "" || app.config.AppCustom.DefaultPipelineVersion == "" {
app.lc.Warnf("no default pipeline name/version specified, skip starting pipeline for device %s", device.Name)
return nil
}

startPipelineRequest := StartPipelineRequest{
PipelineName: app.config.AppCustom.DefaultPipelineName,
PipelineVersion: app.config.AppCustom.DefaultPipelineVersion,
}

protocol, ok := device.Protocols["Onvif"]
if ok {
app.lc.Debugf("Onvif protocol information found for device: %s message: %v", device.Name, protocol)
profileResponse, err := app.getProfiles(device.Name)
if err != nil {
return fmt.Errorf("failed to get profiles for device %s, message: %v", device.Name, err)

}

app.lc.Debugf("Onvif profile information found for device: %s message: %v", device.Name, profileResponse)
startPipelineRequest.Onvif = &OnvifPipelineConfig{
ProfileToken: string(profileResponse.Profiles[0].Token),
}
} else if _, ok := device.Protocols["USB"]; ok {
app.lc.Debugf("Usb protocol found for device: %s", device.Name)
startPipelineRequest.USB = &USBStartStreamingRequest{}
}

app.lc.Debugf("Starting default pipeline for device %s", device.Name)
if err := app.startPipeline(device.Name, startPipelineRequest); err != nil {
return fmt.Errorf("pipeline failed to start for device %s, message: %v", device.Name, err)

}

return nil
}

// queryAllPipelineStatuses queries EVAM for all pipeline statuses, attempts to link them to devices, and then
// insert them into the pipeline map.
func (app *CameraManagementApp) queryAllPipelineStatuses() error {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,11 @@
version: "3"

networks:
evam_network:
edgex_edgex-network:
external: true
driver: "bridge"

services:
broker:
image: eclipse-mosquitto
hostname: mqtt
volumes:
- ./mosquitto.conf:/mosquitto/config/mosquitto.conf:ro
ports:
- "1883:1883"
- "59001:9001"
networks:
- evam_network

edge_video_analytics_microservice:
image: intel/edge_video_analytics_microservice:0.7.2
hostname: edge_video_analytics_microservice
Expand All @@ -41,7 +31,7 @@ services:
- '8080:8080'
- '8555:8555'
networks:
- evam_network
- edgex_edgex-network
environment:
ENABLE_RTSP: "true"
RTSP_PORT: 8555
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright (C) 2023 Intel Corporation
# SPDX-License-Identifier: Apache-2.0

# Pre-define Devices
[[DeviceList]]
Name = "edge-video-analytics"
ProfileName = "edge-video-analytics-profile"
Description = "Device for EVAM (Edge Video Analytics Microservice) pipeline events"
Labels = ["cv", "dlstreamer", "evam", "pipeline-server", "edge-video-analytics"]
[DeviceList.Protocols]
[DeviceList.Protocols.mqtt]
CommandTopic = "command/edge-video-analytics"
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#
# Copyright (c) 2023 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

version: '3.7'

services:
device-mqtt:
environment:
DEVICE_DEVICESDIR: /evam-mqtt-edgex/devices
DEVICE_PROFILESDIR: /evam-mqtt-edgex/profiles
MQTTBROKERINFO_INCOMINGTOPIC: "incoming/data/#"
MQTTBROKERINFO_USETOPICLEVELS: "true"
volumes:
# example: - /home/github.com/edgexfoundry/edgex-compose/compose-builder/evam-mqtt-edgex:/evam-mqtt-edgex
- <add-full-path-of-your-edgex-compose-builder-here-example-above>/evam-mqtt-edgex:/evam-mqtt-edgex

mqtt-broker:
volumes:
# example: - /home/github.com/edgexfoundry/edgex-compose/compose-builder/evam-mqtt-edgex:/evam-mqtt-edgex
- <add-full-path-of-your-edgex-compose-builder-here>/evam-mqtt-edgex/mosquitto.conf:/mosquitto-no-auth.conf:ro
ports:
- "59001:9001"
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#
# Copyright (C) 2022-2023 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0

allow_anonymous true
listener 1883

log_type error
log_type warning
log_type notice
log_type information
log_type subscribe
log_type unsubscribe
log_type websockets

listener 9001
protocol websockets
Loading