├── app-services ├── simple-filter-xml-mqtt │ ├── mosquitto.conf │ ├── res │ │ └── configuration.toml │ └── main.go ├── http-command-service │ ├── status-on-request.json │ ├── status-off-request.json │ ├── iot-hub │ │ └── Quickstarts │ │ │ ├── proxy-device │ │ │ ├── run.sh │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ └── java │ │ │ │ │ └── com │ │ │ │ │ └── microsoft │ │ │ │ │ └── docs │ │ │ │ │ └── iothub │ │ │ │ │ └── samples │ │ │ │ │ ├── Switch.java │ │ │ │ │ └── ProxyDevice.java │ │ │ └── pom.xml │ │ │ ├── back-end-application │ │ │ ├── off.sh │ │ │ ├── on.sh │ │ │ ├── pom.xml │ │ │ └── src │ │ │ │ └── main │ │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── microsoft │ │ │ │ └── docs │ │ │ │ └── iothub │ │ │ │ └── samples │ │ │ │ └── BackEndApplication.java │ │ │ └── pom.xml │ ├── Southbound.png │ ├── res │ │ └── configuration.toml │ ├── functions │ │ ├── console_print.go │ │ └── switch_cmd.go │ ├── main.go │ └── README.md ├── advanced-target-type │ ├── res │ │ └── configuration.toml │ ├── functions │ │ ├── output.go │ │ └── convert.go │ ├── Post Person to Trigger.postman_collection.json │ ├── README.md │ └── main.go ├── simple-cbor-filter │ ├── res │ │ └── configuration.toml │ ├── Device Simple Switch commands.postman_collection.json │ ├── README.md │ └── main.go ├── simple-filter-xml │ ├── EdgeX Applications Function SDK.postman_collection.json │ ├── Dockerfile │ ├── res │ │ └── configuration.toml │ ├── main.go │ └── EdgeX Application Function SDK Device Name.postman_collection.json ├── advanced-filter-convert-publish │ ├── res │ │ └── configuration.toml │ ├── functions │ │ ├── output.go │ │ └── convert.go │ ├── main.go │ └── README.md └── simple-filter-xml-post │ ├── res │ └── configuration.toml │ └── main.go ├── .gitignore ├── go.mod ├── Makefile ├── README.md └── LICENSE /app-services/simple-filter-xml-mqtt/mosquitto.conf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app-services/http-command-service/status-on-request.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "on" 3 | } 4 | -------------------------------------------------------------------------------- /app-services/http-command-service/status-off-request.json: -------------------------------------------------------------------------------- 1 | { 2 | "status" : "off" 3 | } 4 | -------------------------------------------------------------------------------- /app-services/http-command-service/iot-hub/Quickstarts/proxy-device/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | java -jar target/proxy-device-1.0.0-with-deps.jar 3 | -------------------------------------------------------------------------------- /app-services/http-command-service/Southbound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/app-service-examples/master/app-services/http-command-service/Southbound.png -------------------------------------------------------------------------------- /app-services/http-command-service/iot-hub/Quickstarts/back-end-application/off.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | java -jar target/back-end-application-1.0.0-with-deps.jar turn-off 3 | -------------------------------------------------------------------------------- /app-services/http-command-service/iot-hub/Quickstarts/back-end-application/on.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | java -jar target/back-end-application-1.0.0-with-deps.jar turn-on 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.out 2 | examples/bin 3 | .idea/ 4 | .vscode 5 | **/debug 6 | **/debug.test 7 | app-service 8 | *.log 9 | *.txt 10 | *.html 11 | *.out 12 | *.dll 13 | *.exe 14 | go.sum -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/edgexfoundry-holding/app-service-examples 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/edgexfoundry/app-functions-sdk-go v1.0.0-dev.2 7 | github.com/edgexfoundry/go-mod-core-contracts v0.1.25 8 | ) 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build clean 2 | 3 | GO=CGO_ENABLED=1 GO111MODULE=on go 4 | 5 | APP_SERVICES=app-services/* 6 | 7 | GIT_SHA=$(shell git rev-parse HEAD) 8 | 9 | .PHONY: build $(APP_SERVICES) 10 | 11 | build: $(APP_SERVICES) 12 | 13 | $(APP_SERVICES): 14 | $(GO) build $(GOFLAGS) -o $@/app-service ./$@ 15 | 16 | clean: 17 | rm -f app-services/*/app-service 18 | 19 | docker: 20 | docker build \ 21 | --build-arg http_proxy \ 22 | --build-arg https_proxy \ 23 | -f app-services/simple-filter-xml/Dockerfile \ 24 | --label "git_sha=$(GIT_SHA)" \ 25 | -t edgexfoundry/docker-simple-filter-xml:$(GIT_SHA) \ 26 | -t edgexfoundry/docker-simple-filter-xml:dev \ 27 | . 28 | -------------------------------------------------------------------------------- /app-services/http-command-service/iot-hub/Quickstarts/proxy-device/src/main/java/com/microsoft/docs/iothub/samples/Switch.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.docs.iothub.samples; 2 | 3 | import com.google.api.client.json.GenericJson; 4 | import com.google.api.client.util.Key; 5 | 6 | public class Switch extends GenericJson { 7 | @Key 8 | private String SwitchButton; 9 | 10 | public String getSwitchButton() { 11 | return SwitchButton; 12 | } 13 | 14 | public void setSwitchButton(String s) { 15 | this.SwitchButton = s; 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return "SwitchButton is " + SwitchButton; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app-services/http-command-service/res/configuration.toml: -------------------------------------------------------------------------------- 1 | [Writable] 2 | LogLevel = 'DEBUG' 3 | [Writable.StoreAndForward] 4 | Enabled = false 5 | RetryInterval = '5m' 6 | MaxRetryCount = 10 7 | 8 | [Service] 9 | BootTimeout = '30s' 10 | ClientMonitor = '15s' 11 | CheckInterval = '10s' 12 | Host = 'localhost' 13 | Port = 48095 14 | Protocol = 'http' 15 | ReadMaxLimit = 100 16 | StartupMsg = 'This is a sample Application Service to receive HTTP request as trigger' 17 | Timeout = '30s' 18 | 19 | [Registry] 20 | Host = 'localhost' 21 | Port = 8500 22 | Type = 'consul' 23 | 24 | [Logging] 25 | EnableRemote = false 26 | File = './logs/http-command-service.log' 27 | 28 | [Clients] 29 | [Clients.Logging] 30 | Protocol = "http" 31 | Host = "localhost" 32 | Port = 48061 33 | [Clients.Command] 34 | Protocol = 'http' 35 | Host = 'localhost' 36 | Port = 48082 37 | 38 | # This example expect custom type via HTTP Trigger 39 | [Binding] 40 | Type="http" 41 | 42 | [ApplicationSettings] 43 | DeviceID = "a0fa03eb-1ecc-4726-9017-fa21444020d9" 44 | CommandID = "384afb70-ead6-4ff8-a7a0-d96690002f76" 45 | -------------------------------------------------------------------------------- /app-services/advanced-target-type/res/configuration.toml: -------------------------------------------------------------------------------- 1 | [Writable] 2 | LogLevel = 'DEBUG' 3 | [Writable.StoreAndForward] 4 | Enabled = false 5 | RetryInterval = '5m' 6 | MaxRetryCount = 10 7 | 8 | [Service] 9 | BootTimeout = '30s' 10 | ClientMonitor = '15s' 11 | CheckInterval = '10s' 12 | Host = 'localhost' 13 | Port = 48095 14 | Protocol = 'http' 15 | ReadMaxLimit = 100 16 | StartupMsg = 'This is a sample Application Service which use of TargetType' 17 | Timeout = '30s' 18 | 19 | [Registry] 20 | Host = 'localhost' 21 | Port = 8500 22 | Type = 'consul' 23 | 24 | [Logging] 25 | EnableRemote = false 26 | File = './logs/simple-filter-xml-mqtt.log' 27 | 28 | [Database] 29 | Type = "mongodb" 30 | Host = "localhost" 31 | Port = 27017 32 | Timeout = "30s" 33 | Username = "" 34 | Password = "" 35 | 36 | [Clients] 37 | [Clients.CoreData] 38 | Protocol = 'http' 39 | Host = 'localhost' 40 | Port = 48080 41 | 42 | [Clients.Logging] 43 | Protocol = "http" 44 | Host = "localhost" 45 | Port = 48061 46 | 47 | # This example expect custom type via HTTP Trigger 48 | [Binding] 49 | Type="http" 50 | 51 | [ApplicationSettings] 52 | -------------------------------------------------------------------------------- /app-services/advanced-target-type/functions/output.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package functions 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/edgexfoundry/app-functions-sdk-go/appcontext" 23 | ) 24 | 25 | func PrintXmlToConsole(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) { 26 | edgexcontext.LoggingClient.Debug("PrintXmlToConsole") 27 | 28 | if len(params) < 1 { 29 | // We didn't receive a result 30 | return false, nil 31 | } 32 | 33 | fmt.Println(params[0].(string)) 34 | 35 | return true, params[0] 36 | } 37 | -------------------------------------------------------------------------------- /app-services/http-command-service/functions/console_print.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package functions 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/edgexfoundry/app-functions-sdk-go/appcontext" 23 | ) 24 | 25 | func PrintToConsole(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) { 26 | edgexcontext.LoggingClient.Debug("PrintToConsole") 27 | 28 | if len(params) < 1 { 29 | // We didn't receive a result 30 | return false, nil 31 | } 32 | 33 | fmt.Println(params[0].(string)) 34 | 35 | return true, params[0] 36 | } 37 | -------------------------------------------------------------------------------- /app-services/advanced-target-type/Post Person to Trigger.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "c8645c7d-0b6a-48ce-81a0-4fd0760f1334", 4 | "name": "Post Person to Trigger", 5 | "description": "Sends Person JSON to http trigger of Application Service", 6 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 7 | }, 8 | "item": [ 9 | { 10 | "name": "Person Trigger", 11 | "request": { 12 | "method": "POST", 13 | "header": [ 14 | { 15 | "key": "Content-Type", 16 | "name": "Content-Type", 17 | "value": "application/json", 18 | "type": "text" 19 | } 20 | ], 21 | "body": { 22 | "mode": "raw", 23 | "raw": "{\n \"first_name\": \"Sam\",\n \"last_name\": \"Smith\",\n \"event\": null,\n \"phone\": {\n \"country_code\": 1,\n \"area_code\": 480,\n \"local_prefix\": 970,\n \"local_number\": 3476\n }\n}" 24 | }, 25 | "url": { 26 | "raw": "127.0.0.1:48095/trigger", 27 | "host": [ 28 | "127", 29 | "0", 30 | "0", 31 | "1" 32 | ], 33 | "port": "48095", 34 | "path": [ 35 | "trigger" 36 | ] 37 | } 38 | }, 39 | "response": [] 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /app-services/http-command-service/iot-hub/Quickstarts/back-end-application/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.microsoft.docs.iothub.samples 6 | quickstarts 7 | 1.0 8 | 9 | com.microsoft.docs.iothub.samples 10 | back-end-application 11 | 1.0.0 12 | Back end application 13 | 14 | 15 | com.microsoft.azure.sdk.iot 16 | iot-service-client 17 | 1.13.0 18 | 19 | 20 | 21 | UTF-8 22 | 23 | 24 | 25 | 26 | org.apache.maven.plugins 27 | maven-jar-plugin 28 | 2.6 29 | 30 | 31 | 32 | true 33 | com.microsoft.docs.iothub.samples.BackEndApplication 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app-services/advanced-target-type/README.md: -------------------------------------------------------------------------------- 1 | # Advanced Target Type 2 | 3 | This **advanced-target-type** Application Service demonstrates how to create an Application Service that expects a custom type to feed to the functions pipeline. For more detail refer to the SDK's main README's section on [TargetType](https://github.com/edgexfoundry/app-functions-sdk-go/blob/master/README.md#target-type) 4 | 5 | To run this example: 6 | 7 | 1. Clone **[app-functions-sdk-go](https://github.com/edgexfoundry/app-functions-sdk-go)** repo 8 | 9 | 2. run `make build` 10 | 11 | 3. cd `examples/advance-target-type` 12 | 13 | 4. run `./advance-target-type` 14 | 15 | 5. Start PostMan 16 | 17 | 6. Load `Post Person to Trgger.postman_collection.json` collection in PostMan 18 | 19 | 7. Run the `Person Trigger` request 20 | 21 | - The following XML will be printed to the console by the Application Service and will be returned as the trigger HTTP response in PostMan. 22 | 23 | ``` 24 | 25 | Sam 26 | Smith 27 | 28 | 1 29 | 480 30 | 970 31 | 3476 32 | 33 | +01(480) 970-3476 34 | 35 | ``` 36 | 37 | - Note that the PhoneDisplay field that is not present in the XML sent from PostMan is now present and filled out. 38 | 39 | 40 | -------------------------------------------------------------------------------- /app-services/simple-cbor-filter/res/configuration.toml: -------------------------------------------------------------------------------- 1 | [Writable] 2 | LogLevel = 'INFO' 3 | [Writable.StoreAndForward] 4 | Enabled = false 5 | RetryInterval = '5m' 6 | MaxRetryCount = 10 7 | 8 | [Service] 9 | BootTimeout = '30s' 10 | ClientMonitor = '15s' 11 | CheckInterval = '10s' 12 | Host = 'localhost' 13 | Port = 48095 14 | Protocol = 'http' 15 | ReadMaxLimit = 100 16 | StartupMsg = 'Simple CBOR Filter Application Service started' 17 | Timeout = '30s' 18 | 19 | [Registry] 20 | Host = 'localhost' 21 | Port = 8500 22 | Type = 'consul' 23 | 24 | [Logging] 25 | EnableRemote = false 26 | File = './logs/simple-filter-xml-mqtt.log' 27 | 28 | [Database] 29 | Type = "mongodb" 30 | Host = "localhost" 31 | Port = 27017 32 | Timeout = "30s" 33 | Username = "" 34 | Password = "" 35 | 36 | [Clients] 37 | [Clients.CoreData] 38 | Protocol = 'http' 39 | Host = 'localhost' 40 | Port = 48080 41 | 42 | [Clients.Logging] 43 | Protocol = "http" 44 | Host = "localhost" 45 | Port = 48061 46 | 47 | [MessageBus] 48 | Type = 'zero' 49 | [MessageBus.PublishHost] 50 | Host = '*' 51 | Port = 5564 52 | Protocol = 'tcp' 53 | [MessageBus.SubscribeHost] 54 | Host = 'localhost' 55 | Port = 5563 56 | Protocol = 'tcp' 57 | 58 | # Choose either an HTTP trigger or MessageBus trigger (aka Binding) 59 | 60 | #[Binding] 61 | #Type="http" 62 | 63 | [Binding] 64 | Type="messagebus" 65 | SubscribeTopic="events" 66 | PublishTopic="somewhere" 67 | 68 | [ApplicationSettings] 69 | ValueDescriptors = "Image" 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /app-services/simple-filter-xml/EdgeX Applications Function SDK.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "f6c30e8c-639b-49a5-853f-fdf4145a6274", 4 | "name": "EdgeX Applications Function SDK", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "CoreData Event Trigger (Random-Float-Device)", 10 | "request": { 11 | "auth": { 12 | "type": "noauth" 13 | }, 14 | "method": "POST", 15 | "header": [], 16 | "body": { 17 | "mode": "raw", 18 | "raw": "{\n \"id\": \"5bd794de0e36080001f53eab\",\n \"pushed\": 0,\n \"device\": \"Random-Float-Device\",\n \"created\": 1540855006481,\n \"modified\": 0,\n \"origin\": 1540855006469,\n \"schedule\": null,\n \"event\": null,\n \"readings\": [\n {\n \"id\": \"5bd794de0e36080001f53eac\",\n \"pushed\": 0,\n \"created\": 1540855006481,\n \"origin\": 1540855006469,\n \"modified\": 0,\n \"device\": \"Random-Float-Device\",\n \"name\": \"RandomValue_Float64\",\n \"value\": \"QAFk2HxRUOo=\"\n }\n ]\n}" 19 | }, 20 | "url": { 21 | "raw": "127.0.0.1:9090", 22 | "host": [ 23 | "127", 24 | "0", 25 | "0", 26 | "1" 27 | ], 28 | "port": "9090" 29 | }, 30 | "description": "This request will send a valid EdgeX event to Applications Function SDK via HTTP in order to trigger a configure pipeline. The default is :9090." 31 | }, 32 | "response": [] 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /app-services/simple-filter-xml/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019 Intel Corporation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | FROM golang:1.12-alpine AS builder 18 | 19 | LABEL license='SPDX-License-Identifier: Apache-2.0' \ 20 | copyright='Copyright (c) 2019: Intel' 21 | 22 | # add git for go modules 23 | RUN apk update && apk add --no-cache make git gcc libc-dev libsodium-dev zeromq-dev 24 | WORKDIR /app-service-examples 25 | 26 | COPY go.mod . 27 | 28 | RUN go mod download 29 | 30 | COPY . . 31 | RUN apk info -a zeromq-dev 32 | 33 | RUN make app-services/simple-filter-xml 34 | 35 | # Next image - Copy built Go binary into new workspace 36 | FROM alpine 37 | 38 | LABEL license='SPDX-License-Identifier: Apache-2.0' \ 39 | copyright='Copyright (c) 2019: Intel' 40 | 41 | RUN apk --no-cache add zeromq 42 | 43 | COPY --from=builder /app-service-examples/app-services/simple-filter-xml/res /res 44 | COPY --from=builder /app-service-examples/app-services/simple-filter-xml/app-service /simple-filter-xml 45 | 46 | CMD [ "/simple-filter-xml" ,"--registry","--confdir=/res"] -------------------------------------------------------------------------------- /app-services/advanced-filter-convert-publish/res/configuration.toml: -------------------------------------------------------------------------------- 1 | [Writable] 2 | LogLevel = 'INFO' 3 | [Writable.StoreAndForward] 4 | Enabled = false 5 | RetryInterval = '5m' 6 | MaxRetryCount = 10 7 | 8 | [Service] 9 | BootTimeout = '30s' 10 | ClientMonitor = '15s' 11 | CheckInterval = '10s' 12 | Host = 'localhost' 13 | Port = 48095 14 | Protocol = 'http' 15 | ReadMaxLimit = 100 16 | StartupMsg = 'This is a sample Application Service which filters, custom converts, prints and publishes back to message bus' 17 | Timeout = '30s' 18 | 19 | [Registry] 20 | Host = 'localhost' 21 | Port = 8500 22 | Type = 'consul' 23 | 24 | [Logging] 25 | EnableRemote = false 26 | File = './logs/simple-filter-xml-mqtt.log' 27 | 28 | [Database] 29 | Type = "mongodb" 30 | Host = "localhost" 31 | Port = 27017 32 | Timeout = "30s" 33 | Username = "" 34 | Password = "" 35 | 36 | [Clients] 37 | [Clients.CoreData] 38 | Protocol = 'http' 39 | Host = 'localhost' 40 | Port = 48080 41 | 42 | [Clients.Logging] 43 | Protocol = "http" 44 | Host = "localhost" 45 | Port = 48061 46 | 47 | [MessageBus] 48 | Type = 'zero' 49 | [MessageBus.PublishHost] 50 | Host = '*' 51 | Port = 5564 52 | Protocol = 'tcp' 53 | [MessageBus.SubscribeHost] 54 | Host = 'localhost' 55 | Port = 5563 56 | Protocol = 'tcp' 57 | 58 | 59 | # This example depends on events generated by Device-Virtual-Go, so must use MessageBus trigger. 60 | # It will publish back to the bus on the "converted" topic 61 | [Binding] 62 | Type="messagebus" 63 | SubscribeTopic="events" 64 | PublishTopic="converted" 65 | 66 | [ApplicationSettings] 67 | ApplicationName = "advanced-filter-convert-publish" 68 | ValueDescriptors = "RandomValue_Float32, RandomValue_Float64" 69 | -------------------------------------------------------------------------------- /app-services/simple-filter-xml/res/configuration.toml: -------------------------------------------------------------------------------- 1 | [Writable] 2 | LogLevel = 'INFO' 3 | [Writable.StoreAndForward] 4 | Enabled = false 5 | RetryInterval = '5m' 6 | MaxRetryCount = 10 7 | 8 | [Service] 9 | BootTimeout = '30s' 10 | ClientMonitor = '15s' 11 | CheckInterval = '10s' 12 | Host = 'localhost' 13 | Port = 48095 14 | Protocol = 'http' 15 | ReadMaxLimit = 100 16 | StartupMsg = 'This is a sample Filter/XML Transform Application Service' 17 | Timeout = '30s' 18 | 19 | [Registry] 20 | Host = 'localhost' 21 | Port = 8500 22 | Type = 'consul' 23 | 24 | [Logging] 25 | EnableRemote = false 26 | File = './logs/simple-filter-xml-mqtt.log' 27 | 28 | [Database] 29 | Type = "mongodb" 30 | Host = "localhost" 31 | Port = 27017 32 | Timeout = "30s" 33 | Username = "" 34 | Password = "" 35 | 36 | [SecretStore] 37 | Host = 'localhost' 38 | Port = 8200 39 | Path = '/v1/secret/edgex/appservice/' 40 | Protocol = 'https' 41 | 42 | [SecretStore.Authentication] 43 | AuthType = 'X-Vault-Token' 44 | AuthToken = 'edgex' 45 | 46 | [Clients] 47 | [Clients.CoreData] 48 | Protocol = 'http' 49 | Host = 'localhost' 50 | Port = 48080 51 | 52 | [Clients.Logging] 53 | Protocol = "http" 54 | Host = "localhost" 55 | Port = 48061 56 | 57 | [MessageBus] 58 | Type = 'zero' 59 | [MessageBus.PublishHost] 60 | Host = '*' 61 | Port = 5564 62 | Protocol = 'tcp' 63 | [MessageBus.SubscribeHost] 64 | Host = 'localhost' 65 | Port = 5563 66 | Protocol = 'tcp' 67 | 68 | # Choose either an HTTP trigger or MessageBus trigger (aka Binding) 69 | 70 | #[Binding] 71 | #Type="http" 72 | 73 | [Binding] 74 | Type="messagebus" 75 | SubscribeTopic="events" 76 | PublishTopic="somewhere" 77 | 78 | [ApplicationSettings] 79 | ApplicationName = "simple-filter-xml" 80 | -------------------------------------------------------------------------------- /app-services/http-command-service/iot-hub/Quickstarts/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | com.microsoft.docs.iothub.samples 5 | quickstarts 6 | IoT Hub Java quickstart samples 7 | 1.0 8 | pom 9 | 10 | 12 | proxy-device 13 | back-end-application 14 | 15 | 16 | 17 | 18 | org.apache.maven.plugins 19 | maven-compiler-plugin 20 | 3.3 21 | 22 | 1.8 23 | 1.8 24 | 25 | 26 | 27 | maven-shade-plugin 28 | 2.4 29 | 30 | 31 | package 32 | 33 | shade 34 | 35 | 36 | 37 | 38 | *:* 39 | 40 | META-INF/*.SF 41 | META-INF/*.RSA 42 | 43 | 44 | 45 | true 46 | with-deps 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /app-services/http-command-service/iot-hub/Quickstarts/proxy-device/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.microsoft.docs.iothub.samples 6 | quickstarts 7 | 1.0 8 | 9 | com.microsoft.docs.iothub.samples 10 | proxy-device 11 | 1.0.0 12 | Proxy device 13 | 14 | 15 | com.microsoft.azure.sdk.iot 16 | iot-device-client 17 | 1.16.0 18 | 19 | 20 | com.google.code.gson 21 | gson 22 | 2.3.1 23 | 24 | 25 | com.google.http-client 26 | google-http-client 27 | 1.23.0 28 | 29 | 30 | com.google.http-client 31 | google-http-client-gson 32 | 1.23.0 33 | 34 | 35 | 36 | UTF-8 37 | 38 | 39 | 40 | 41 | org.apache.maven.plugins 42 | maven-jar-plugin 43 | 2.6 44 | 45 | 46 | 47 | true 48 | com.microsoft.docs.iothub.samples.ProxyDevice 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /app-services/advanced-filter-convert-publish/functions/output.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package functions 18 | 19 | import ( 20 | "encoding/json" 21 | "fmt" 22 | 23 | "github.com/edgexfoundry/app-functions-sdk-go/appcontext" 24 | "github.com/edgexfoundry/go-mod-core-contracts/models" 25 | ) 26 | 27 | func PrintFloatValuesToConsole(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) { 28 | if len(params) < 1 { 29 | // We didn't receive a result 30 | return false, nil 31 | } 32 | 33 | event := params[0].(models.Event) 34 | 35 | for _, eventReading := range event.Readings { 36 | fmt.Printf("%s readable value from %s is %s\n", eventReading.Name, event.Device, eventReading.Value) 37 | } 38 | 39 | return true, event 40 | 41 | } 42 | 43 | func Publish(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) { 44 | 45 | edgexcontext.LoggingClient.Debug("Publish") 46 | 47 | if len(params) < 1 { 48 | // We didn't receive a result 49 | return false, nil 50 | } 51 | 52 | event := params[0].(models.Event) 53 | payload, _ := json.Marshal(event) 54 | 55 | // By calling Complete, the filtered and converted events will be posted back to the message bus on the new topic defined in the configuration. 56 | edgexcontext.Complete(payload) 57 | 58 | return false, nil 59 | } 60 | -------------------------------------------------------------------------------- /app-services/simple-filter-xml-mqtt/res/configuration.toml: -------------------------------------------------------------------------------- 1 | [Writable] 2 | LogLevel = 'INFO' 3 | [Writable.StoreAndForward] 4 | Enabled = false 5 | RetryInterval = '5m' 6 | MaxRetryCount = 10 7 | 8 | [Service] 9 | BootTimeout = '30s' 10 | ClientMonitor = '15s' 11 | CheckInterval = '10s' 12 | Host = 'localhost' 13 | Port = 48095 14 | Protocol = 'http' 15 | ReadMaxLimit = 100 16 | StartupMsg = 'This is a sample Filter/XML/MQTT Transform Application Service' 17 | Timeout = '30s' 18 | 19 | [Registry] 20 | Host = 'localhost' 21 | Port = 8500 22 | Type = 'consul' 23 | 24 | [Logging] 25 | EnableRemote = false 26 | File = './logs/simple-filter-xml-mqtt.log' 27 | 28 | # Database is require when Store and Forward is enabled 29 | [Database] 30 | Type = "mongodb" 31 | Host = "localhost" 32 | Port = 27017 33 | Timeout = "30s" 34 | Username = "" 35 | Password = "" 36 | 37 | # SecretStore is required when Store and Forward is enabled and running with security 38 | # so Databse credentails can be pulled from Vault. 39 | [SecretStore] 40 | Host = 'localhost' 41 | Port = 8200 42 | Path = '/v1/secret/edgex/application-service/' 43 | Protocol = 'https' 44 | 45 | [SecretStore.Authentication] 46 | AuthType = 'X-Vault-Token' 47 | AuthToken = 'edgex' 48 | 49 | [Clients] 50 | [Clients.CoreData] 51 | Protocol = 'http' 52 | Host = 'localhost' 53 | Port = 48080 54 | 55 | [Clients.Logging] 56 | Protocol = "http" 57 | Host = "localhost" 58 | Port = 48061 59 | 60 | [MessageBus] 61 | Type = 'zero' 62 | [MessageBus.PublishHost] 63 | Host = '*' 64 | Port = 5564 65 | Protocol = 'tcp' 66 | [MessageBus.SubscribeHost] 67 | Host = 'localhost' 68 | Port = 5563 69 | Protocol = 'tcp' 70 | 71 | # Choose either an HTTP trigger or MessageBus trigger (aka Binding) 72 | 73 | #[Binding] 74 | #Type="http" 75 | 76 | [Binding] 77 | Type="messagebus" 78 | SubscribeTopic="events" 79 | PublishTopic="somewhere" 80 | 81 | [ApplicationSettings] 82 | ApplicationName = "simple-filter-xml-mqtt" 83 | -------------------------------------------------------------------------------- /app-services/simple-filter-xml-post/res/configuration.toml: -------------------------------------------------------------------------------- 1 | [Writable] 2 | LogLevel = 'INFO' 3 | [Writable.StoreAndForward] 4 | Enabled = false 5 | RetryInterval = '5m' 6 | MaxRetryCount = 10 7 | 8 | [Service] 9 | BootTimeout = '30s' 10 | ClientMonitor = '15s' 11 | CheckInterval = '10s' 12 | Host = 'localhost' 13 | Port = 48095 14 | Protocol = 'http' 15 | ReadMaxLimit = 100 16 | StartupMsg = 'This is a sample Filter/XML/Post Transform Application Service' 17 | Timeout = '30s' 18 | 19 | [Registry] 20 | Host = 'localhost' 21 | Port = 8500 22 | Type = 'consul' 23 | 24 | # Database is require when Store and Forward is enabled 25 | [Database] 26 | Type = "mongodb" 27 | Host = "localhost" 28 | Port = 27017 29 | Timeout = "30s" 30 | Username = "" 31 | Password = "" 32 | 33 | # SecretStore is required when Store and Forward is enabled and running with security 34 | # so Databse credentails can be pulled from Vault. 35 | [SecretStore] 36 | Host = 'localhost' 37 | Port = 8200 38 | Path = '/v1/secret/edgex/application-service/' 39 | Protocol = 'https' 40 | 41 | [SecretStore.Authentication] 42 | AuthType = 'X-Vault-Token' 43 | AuthToken = 'edgex' 44 | 45 | [Logging] 46 | EnableRemote = false 47 | File = './logs/simple-filter-xml-post.log' 48 | 49 | [Clients] 50 | [Clients.CoreData] 51 | Protocol = 'http' 52 | Host = 'localhost' 53 | Port = 48080 54 | 55 | [Clients.Logging] 56 | Protocol = "http" 57 | Host = "localhost" 58 | Port = 48061 59 | 60 | # Required when using Store and Forward 61 | [Clients.Scheduler] 62 | Protocol = 'http' 63 | Host = 'localhost' 64 | Port = 48085 65 | 66 | [MessageBus] 67 | Type = 'zero' 68 | [MessageBus.PublishHost] 69 | Host = '*' 70 | Port = 5563 71 | Protocol = 'tcp' 72 | [MessageBus.SubscribeHost] 73 | Host = 'localhost' 74 | Port = 5563 75 | Protocol = 'tcp' 76 | 77 | # Choose either an HTTP trigger or MessageBus trigger (aka Binding) 78 | 79 | #[Binding] 80 | #Type="http" 81 | 82 | [Binding] 83 | Type="messagebus" 84 | SubscribeTopic="events" 85 | PublishTopic="somewhere" 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # app-service-examples 2 | 3 | ### Overview 4 | 5 | This repo contains various examples of Application Services based on the App Functions SDK. See the App Functions SDK for [README](https://github.com/edgexfoundry/app-functions-sdk-go/blob/v1.0.0-dev.2/README.md) for complete details on the SDK. 6 | 7 | ### Build Prerequisites 8 | 9 | Please see the [edgex-go README](https://github.com/edgexfoundry/edgex-go/blob/master/README.md). 10 | 11 | ### Building Examples 12 | 13 | The `makefile` is designed to build all/any examples under the `app-services` folder. Thus the makefile does not need to be updated when a new example is added to the `app-services` folder 14 | 15 | ​ run `make build` to build all examples. 16 | 17 | ​ run `make app-services/` to build a specific example, i.e. `make app-services/simple-filter-xml` 18 | 19 | For simplicity, the executable create for each example is named `app-service` and placed in that examples sub-folder. 20 | 21 | ### Running an Example 22 | 23 | After building examples you simply cd to the folder for the example you want to run and run the executable for that example with or without any of the supported command line options. 24 | 25 | The following commands will run the `simple-filter-xml` example 26 | 27 | ​ run `cd app-services/simple-filter-xml` 28 | 29 | ​ run `./app-service` 30 | 31 | ### Building App Service Docker Image 32 | 33 | The `simple-filter-xml` example contains an example `Dockerfile` to demonstrate how to build a **Docker Image** for your Application Service. 34 | 35 | The makefile also contains the `docker` target which will build the **Docker Image** for the `simple-filter-xml` example. 36 | 37 | ​ run `make docker` 38 | 39 | > *Note that Application Services no longer use docker profiles. They use Environment Overrides in the docker compose file to make the necessary changes to the configuration for running in Docker. See the **Environment Variable Overrides For Docker** section in [App Service Configurable's README](https://github.com/edgexfoundry/app-service-configurable/blob/master/README.md#environment-variable-overrides-for-docker)* for more details and an example. 40 | 41 | ### Profiles 42 | 43 | The profiles folder contains example profiles for use with App Service Configurable. -------------------------------------------------------------------------------- /app-services/http-command-service/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/edgexfoundry-holding/app-service-examples/app-services/http-command-service/functions" 24 | "github.com/edgexfoundry/app-functions-sdk-go/appsdk" 25 | "github.com/edgexfoundry/app-functions-sdk-go/pkg/transforms" 26 | ) 27 | 28 | const ( 29 | serviceKey = "httpCommandService" 30 | ) 31 | 32 | func main() { 33 | // 1) First thing to do is to create an instance of the EdgeX SDK with TargetType Switch 34 | // and initialize it. 35 | edgexSdk := &appsdk.AppFunctionsSDK{ServiceKey: serviceKey, TargetType: &functions.Switch{}} 36 | if err := edgexSdk.Initialize(); err != nil { 37 | edgexSdk.LoggingClient.Error(fmt.Sprintf("SDK initialization failed: %v\n", err)) 38 | os.Exit(-1) 39 | } 40 | 41 | // 2) This is our functions pipeline configuration, the collection of functions to 42 | // execute every time an event is triggered. 43 | err := edgexSdk.SetFunctionsPipeline( 44 | functions.SendSwitchCommand, 45 | functions.PrintToConsole, 46 | transforms.NewOutputData().SetOutputData, 47 | ) 48 | 49 | if err != nil { 50 | edgexSdk.LoggingClient.Error("Setting Functions Pipeline failed: " + err.Error()) 51 | os.Exit(-1) 52 | } 53 | 54 | // 3) Lastly, we'll go ahead and tell the SDK to "start" and begin listening for Persons 55 | // to trigger the pipeline. 56 | err = edgexSdk.MakeItRun() 57 | if err != nil { 58 | edgexSdk.LoggingClient.Error("MakeItRun returned error: ", err.Error()) 59 | os.Exit(-1) 60 | } 61 | 62 | // Do any required cleanup here 63 | os.Exit(0) 64 | } 65 | -------------------------------------------------------------------------------- /app-services/simple-filter-xml-post/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/edgexfoundry/app-functions-sdk-go/pkg/transforms" 24 | 25 | "github.com/edgexfoundry/app-functions-sdk-go/appsdk" 26 | ) 27 | 28 | const ( 29 | serviceKey = "sampleFilterXmlPost" 30 | ) 31 | 32 | func main() { 33 | // 1) First thing to do is to create an instance of the EdgeX SDK and initialize it. 34 | edgexSdk := &appsdk.AppFunctionsSDK{ServiceKey: serviceKey} 35 | if err := edgexSdk.Initialize(); err != nil { 36 | edgexSdk.LoggingClient.Error(fmt.Sprintf("SDK initialization failed: %v\n", err)) 37 | os.Exit(-1) 38 | } 39 | 40 | // 2) Since our FilterByDeviceName Function requires the list of device names we would 41 | // like to search for, we'll go ahead and define that now. 42 | deviceNames := []string{"Random-Float-Device"} 43 | // 3) This is our pipeline configuration, the collection of functions to 44 | // execute every time an event is triggered. 45 | edgexSdk.SetFunctionsPipeline( 46 | transforms.NewFilter(deviceNames).FilterByDeviceName, 47 | transforms.NewConversion().TransformToXML, 48 | transforms.NewHTTPSender("", "application/xml", false).HTTPPost, 49 | ) 50 | 51 | // 4) This example doesn't have any application's specific configuration settings. Skipping call to sdk.ApplicationSettings 52 | 53 | // 5) Lastly, we'll go ahead and tell the SDK to "start" and begin listening for events 54 | // to trigger the pipeline. 55 | err := edgexSdk.MakeItRun() 56 | if err != nil { 57 | edgexSdk.LoggingClient.Error("MakeItRun returned error: ", err.Error()) 58 | os.Exit(-1) 59 | } 60 | 61 | // Do any required cleanup here 62 | 63 | os.Exit(0) 64 | } 65 | -------------------------------------------------------------------------------- /app-services/advanced-target-type/functions/convert.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package functions 18 | 19 | import ( 20 | "encoding/xml" 21 | "fmt" 22 | 23 | "github.com/edgexfoundry/app-functions-sdk-go/appcontext" 24 | ) 25 | 26 | type PhoneInfo struct { 27 | CountryCode int `json:"country_code"` 28 | AreaCode int `json:"area_code"` 29 | LocalPrefix int `json:"local_prefix"` 30 | LocalNumber int `json:"local_number"` 31 | } 32 | 33 | type Person struct { 34 | FirstName string `json:"first_name"` 35 | LastName string `json:"last_name"` 36 | Phone PhoneInfo `json:"phone"` 37 | PhoneDisplay string `json:"phone_display"` 38 | } 39 | 40 | func FormatPhoneDisplay(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) { 41 | 42 | edgexcontext.LoggingClient.Debug("Format Phone Number") 43 | 44 | if len(params) < 1 { 45 | // We didn't receive a result 46 | return false, nil 47 | } 48 | 49 | person, ok := params[0].(Person) 50 | if !ok { 51 | edgexcontext.LoggingClient.Error("type received is not a Person") 52 | } 53 | 54 | person.PhoneDisplay = fmt.Sprintf("+%02d(%03d) %03d-%04d", 55 | person.Phone.CountryCode, person.Phone.AreaCode, person.Phone.LocalPrefix, person.Phone.LocalNumber) 56 | 57 | return true, person 58 | } 59 | 60 | func ConvertToXML(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) { 61 | edgexcontext.LoggingClient.Debug("Convert to XML") 62 | 63 | if len(params) < 1 { 64 | // We didn't receive a result 65 | return false, nil 66 | } 67 | 68 | person, ok := params[0].(Person) 69 | if !ok { 70 | edgexcontext.LoggingClient.Error("type received is not a Person") 71 | } 72 | 73 | result, err := xml.MarshalIndent(person, "", " ") 74 | if err != nil { 75 | return false, fmt.Sprintf("Error parsing XML. Error: %s", err.Error()) 76 | } 77 | 78 | return true, string(result) 79 | } 80 | -------------------------------------------------------------------------------- /app-services/advanced-target-type/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/edgexfoundry/app-functions-sdk-go/appsdk" 24 | "github.com/edgexfoundry/app-functions-sdk-go/examples/advanced-target-type/functions" 25 | "github.com/edgexfoundry/app-functions-sdk-go/pkg/transforms" 26 | ) 27 | 28 | const ( 29 | serviceKey = "advancedTargetType" 30 | ) 31 | 32 | func main() { 33 | // 1) First thing to do is to create an instance of the EdgeX SDK with your TargetType set 34 | // and initialize it. Note that the TargetType is a pointer to an instance of the type. 35 | edgexSdk := &appsdk.AppFunctionsSDK{ServiceKey: serviceKey, TargetType: &functions.Person{}} 36 | if err := edgexSdk.Initialize(); err != nil { 37 | edgexSdk.LoggingClient.Error(fmt.Sprintf("SDK initialization failed: %v\n", err)) 38 | os.Exit(-1) 39 | } 40 | 41 | // 2) This is our functions pipeline configuration, the collection of functions to 42 | // execute every time an event is triggered. 43 | err := edgexSdk.SetFunctionsPipeline( 44 | functions.FormatPhoneDisplay, // Expects a Person as set by TargetType 45 | functions.ConvertToXML, // Expects a Person 46 | functions.PrintXmlToConsole, // Expects XML string 47 | transforms.NewOutputData().SetOutputData, // Expects string or []byte. Returns XML formatted Person with PhoneDisplay set sent as the trigger response 48 | ) 49 | 50 | if err != nil { 51 | edgexSdk.LoggingClient.Error("Setting Functions Pipeline failed: " + err.Error()) 52 | os.Exit(-1) 53 | } 54 | 55 | // 3) Lastly, we'll go ahead and tell the SDK to "start" and begin listening for Persons 56 | // to trigger the pipeline. 57 | err = edgexSdk.MakeItRun() 58 | if err != nil { 59 | edgexSdk.LoggingClient.Error("MakeItRun returned error: ", err.Error()) 60 | os.Exit(-1) 61 | } 62 | 63 | // Do any required cleanup here 64 | 65 | os.Exit(0) 66 | } 67 | -------------------------------------------------------------------------------- /app-services/http-command-service/functions/switch_cmd.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package functions 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/edgexfoundry/app-functions-sdk-go/appcontext" 23 | ) 24 | 25 | const ( 26 | jsonSwitchOn = "{\"SwitchButton\": \"true\"}" 27 | jsonSwitchOff = "{\"SwitchButton\": \"false\"}" 28 | 29 | appConfigDeviceID = "DeviceID" 30 | appConfigCommandID = "CommandID" 31 | ) 32 | 33 | type Switch struct { 34 | Status string `json:"status"` 35 | } 36 | 37 | func SendSwitchCommand(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) { 38 | edgexcontext.LoggingClient.Debug("Sending Switch Command") 39 | 40 | if len(params) < 1 { 41 | // We didn't receive a result 42 | return false, nil 43 | } 44 | 45 | if edgexcontext.CommandClient == nil { 46 | edgexcontext.LoggingClient.Error("Command client is available") 47 | return false, nil 48 | } 49 | 50 | sw, ok := params[0].(Switch) 51 | 52 | if !ok { 53 | edgexcontext.LoggingClient.Error("Invalid switch") 54 | return false, nil 55 | } 56 | 57 | deviceId := edgexcontext.Configuration.ApplicationSettings[appConfigDeviceID] 58 | commandId := edgexcontext.Configuration.ApplicationSettings[appConfigCommandID] 59 | var cmd string 60 | 61 | ctx := context.WithValue(context.Background(), "", "") 62 | 63 | switch status := sw.Status; status { 64 | case "on": 65 | edgexcontext.LoggingClient.Info("Switch On") 66 | cmd = jsonSwitchOn 67 | case "off": 68 | edgexcontext.LoggingClient.Info("Switch Off") 69 | cmd = jsonSwitchOff 70 | default: 71 | edgexcontext.LoggingClient.Error("Invalid switch status: " + status) 72 | return false, nil 73 | } 74 | 75 | edgexcontext.LoggingClient.Info("Device ID: " + deviceId) 76 | edgexcontext.LoggingClient.Info("Command ID: " + commandId) 77 | r, err := edgexcontext.CommandClient.Put(deviceId, commandId, cmd, ctx) 78 | 79 | if err == nil { 80 | edgexcontext.LoggingClient.Debug("Response : " + r) 81 | } else { 82 | edgexcontext.LoggingClient.Error("Error sending request: " + err.Error()) 83 | 84 | } 85 | 86 | return true, cmd 87 | } 88 | -------------------------------------------------------------------------------- /app-services/advanced-filter-convert-publish/functions/convert.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package functions 18 | 19 | import ( 20 | "bytes" 21 | "encoding/base64" 22 | "encoding/binary" 23 | "fmt" 24 | "strconv" 25 | 26 | "github.com/edgexfoundry/app-functions-sdk-go/appcontext" 27 | "github.com/edgexfoundry/go-mod-core-contracts/models" 28 | ) 29 | 30 | var precision = 4 31 | 32 | func ConvertToReadableFloatValues(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) { 33 | 34 | edgexcontext.LoggingClient.Debug("Convert to Readable Float Values") 35 | 36 | if len(params) < 1 { 37 | // We didn't receive a result 38 | return false, nil 39 | } 40 | 41 | event := params[0].(models.Event) 42 | for index := range event.Readings { 43 | eventReading := &event.Readings[index] 44 | edgexcontext.LoggingClient.Debug(fmt.Sprintf("Event Reading for %s: %s is '%s'", event.Device, eventReading.Name, eventReading.Value)) 45 | 46 | data, err := base64.StdEncoding.DecodeString(eventReading.Value) 47 | if err != nil { 48 | edgexcontext.LoggingClient.Error(fmt.Sprintf("Unable to Base 64 decode float32/64 value ('%s'): %s", eventReading.Value, err.Error())) 49 | } 50 | 51 | switch eventReading.Name { 52 | case "RandomValue_Float32": 53 | var value float32 54 | err = binary.Read(bytes.NewReader(data), binary.BigEndian, &value) 55 | if err != nil { 56 | edgexcontext.LoggingClient.Error("Unable to decode float32 value bytes" + err.Error()) 57 | } 58 | 59 | eventReading.Value = strconv.FormatFloat(float64(value), 'f', precision, 32) 60 | 61 | case "RandomValue_Float64": 62 | var value float64 63 | err := binary.Read(bytes.NewReader(data), binary.BigEndian, &value) 64 | if err != nil { 65 | edgexcontext.LoggingClient.Error("Unable to decode float64 value bytes: " + err.Error()) 66 | } 67 | 68 | eventReading.Value = strconv.FormatFloat(value, 'f', precision, 64) 69 | } 70 | 71 | edgexcontext.LoggingClient.Debug(fmt.Sprintf("Converted Event Reading for %s: %s is '%s'", event.Device, eventReading.Name, eventReading.Value)) 72 | } 73 | 74 | return true, event 75 | } 76 | -------------------------------------------------------------------------------- /app-services/simple-cbor-filter/Device Simple Switch commands.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "7c5f760b-8466-46a1-afde-f2c7c0227620", 4 | "name": "Device Simple Switch commands", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "Get Switch status", 10 | "protocolProfileBehavior": { 11 | "disableBodyPruning": true 12 | }, 13 | "request": { 14 | "method": "GET", 15 | "header": [], 16 | "body": { 17 | "mode": "raw", 18 | "raw": "" 19 | }, 20 | "url": { 21 | "raw": "http://localhost:48082/api/v1/device/name/Simple-Device01/command/Switch", 22 | "protocol": "http", 23 | "host": [ 24 | "localhost" 25 | ], 26 | "port": "48082", 27 | "path": [ 28 | "api", 29 | "v1", 30 | "device", 31 | "name", 32 | "Simple-Device01", 33 | "command", 34 | "Switch" 35 | ] 36 | } 37 | }, 38 | "response": [] 39 | }, 40 | { 41 | "name": "Turn Switch on", 42 | "request": { 43 | "method": "PUT", 44 | "header": [ 45 | { 46 | "key": "Content-Type", 47 | "name": "Content-Type", 48 | "value": "application/json", 49 | "type": "text" 50 | } 51 | ], 52 | "body": { 53 | "mode": "raw", 54 | "raw": "{\n\t\"SwitchButton\": \"true\"\n}" 55 | }, 56 | "url": { 57 | "raw": "http://localhost:48082/api/v1/device/name/Simple-Device01/command/Switch", 58 | "protocol": "http", 59 | "host": [ 60 | "localhost" 61 | ], 62 | "port": "48082", 63 | "path": [ 64 | "api", 65 | "v1", 66 | "device", 67 | "name", 68 | "Simple-Device01", 69 | "command", 70 | "Switch" 71 | ] 72 | }, 73 | "description": "Sends Switch command to turn on the switch" 74 | }, 75 | "response": [] 76 | }, 77 | { 78 | "name": "Turn Switch off", 79 | "request": { 80 | "method": "PUT", 81 | "header": [ 82 | { 83 | "key": "Content-Type", 84 | "name": "Content-Type", 85 | "value": "application/json", 86 | "type": "text" 87 | } 88 | ], 89 | "body": { 90 | "mode": "raw", 91 | "raw": "{\n\t\"SwitchButton\": \"false\"\n}" 92 | }, 93 | "url": { 94 | "raw": "http://localhost:48082/api/v1/device/name/Simple-Device01/command/Switch", 95 | "protocol": "http", 96 | "host": [ 97 | "localhost" 98 | ], 99 | "port": "48082", 100 | "path": [ 101 | "api", 102 | "v1", 103 | "device", 104 | "name", 105 | "Simple-Device01", 106 | "command", 107 | "Switch" 108 | ] 109 | }, 110 | "description": "Sends Switch command to turn off the switch" 111 | }, 112 | "response": [] 113 | } 114 | ] 115 | } -------------------------------------------------------------------------------- /app-services/http-command-service/iot-hub/Quickstarts/back-end-application/src/main/java/com/microsoft/docs/iothub/samples/BackEndApplication.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | // This application uses the Azure IoT Hub service SDK for Java 5 | // For samples see: https://github.com/Azure/azure-iot-sdk-java/tree/master/service/iot-service-samples 6 | 7 | package com.microsoft.docs.iothub.samples; 8 | 9 | import com.microsoft.azure.sdk.iot.service.devicetwin.DeviceMethod; 10 | import com.microsoft.azure.sdk.iot.service.devicetwin.MethodResult; 11 | import com.microsoft.azure.sdk.iot.service.exceptions.IotHubException; 12 | 13 | import java.io.IOException; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | public class BackEndApplication { 17 | 18 | // Connection string for your IoT Hub 19 | // az iot hub show-connection-string --hub-name {your iot hub name} --policy-name service 20 | public static final String iotHubConnectionString = "{Your service connection string here}"; 21 | 22 | // Device to call direct method on. 23 | public static final String deviceId = "{Your device Id here}"; 24 | 25 | private static final String TURN_ON = "turn-on"; 26 | private static final String TURN_OFF = "turn-off"; 27 | 28 | // Name of direct method and payload. 29 | private static String methodName = "SetTelemetryInterval"; 30 | public static final int payload = 10; // Number of seconds for telemetry interval. 31 | 32 | public static final Long responseTimeout = TimeUnit.SECONDS.toSeconds(30); 33 | public static final Long connectTimeout = TimeUnit.SECONDS.toSeconds(5); 34 | 35 | public static void main(String[] args) { 36 | try { 37 | // Create a DeviceMethod instance to call a direct method. 38 | DeviceMethod methodClient = DeviceMethod.createFromConnectionString(iotHubConnectionString); 39 | 40 | MethodResult result = null; 41 | 42 | if (args.length == 1) { 43 | switch (args[0]) { 44 | case TURN_ON: 45 | methodName = TURN_ON; 46 | break; 47 | case TURN_OFF: 48 | methodName = TURN_OFF; 49 | break; 50 | } 51 | } 52 | 53 | // Call the direct method. 54 | result = methodClient.invoke(deviceId, methodName, responseTimeout, connectTimeout, payload); 55 | 56 | System.out.println("Calling direct method " + methodName); 57 | 58 | if (result == null) { 59 | throw new IOException("Direct method invoke returns null"); 60 | } 61 | 62 | // Show the acknowledgement from the device. 63 | System.out.println("Status: " + result.getStatus()); 64 | System.out.println("Response: " + result.getPayload()); 65 | } catch (IotHubException e) { 66 | System.out.println("IotHubException calling direct method:"); 67 | System.out.println(e.getMessage()); 68 | } catch (IOException e) { 69 | System.out.println("IOException calling direct method:"); 70 | System.out.println(e.getMessage()); 71 | } 72 | System.out.println("Done!"); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app-services/simple-filter-xml/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/edgexfoundry/app-functions-sdk-go/appcontext" 24 | "github.com/edgexfoundry/app-functions-sdk-go/appsdk" 25 | "github.com/edgexfoundry/app-functions-sdk-go/pkg/transforms" 26 | ) 27 | 28 | const ( 29 | serviceKey = "sampleFilterXml" 30 | ) 31 | 32 | var counter int 33 | 34 | func main() { 35 | 36 | // 1) First thing to do is to create an instance of the EdgeX SDK and initialize it. 37 | edgexSdk := &appsdk.AppFunctionsSDK{ServiceKey: serviceKey} 38 | if err := edgexSdk.Initialize(); err != nil { 39 | edgexSdk.LoggingClient.Error(fmt.Sprintf("SDK initialization failed: %v\n", err)) 40 | os.Exit(-1) 41 | } 42 | 43 | // 2) Since our FilterByDeviceName Function requires the list of device names we would 44 | // like to search for, we'll go ahead and define that now. 45 | deviceNames := []string{"Random-Float-Device"} 46 | 47 | // 3) This is our pipeline configuration, the collection of functions to 48 | // execute every time an event is triggered. 49 | edgexSdk.SetFunctionsPipeline( 50 | transforms.NewFilter(deviceNames).FilterByDeviceName, 51 | transforms.NewConversion().TransformToXML, 52 | printXMLToConsole, 53 | ) 54 | 55 | // 4) shows how to access the application's specific configuration settings. 56 | appSettings := edgexSdk.ApplicationSettings() 57 | if appSettings != nil { 58 | appName, ok := appSettings["ApplicationName"] 59 | if ok { 60 | edgexSdk.LoggingClient.Info(fmt.Sprintf("%s now running...", appName)) 61 | } else { 62 | edgexSdk.LoggingClient.Error("ApplicationName application setting not found") 63 | os.Exit(-1) 64 | } 65 | } else { 66 | edgexSdk.LoggingClient.Error("No application settings found") 67 | os.Exit(-1) 68 | } 69 | 70 | // 5) Lastly, we'll go ahead and tell the SDK to "start" and begin listening for events 71 | // to trigger the pipeline. 72 | err := edgexSdk.MakeItRun() 73 | if err != nil { 74 | edgexSdk.LoggingClient.Error("MakeItRun returned error: ", err.Error()) 75 | os.Exit(-1) 76 | } 77 | 78 | // Do any required cleanup here 79 | 80 | os.Exit(0) 81 | } 82 | 83 | func printXMLToConsole(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) { 84 | if len(params) < 1 { 85 | // We didn't receive a result 86 | return false, nil 87 | } 88 | 89 | fmt.Println(params[0].(string)) 90 | 91 | // Leverage the built in logging service in EdgeX 92 | edgexcontext.LoggingClient.Debug("XML printed to console") 93 | 94 | edgexcontext.Complete([]byte(params[0].(string))) 95 | return false, nil 96 | } 97 | -------------------------------------------------------------------------------- /app-services/advanced-filter-convert-publish/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "strings" 23 | 24 | "github.com/edgexfoundry/app-functions-sdk-go/pkg/transforms" 25 | 26 | "github.com/edgexfoundry/app-functions-sdk-go/appsdk" 27 | "github.com/edgexfoundry/app-functions-sdk-go/examples/advanced-filter-convert-publish/functions" 28 | ) 29 | 30 | const ( 31 | serviceKey = "advancedFilterConvertPublish" 32 | ) 33 | 34 | func main() { 35 | // 1) First thing to do is to create an instance of the EdgeX SDK and initialize it. 36 | edgexSdk := &appsdk.AppFunctionsSDK{ServiceKey: serviceKey} 37 | if err := edgexSdk.Initialize(); err != nil { 38 | edgexSdk.LoggingClient.Error(fmt.Sprintf("SDK initialization failed: %v\n", err)) 39 | os.Exit(-1) 40 | } 41 | 42 | // 2) shows how to access the application's specific configuration settings. 43 | appSettings := edgexSdk.ApplicationSettings() 44 | if appSettings == nil { 45 | edgexSdk.LoggingClient.Error("No application settings found") 46 | os.Exit(-1) 47 | } 48 | 49 | appName, ok := appSettings["ApplicationName"] 50 | if ok { 51 | edgexSdk.LoggingClient.Info(fmt.Sprintf("%s now running...", appName)) 52 | } else { 53 | edgexSdk.LoggingClient.Error("ApplicationName application setting not found") 54 | os.Exit(-1) 55 | } 56 | 57 | valueDescriptorList, ok := appSettings["ValueDescriptors"] 58 | if !ok { 59 | edgexSdk.LoggingClient.Error("ValueDescriptors application setting not found") 60 | os.Exit(-1) 61 | } 62 | 63 | // 3) Since our FilterByValueDescriptor Function requires the list of ValueDescriptor's we would 64 | // like to search for, we'll go ahead create that list from the corresponding configuration setting. 65 | valueDescriptorList = strings.Replace(valueDescriptorList, " ", "", -1) 66 | valueDescriptors := strings.Split(valueDescriptorList, ",") 67 | edgexSdk.LoggingClient.Info(fmt.Sprintf("Filtering for %v value descriptors...", valueDescriptors)) 68 | 69 | // 4) This is our functions pipeline configuration, the collection of functions to 70 | // execute every time an event is triggered. 71 | err := edgexSdk.SetFunctionsPipeline( 72 | transforms.NewFilter(valueDescriptors).FilterByValueDescriptor, 73 | functions.ConvertToReadableFloatValues, 74 | functions.PrintFloatValuesToConsole, 75 | functions.Publish, 76 | ) 77 | 78 | if err != nil { 79 | edgexSdk.LoggingClient.Error("SDK initialization failed: " + err.Error()) 80 | os.Exit(-1) 81 | } 82 | 83 | // 5) Lastly, we'll go ahead and tell the SDK to "start" and begin listening for events 84 | // to trigger the pipeline. 85 | err = edgexSdk.MakeItRun() 86 | if err != nil { 87 | edgexSdk.LoggingClient.Error("MakeItRun returned error: ", err.Error()) 88 | os.Exit(-1) 89 | } 90 | 91 | // Do any required cleanup here 92 | 93 | os.Exit(0) 94 | } 95 | -------------------------------------------------------------------------------- /app-services/simple-cbor-filter/README.md: -------------------------------------------------------------------------------- 1 | # Simple CBOR Filter Application Service 2 | 3 | This **simple-cbor-filter** Application Service demonstrates end to end `CBOR` integration. It depends on the **device-simple** example device service from **Device SDK Go** to generate `CBOR` encode events. 4 | 5 | This **simple-cbor-filter** Application Service uses two application functions: 6 | 7 | - Built in **Filter by Value Descriptor** function to filter just for the **Image** values. 8 | - Custom **Process Images** function which re-encodes the `binary value` as an Image and prints stats about the image to the console. 9 | 10 | The end result from this application service is that it shows that the Application Functions SDK is un-marshaling `CBOR` encode events sent from the **device-simple** device service. These event can be processed by functions similar to `JSON` encoded events. The only difference is the `CBOR` encode events have the `BinaryValue` field set, while the `JSON` encoded events have the `Value` field set. 11 | 12 | #### Follow these steps to run the end to end CBOR demonstration 13 | 14 | 1. Start EdgeX Mongo 15 | 16 | - [ ] clone **[developer-scripts](https://github.com/edgexfoundry/developer-scripts)** repo 17 | - [ ] cd **compose-files** folder 18 | - [ ] run "**docker-compose up mongo**" 19 | - This uses the default compose file to start the EdgeX Mongo service which exposes it's port to apps running on localhost 20 | 21 | 2. Run EdgeX cores services 22 | 23 | - [ ] Clone **[edgex-go](https://github.com/edgexfoundry/edgex-go)** repo 24 | - [ ] run "**make build**" 25 | - [ ] run "**make run**" 26 | - This starts all the required EdgeX services 27 | 28 | 3. Run **simple-cbor-filter** example 29 | 30 | - [ ] Clone **[app-functions-sdk-go](https://github.com/edgexfoundry/app-functions-sdk-go)** repo 31 | - [ ] run "**make build**" 32 | - [ ] cd to **examples/simple-cbor-filter** folder 33 | - [ ] run "./**simple-cbor-filter**" 34 | 35 | 4. Run **device-simple** device service 36 | 37 | - [ ] Clone **** repo 38 | 39 | - [ ] run "**make build**" 40 | 41 | - [ ] cd to **example/cmd/device-simple** folder 42 | 43 | - [ ] run "./**device-simple**" 44 | 45 | This sample device service will send a `png` (light bulb on) or `jpeg` (light bulb off) image every 30 seconds. The image it sends depends on the value of its `switch` resource, which is `off` (false) by default. 46 | 47 | 5. Now data will be flowing due to auto-events configured in **device-simple**. 48 | 49 | - In the terminal that you ran **simple-cbor-filter** you will see the messages like this: 50 | 51 | ```text 52 | Received Image from Device: Simple-Device01, ReadingName: Image, Image Type: jpeg, Image Size: (1000,1307), Color in middle: {0 128 128} 53 | ``` 54 | 55 | Note that the image received is a jpeg since the `switch` resource in **device-simple** is set to `off ` (false) 56 | 57 | - The `switch` resource can be queried and changed using commands sent via PostMan by doing the following: 58 | 59 | 1. Start PostMan 60 | 61 | 2. Load the postman collection from the **simple-cbor-filter** example 62 | 63 | `Device Simple Switch commands.postman_collection.json` 64 | 65 | 3. This collection contains 3 commands 66 | 67 | - `Get Switch status` 68 | - `Turn Switch on` 69 | - `Turn Switch off` 70 | 71 | 4. Run `Turn Switch on` 72 | 73 | - Now see how the **simple-cbor-filter** output has changed 74 | 75 | ``` 76 | Received Image from Device: Simple-Device01, ReadingName: Image, Image Type: png, Image Size: (1000,1307), Color in middle: {255 246 0 255} 77 | ``` 78 | 79 | 80 | -------------------------------------------------------------------------------- /app-services/simple-cbor-filter/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package main 18 | 19 | import ( 20 | "bytes" 21 | "errors" 22 | "fmt" 23 | "image" 24 | _ "image/jpeg" 25 | _ "image/png" 26 | "os" 27 | "strings" 28 | 29 | "github.com/edgexfoundry/app-functions-sdk-go/pkg/transforms" 30 | 31 | "github.com/edgexfoundry/go-mod-core-contracts/models" 32 | 33 | "github.com/edgexfoundry/app-functions-sdk-go/appcontext" 34 | "github.com/edgexfoundry/app-functions-sdk-go/appsdk" 35 | ) 36 | 37 | const ( 38 | serviceKey = "sampleCborFilter" 39 | ) 40 | 41 | var counter int = 0 42 | 43 | func main() { 44 | // 1) First thing to do is to create an instance of the EdgeX SDK and initialize it. 45 | edgexSdk := &appsdk.AppFunctionsSDK{ServiceKey: serviceKey} 46 | if err := edgexSdk.Initialize(); err != nil { 47 | edgexSdk.LoggingClient.Error(fmt.Sprintf("SDK initialization failed: %v\n", err)) 48 | os.Exit(-1) 49 | } 50 | 51 | // 2) shows how to access the application's specific configuration settings. 52 | appSettings := edgexSdk.ApplicationSettings() 53 | if appSettings == nil { 54 | edgexSdk.LoggingClient.Error("No application settings found") 55 | os.Exit(-1) 56 | } 57 | 58 | valueDescriptorList, ok := appSettings["ValueDescriptors"] 59 | if !ok { 60 | edgexSdk.LoggingClient.Error("ValueDescriptors application setting not found") 61 | os.Exit(-1) 62 | } 63 | 64 | // 3) Since our FilterByValueDescriptor Function requires the list of ValueDescriptor's we would 65 | // like to search for, we'll go ahead create that list from the corresponding configuration setting. 66 | valueDescriptorList = strings.Replace(valueDescriptorList, " ", "", -1) 67 | valueDescriptors := strings.Split(valueDescriptorList, ",") 68 | edgexSdk.LoggingClient.Info(fmt.Sprintf("Filtering for %v value descriptors...", valueDescriptors)) 69 | 70 | // 4) This is our pipeline configuration, the collection of functions to 71 | // execute every time an event is triggered. 72 | edgexSdk.SetFunctionsPipeline( 73 | transforms.NewFilter(valueDescriptors).FilterByValueDescriptor, 74 | processImages, 75 | ) 76 | 77 | // 5) Lastly, we'll go ahead and tell the SDK to "start" and begin listening for events 78 | // to trigger the pipeline. 79 | err := edgexSdk.MakeItRun() 80 | if err != nil { 81 | edgexSdk.LoggingClient.Error("MakeItRun returned error: ", err.Error()) 82 | os.Exit(-1) 83 | } 84 | 85 | // Do any required cleanup here 86 | 87 | os.Exit(0) 88 | } 89 | 90 | func processImages(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) { 91 | if len(params) < 1 { 92 | // We didn't receive a result 93 | return false, nil 94 | } 95 | 96 | event, ok := params[0].(models.Event) 97 | if !ok { 98 | return false, errors.New("processImages didn't receive expect models.Event type") 99 | 100 | } 101 | 102 | for _, reading := range event.Readings { 103 | // For this to work the image/jpeg & image/png packages must be imported to register their decoder 104 | imageData, imageType, err := image.Decode(bytes.NewReader(reading.BinaryValue)) 105 | 106 | if err != nil { 107 | return false, errors.New("unable to decode image: " + err.Error()) 108 | } 109 | 110 | // Since this is a example, we will just print put some stats from the images received 111 | fmt.Printf("Received Image from Device: %s, ReadingName: %s, Image Type: %s, Image Size: %s, Color in middle: %v\n", 112 | reading.Device, reading.Name, imageType, imageData.Bounds().Size().String(), 113 | imageData.At(imageData.Bounds().Size().X/2, imageData.Bounds().Size().Y/2)) 114 | } 115 | 116 | return false, nil 117 | } 118 | -------------------------------------------------------------------------------- /app-services/simple-filter-xml-mqtt/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/edgexfoundry/app-functions-sdk-go/pkg/transforms" 24 | 25 | "github.com/edgexfoundry/app-functions-sdk-go/appcontext" 26 | "github.com/edgexfoundry/app-functions-sdk-go/appsdk" 27 | "github.com/edgexfoundry/go-mod-core-contracts/models" 28 | ) 29 | 30 | const ( 31 | serviceKey = "sampleFilterXmlMqtt" 32 | ) 33 | 34 | func main() { 35 | // 1) First thing to do is to create an instance of the EdgeX SDK and initialize it. 36 | edgexSdk := &appsdk.AppFunctionsSDK{ServiceKey: serviceKey} 37 | if err := edgexSdk.Initialize(); err != nil { 38 | edgexSdk.LoggingClient.Error(fmt.Sprintf("SDK initialization failed: %v\n", err)) 39 | os.Exit(-1) 40 | } 41 | 42 | // 2) Since our FilterByDeviceName Function requires the list of device names we would 43 | // like to search for, we'll go ahead and define that now. 44 | deviceNames := []string{"Random-Float-Device"} 45 | // Since we are using MQTT, we'll also need to set up the addressable model to 46 | // configure it to send to our broker. If you don't have a broker setup you can pull one from docker i.e: 47 | // docker run -it -p 1883:1883 -p 9001:9001 eclipse-mosquitto 48 | addressable := models.Addressable{ 49 | Address: "localhost", 50 | Port: 1883, 51 | Protocol: "tcp", 52 | Publisher: "MyApp", 53 | User: "", 54 | Password: "", 55 | Topic: "sampleTopic", 56 | } 57 | 58 | // Using default settings, so not changing any fields in MqttConfig 59 | mqttConfig := transforms.MqttConfig{} 60 | 61 | // Make sure you change KeyFile and CertFile here to point to actual key/cert files 62 | // or an error will be logged for failing to load key/cert files 63 | // If you don't use key/cert for MQTT authentication, just pass nil to NewMQTTSender() as following: 64 | // mqttSender := transforms.NewMQTTSender(edgexSdk.LoggingClient, addressable, nil, mqttConfig) 65 | pair := transforms.KeyCertPair{ 66 | KeyFile: "PATH_TO_YOUR_KEY_FILE", 67 | CertFile: "PATH_TO_YOUR_CERT_FILE", 68 | } 69 | 70 | mqttSender := transforms.NewMQTTSender(edgexSdk.LoggingClient, addressable, &pair, mqttConfig, false) 71 | 72 | // 3) This is our pipeline configuration, the collection of functions to 73 | // execute every time an event is triggered. 74 | edgexSdk.SetFunctionsPipeline( 75 | transforms.NewFilter(deviceNames).FilterByDeviceName, 76 | transforms.NewConversion().TransformToXML, 77 | printXMLToConsole, 78 | mqttSender.MQTTSend, 79 | ) 80 | 81 | // 4) shows how to access the application's specific configuration settings. 82 | appSettings := edgexSdk.ApplicationSettings() 83 | if appSettings != nil { 84 | appName, ok := appSettings["ApplicationName"] 85 | if ok { 86 | edgexSdk.LoggingClient.Info(fmt.Sprintf("%s now running...", appName)) 87 | } else { 88 | edgexSdk.LoggingClient.Error("ApplicationName application setting not found") 89 | os.Exit(-1) 90 | } 91 | } else { 92 | edgexSdk.LoggingClient.Error("No application settings found") 93 | os.Exit(-1) 94 | } 95 | 96 | // 5) Lastly, we'll go ahead and tell the SDK to "start" and begin listening for events 97 | // to trigger the pipeline. 98 | err := edgexSdk.MakeItRun() 99 | if err != nil { 100 | edgexSdk.LoggingClient.Error("MakeItRun returned error: ", err.Error()) 101 | os.Exit(-1) 102 | } 103 | 104 | // Do any required cleanup here 105 | 106 | os.Exit(0) 107 | } 108 | 109 | func printXMLToConsole(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) { 110 | if len(params) < 1 { 111 | // We didn't receive a result 112 | return false, nil 113 | } 114 | 115 | // Leverage the built in logging service in EdgeX 116 | edgexcontext.LoggingClient.Debug(params[0].(string)) 117 | edgexcontext.Complete(([]byte)(params[0].(string))) 118 | return true, params[0].(string) 119 | } 120 | -------------------------------------------------------------------------------- /app-services/simple-filter-xml/EdgeX Application Function SDK Device Name.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "ea5bca34-040c-406e-885f-49842aea97a1", 4 | "name": "EdgeX Application Function SDK Device Name", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "CoreData Event Trigger (Random-Float-Device)", 10 | "event": [ 11 | { 12 | "listen": "test", 13 | "script": { 14 | "id": "6c139344-5a75-4dec-ae3c-04d94454a6c2", 15 | "exec": [ 16 | "pm.test(\"Status code is 200\", function () {", 17 | " pm.response.to.have.status(200);", 18 | "});", 19 | "", 20 | "tests[\"Response time is less than 200ms\"] = responseTime < 200;", 21 | "", 22 | "tests[\"Body contains\"] = responseBody.has(\"Random-Float-Device\");", 23 | "", 24 | "" 25 | ], 26 | "type": "text/javascript" 27 | } 28 | } 29 | ], 30 | "request": { 31 | "method": "POST", 32 | "header": [], 33 | "body": { 34 | "mode": "raw", 35 | "raw": "{\n \"id\": \"5bd794de0e36080001f53eab\",\n \"pushed\": 0,\n \"device\": \"Random-Float-Device\",\n \"created\": 1540855006481,\n \"modified\": 0,\n \"origin\": 1540855006469,\n \"schedule\": null,\n \"event\": null,\n \"readings\": [\n {\n \"id\": \"5bd794de0e36080001f53eac\",\n \"pushed\": 0,\n \"created\": 1540855006481,\n \"origin\": 1540855006469,\n \"modified\": 0,\n \"device\": \"Random-Float-Device\",\n \"name\": \"RandomValue_Float32\",\n \"value\": \"P63Kqg==\"\n }\n ]\n}" 36 | }, 37 | "url": { 38 | "raw": "127.0.0.1:9090", 39 | "host": [ 40 | "127", 41 | "0", 42 | "0", 43 | "1" 44 | ], 45 | "port": "9090" 46 | }, 47 | "description": "This request will send a valid EdgeX event to Applications Function SDK via HTTP in order to trigger a configure pipeline. The default is :9090." 48 | }, 49 | "response": [] 50 | }, 51 | { 52 | "name": "CoreData Event Trigger (Random-Integer-Generator01)", 53 | "event": [ 54 | { 55 | "listen": "test", 56 | "script": { 57 | "id": "6c139344-5a75-4dec-ae3c-04d94454a6c2", 58 | "exec": [ 59 | "pm.test(\"Status code is 200\", function () {", 60 | " pm.response.to.have.status(200);", 61 | "}); ", 62 | "", 63 | "pm.test(\"Response time is less than 200ms\", function () {", 64 | " pm.expect(pm.response.responseTime).to.be.below(200);", 65 | "});", 66 | "", 67 | "", 68 | "pm.test(\"No data in responseBody\", function () {", 69 | " pm.expect(responseBody.length).to.be.equal(0);", 70 | "});" 71 | ], 72 | "type": "text/javascript" 73 | } 74 | } 75 | ], 76 | "request": { 77 | "method": "POST", 78 | "header": [], 79 | "body": { 80 | "mode": "raw", 81 | "raw": "{\n \"id\": \"5bd794de0e36080001f53eab\",\n \"pushed\": 0,\n \"device\": \"Random-Integer-Generator0\",\n \"created\": 1540855006481,\n \"modified\": 0,\n \"origin\": 1540855006469,\n \"schedule\": null,\n \"event\": null,\n \"readings\": [\n {\n \"id\": \"5bd794de0e36080001f53eac\",\n \"pushed\": 0,\n \"created\": 1540855006481,\n \"origin\": 1540855006469,\n \"modified\": 0,\n \"device\": \"Random-Integer-Generator0\",\n \"name\": \"RandomValue_Int32\",\n \"value\": \"-1406268274\"\n }\n ]\n}" 82 | }, 83 | "url": { 84 | "raw": "127.0.0.1:9090", 85 | "host": [ 86 | "127", 87 | "0", 88 | "0", 89 | "1" 90 | ], 91 | "port": "9090" 92 | }, 93 | "description": "This request will send a valid EdgeX event to Applications Function SDK via HTTP in order to trigger a configure pipeline. The default is :9090." 94 | }, 95 | "response": [] 96 | }, 97 | { 98 | "name": "CoreData Event Trigger (empty string)", 99 | "event": [ 100 | { 101 | "listen": "test", 102 | "script": { 103 | "id": "6c139344-5a75-4dec-ae3c-04d94454a6c2", 104 | "exec": [ 105 | "pm.test(\"Status code is 200\", function () {", 106 | " pm.response.to.have.status(200);", 107 | "}); ", 108 | "", 109 | "pm.test(\"Response time is less than 200ms\", function () {", 110 | " pm.expect(pm.response.responseTime).to.be.below(200);", 111 | "});", 112 | "", 113 | "", 114 | "pm.test(\"No data in responseBody\", function () {", 115 | " pm.expect(responseBody.length).to.be.equal(0);", 116 | "});" 117 | ], 118 | "type": "text/javascript" 119 | } 120 | } 121 | ], 122 | "request": { 123 | "method": "POST", 124 | "header": [], 125 | "body": { 126 | "mode": "raw", 127 | "raw": "{\n \"id\": \"5bd794de0e36080001f53eab\",\n \"pushed\": 0,\n \"device\": \"\",\n \"created\": 1540855006481,\n \"modified\": 0,\n \"origin\": 1540855006469,\n \"schedule\": null,\n \"event\": null,\n \"readings\": [\n {\n \"id\": \"5bd794de0e36080001f53eac\",\n \"pushed\": 0,\n \"created\": 1540855006481,\n \"origin\": 1540855006469,\n \"modified\": 0,\n \"device\": \"Random-Float-Device\",\n \"name\": \"RandomValue_Float64\",\n \"value\": \"QAFk2HxRUOo=\\\"\n }\n ]\n}" 128 | }, 129 | "url": { 130 | "raw": "127.0.0.1:9090", 131 | "host": [ 132 | "127", 133 | "0", 134 | "0", 135 | "1" 136 | ], 137 | "port": "9090" 138 | }, 139 | "description": "This request will send a valid EdgeX event to Applications Function SDK via HTTP in order to trigger a configure pipeline. The default is :9090." 140 | }, 141 | "response": [] 142 | } 143 | ] 144 | } -------------------------------------------------------------------------------- /app-services/advanced-filter-convert-publish/README.md: -------------------------------------------------------------------------------- 1 | # Example Advanced App Functions Service 2 | 3 | This **advanced-filter-convert-publish** Application Service depends on the new Device Virtual Go device service to be generating random number events. It uses the following functions in its pipeline: 4 | 5 | - Built in **Filter by Value Descriptor** function to filter just for the random **float32** & **float64** values. 6 | - Custom **Value Converter** function which converts the encoded float values to human readable string values. 7 | - Custom **Print to Console** function which simply prints the human readable strings to the console. 8 | - Custom **Publish** function which prepares the modified event with converted values and outputs it to be published back to the message bus using the configured publish host/topic. 9 | 10 | The end result from this application service is random float values in human readable format are published back to the message bus for another App Service to consume. 11 | 12 | #### End to end Edgex integration proof point for App Functions 13 | 14 | Using the following setup, this example advanced **App Functions Service** can be used to demonstrate an EdgeX end to end proof point with **App Functions**. 15 | 16 | 1. Start **EdgeX Mongo** 17 | 18 | - [ ] clone **[developer-scripts](https://github.com/edgexfoundry/developer-scripts)** repo 19 | - [ ] cd **compose-files** folder 20 | - [ ] run "**docker-compose up mongo**" 21 | - This uses the default compose file to start the EdgeX Mongo service which exposes it's port to apps running on localhost 22 | 23 | 2. Run **EdgeX Core Services** 24 | 25 | - [ ] Clone **[edgex-go](https://github.com/edgexfoundry/edgex-go)** repo 26 | - [ ] run "**make build**" 27 | - [ ] run "**make run**" 28 | - This starts all the required Edgex core, export and support services 29 | 30 | 3. Run **Advanced App Functions** example 31 | 32 | - [ ] Clone **[app-service-examples](https://github.com/edgexfoundry-holding/app-service-examples)** repo 33 | - [ ] run "**make build**" 34 | - [ ] cd to **advanced-filter-convert-publish** folder 35 | - [ ] run "./**advanced-filter-convert-publish**" 36 | 37 | 4. Configure and Run **Simple Filter XML** App Functions example 38 | 39 | - [ ] cd **simple-filter-xml** folder 40 | 41 | - [ ] edit **res/configuration.toml** so the **MessageBus** and **Binding** sections are as follows: 42 | 43 | ```toml 44 | [MessageBus] 45 | Type = 'zero' 46 | [MessageBus.PublishHost] 47 | Host = '*' 48 | Port = 5565 49 | Protocol = 'tcp' 50 | [MessageBus.SubscribeHost] 51 | Host = 'localhost' 52 | Port = 5564 53 | Protocol = 'tcp' 54 | 55 | [Binding] 56 | Type="messagebus" 57 | SubscribeTopic="converted" 58 | PublishTopic="xml" 59 | ``` 60 | 61 | - [ ] Run "**./simple-filter-xml**" 62 | 63 | 5. Run **Device Virtual** service 64 | 65 | - [ ] Clone **** repo 66 | 67 | - [ ] run "**make build**" 68 | 69 | - [ ] cd to **cmd**/res folder 70 | 71 | - [ ] Edit the `device.virtual.float.yaml` file 72 | 73 | This app functions example expects the float encoding for all random floats to be `Base64` and needs to restrict the range of values that are generated so they are easy to read. By default the device-virtual is using `Base64` for Float32 & `eNotation` for Float64 and doesn't set any range limits. Make the following changes to the `deviceResources` section to meet these needs. 74 | 75 | For `RandomValue_Float32` change the `value` property to: 76 | 77 | ``` 78 | { type: "Float32", readWrite: "R", defaultValue: "0", floatEncoding: "Base64", minimum: "1.0", maximum: "1.9" } 79 | ``` 80 | 81 | For `RandomValue_Float64` change the `value` property to: 82 | 83 | ``` 84 | { type: "Float64", readWrite: "R", defaultValue: "0", floatEncoding: "Base64", minimum: "2.0", maximum: "2.9" } 85 | ``` 86 | 87 | - [ ] If you previously ran the Device Virtual service, run the follow `curl` commands to clear the old profile so that the new changes are used when the device service is started. 88 | 89 | ``` 90 | curl -X DELETE http://localhost:48081/api/v1/deviceservice/name/device-virtual 91 | curl -X DELETE http://localhost:48081/api/v1/deviceprofile/name/Random-Float-Device 92 | ``` 93 | 94 | - [ ] cd to **cmd** folder 95 | 96 | - [ ] run "./**device-virtual**" 97 | 98 | first time this is run, the output will have these messages : 99 | ```text 100 | level=INFO ts=2019-04-17T22:42:08.238390389Z app=device-virtual source=service.go:138 msg="**Device Service doesn't exist, creating a new one**" 101 | level=INFO ts=2019-04-17T22:42:08.277064025Z app=device-virtual source=service.go:196 msg="**Addressable device-virtual doesn't exist, creating a new one**" 102 | ``` 103 | 104 | One subsequent runs you will see this message: 105 | 106 | ```text 107 | level=INFO ts=2019-04-18T17:37:18.304805374Z app=device-virtual source=service.go:145 msg="Device Service device-virtual exists" 108 | ``` 109 | 110 | 6. Now data will be flowing due to the auto-events configured in Device Virtual Go. 111 | 112 | - In the terminal that you ran **advanced-filter-convert-publish** you will see the random float values printed. 113 | 114 | ```text 115 | RandomValue_Float64 readable value from Random-Float-Device is '2.1742 116 | RandomValue_Float32 readable value from Random-Float-Device is '1.3577 117 | ``` 118 | 119 | - In the terminal that you ran **simple-filter-xml** you will see the xml representation of the events printed. Note the human readable float values in the event XML. 120 | ```xml 121 | 0Random-Float-Device001555609767442835c5541-d4d2-42a8-8937-8b24b4308d3f0015556097674110Random-Float-DeviceRandomValue_Float642.1742 122 | 0Random-Float-Device00155560979745221c8ccdc-3438-4baa-8fab-23a63bf4fa180015556097974190Random-Float-DeviceRandomValue_Float321.3577 123 | ``` 124 | -------------------------------------------------------------------------------- /app-services/http-command-service/README.md: -------------------------------------------------------------------------------- 1 | # HTTP Command Service # 2 | 3 | #### Overview #### 4 | 5 | Many IoT deployments require some form of integration with the cloud. The integration will be required for both north- and south bound integration. For south-bound services, after analytic processing from the cloud, some device actions are notmally triggered as a result of gathered intelligence. This document demonstrates a sample EdgeX application service–HTTP Command Service that can receive commands from the Azure IoT Hub, consume the device data and invoke commands against devices. The entire technical architecture is illustrated below: 6 | 7 | ![Technical Architecture](./Southbound.png) 8 | 9 | We sometimes want to trigger device actions from the cloud; that is, from the Azure IoT Hub to EdgeX. The EdgeX Core Command Service provides a comprehensive set of APIs to achieve this. However, in some cases, you might prefer not to expose all APIs and therefore require a finer-grained control over the APIs to be exposed. For example, you might want to control the commands on specific devices that can receive commands from outside of EdgeX, or to allow only certain values for a specific command. HTTP Command Service provides a sample implementations to achieve such control. 10 | 11 | #### Prerequisites #### 12 | 13 | * Obtain the code from the https://github.com/edgexfoundry-holding/app-service-examples repo 14 | * Ensure that EdgeX is running with mandatory services, including core services and logging service 15 | * Ensure that the Virtual Device Service is running and managed by EdgeX with at least one pre-defined device, such as Random-Boolean-Device
16 | 17 | If you are unfamiliar with the Azure IoT Hub, read the following documents first, as this document intentionally omits some details on Azure: 18 | * [Create an Azure IoT Hub on the Azure portal](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-create-through-portal) 19 | * [Set up X.509 security on Azure IoT Hub](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-security-x509-get-started) 20 | 21 | #### Steps #### 22 | 23 | 1. The HTTP Command Service exposes an HTTP service for the client to switch on / off of the device without knowing the underlying EdgeX APIs. For the service to send commands to correct device, you must set DeviceID and CommandID in the [res/configuration.toml](./res/configuration.toml) file.
24 | ``` 25 | [ApplicationSettings] 26 | DeviceID = "9f178953-84e7-49f6-9829-5b86b7cbbcda" 27 | CommandID = "15786a22-d89b-474b-a7de-18371c3d22c5" 28 | ``` 29 | **Note:** For real use cases, the DeviceID must be associated with the actual device that is managed by EdgeX; the CommandID must be associated with the required command registered under the EdgeX Core Command Service, and the command must provide correct responses to consume the [switch on / off JSON document](./status-on-request.json). For simplicity, we can use the Virtual Device Service in this sample. 30 | 31 | 2. Build the HTTP command service through steps as described [here](https://github.com/edgexfoundry-holding/app-service-examples#building-examples). 32 | 3. Run the http-command service through steps as described [here](https://github.com/edgexfoundry-holding/app-service-examples#running-an-example), and you have an HTTP service that can switch on/off your device by sending a [JSON payload](./status-on-request.json) to the http://127.0.0.1:48095/api/v1/trigger endpoint. 33 | 4. With the HTTP Command Service ready, your Azure IoT Hub can use a direct method to control the device. Azure provides an IoT device SDK in various programming languages. Refer to [Quickstart: Control a device connected to an Azure IoT hub with Java](https://docs.microsoft.com/en-us/azure/iot-hub/quickstart-control-device-java) for more details of using the direct method. To simplify the implementation, we supply modified code, which is ready to run with the Azure IoT hub: 34 | * Under [iot-hub](./iot-hub) directory, there are two sample modules: 35 | * [Quickstarts/proxy-device](./iot-hub/Quickstarts/proxy-device)
36 | This simulates an Azure device that is associated with the proxy device as defined in step 1, and this proxy device could be used to handle remote methods. 37 | * [Quickstarts/back-end-application](./iot-hub/Quickstarts/back-end-application)
38 | This is a simulated Azure back-end application that issues remote method requests. 39 | * Follow the steps as described [here](https://docs.microsoft.com/en-us/azure/iot-hub/quickstart-control-device-java#register-a-device) to register a proxy device on your Azure IoT hub 40 | * Update the **connection string** and **device Id** as obtained from previous step for both following Java classes: 41 | * [Quickstarts/proxy-device/src/main/java/com/microsoft/docs/iothub/samples/ProxyDevice.java](./iot-hub/Quickstarts/proxy-device/src/main/java/com/microsoft/docs/iothub/samples/ProxyDevice.java)
42 | ```java 43 | public class ProxyDevice { 44 | // The device connection string to authenticate the device with your IoT hub. 45 | // Using the Azure CLI: 46 | // az iot hub device-identity show-connection-string --hub-name {YourIoTHubName} --device-id {YourDeviceId} --output table 47 | private static String connString = "{Your device connection string here}"; 48 | ``` 49 | * [Quickstarts/back-end-application/src/main/java/com/microsoft/docs/iothub/samples/BackEndApplication.java](./iot-hub/Quickstarts/back-end-application/src/main/java/com/microsoft/docs/iothub/samples/BackEndApplication.java)
50 | ```java 51 | public class BackEndApplication { 52 | 53 | // Connection string for your IoT Hub 54 | // az iot hub show-connection-string --hub-name {your iot hub name} --policy-name service 55 | public static final String iotHubConnectionString = "{Your service connection string here}"; 56 | 57 | // Device to call direct method on. 58 | public static final String deviceId = "{Your device Id here}"; 59 | ``` 60 | * Run ``mvn clean package`` in both 2 modules 61 | * Use following scripts to run the applications: 62 | * [Quickstarts/proxy-device/run.sh](./iot-hub/Quickstarts/proxy-device/run.sh)
63 | This initiates the proxy device that is defined on the Azure IoT Hub. This device acts as a proxy to invoke the HTTP Command Service using the http://127.0.0.1:48095/api/v1/trigger endpoint 64 | * [Quickstarts/back-end-application/on.sh](./iot-hub/Quickstarts/back-end-application/on.sh)
65 | This simulates the switch-on request from the Azure IoT Hub. A successful request makes a device method invocation to switch on the device. 66 | * [Quickstarts/back-end-application/off.sh](./iot-hub/Quickstarts/back-end-application/off.sh)
67 | This simulates the switch-off request from the Azure IoT Hub. A successful request makes a device method invocation to switch off the device. -------------------------------------------------------------------------------- /app-services/http-command-service/iot-hub/Quickstarts/proxy-device/src/main/java/com/microsoft/docs/iothub/samples/ProxyDevice.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | // This application uses the Azure IoT Hub device SDK for Java 5 | // For samples see: https://github.com/Azure/azure-iot-sdk-java/tree/master/device/iot-device-samples 6 | 7 | package com.microsoft.docs.iothub.samples; 8 | 9 | import com.microsoft.azure.sdk.iot.device.*; 10 | import com.microsoft.azure.sdk.iot.device.DeviceTwin.*; 11 | import com.google.gson.Gson; 12 | 13 | import java.io.*; 14 | import java.net.URISyntaxException; 15 | import java.util.Random; 16 | import java.util.concurrent.Executors; 17 | import java.util.concurrent.ExecutorService; 18 | 19 | import java.lang.reflect.Type; 20 | import java.util.List; 21 | import java.util.Map; 22 | import java.util.HashMap; 23 | import com.google.api.client.http.GenericUrl; 24 | import com.google.api.client.http.HttpRequest; 25 | import com.google.api.client.http.HttpRequestFactory; 26 | import com.google.api.client.http.HttpTransport; 27 | import com.google.api.client.http.HttpContent; 28 | import com.google.api.client.http.HttpMediaType; 29 | import com.google.api.client.http.json.JsonHttpContent; 30 | import com.google.api.client.http.javanet.NetHttpTransport; 31 | import com.google.api.client.json.JsonFactory; 32 | import com.google.api.client.json.JsonObjectParser; 33 | import com.google.api.client.json.gson.GsonFactory; 34 | import com.google.gson.reflect.TypeToken; 35 | 36 | public class ProxyDevice { 37 | // The device connection string to authenticate the device with your IoT hub. 38 | // Using the Azure CLI: 39 | // az iot hub device-identity show-connection-string --hub-name {YourIoTHubName} --device-id {YourDeviceId} --output table 40 | private static String connString = "{Your device connection string here}"; 41 | 42 | // Using the MQTT protocol to connect to IoT Hub 43 | private static IotHubClientProtocol protocol = IotHubClientProtocol.MQTT; 44 | private static DeviceClient client; 45 | 46 | private static final String TURN_ON = "turn-on"; 47 | private static final String TURN_OFF = "turn-off"; 48 | 49 | // Define method response codes 50 | private static final int METHOD_SUCCESS = 200; 51 | private static final int METHOD_NOT_DEFINED = 404; 52 | private static final int INVALID_PARAMETER = 400; 53 | 54 | private static int telemetryInterval = 1000; 55 | 56 | // For HTTP client 57 | static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport(); 58 | static final JsonFactory JSON_FACTORY = new GsonFactory(); 59 | // 60 | 61 | // Specify the telemetry to send to your IoT hub. 62 | private static class TelemetryDataPoint { 63 | public double temperature; 64 | public double humidity; 65 | 66 | // Serialize object to JSON format. 67 | public String serialize() { 68 | Gson gson = new Gson(); 69 | return gson.toJson(this); 70 | } 71 | } 72 | 73 | // Print the acknowledgement received from IoT Hub for the method acknowledgement sent. 74 | protected static class DirectMethodStatusCallback implements IotHubEventCallback 75 | { 76 | public void execute(IotHubStatusCode status, Object context) 77 | { 78 | System.out.println("Direct method # IoT Hub responded to device method acknowledgement with status: " + status.name()); 79 | } 80 | } 81 | 82 | // Print the acknowledgement received from IoT Hub for the telemetry message sent. 83 | private static class EventCallback implements IotHubEventCallback { 84 | public void execute(IotHubStatusCode status, Object context) { 85 | System.out.println("IoT Hub responded to message with status: " + status.name()); 86 | 87 | if (context != null) { 88 | synchronized (context) { 89 | context.notify(); 90 | } 91 | } 92 | } 93 | } 94 | 95 | protected static class DirectMethodCallback implements DeviceMethodCallback 96 | { 97 | private void setTelemetryInterval(int interval) 98 | { 99 | System.out.println("Direct method # Setting telemetry interval (seconds): " + interval); 100 | telemetryInterval = interval * 1000; 101 | } 102 | 103 | private void switchStatus(boolean s) { 104 | try { 105 | HttpRequestFactory requestFactory = HTTP_TRANSPORT.createRequestFactory((HttpRequest request) -> { 106 | request.setParser(new JsonObjectParser(JSON_FACTORY)); 107 | }); 108 | 109 | GenericUrl url = new GenericUrl("http://127.0.0.1:48095/api/v1/trigger"); 110 | Map data = new HashMap<>(); 111 | data.put("status", s ? "on" : "off"); 112 | HttpContent content = new JsonHttpContent(JSON_FACTORY, data).setMediaType(new HttpMediaType("application/json")); 113 | HttpRequest request = requestFactory.buildPostRequest(url, content); 114 | 115 | Switch sw = (Switch) request.execute().parseAs(Switch.class); 116 | 117 | System.out.println(sw); 118 | } catch (IOException ioe) { 119 | ioe.printStackTrace(); 120 | } 121 | } 122 | 123 | @Override 124 | public DeviceMethodData call(String methodName, Object methodData, Object context) 125 | { 126 | DeviceMethodData deviceMethodData; 127 | String payload = new String((byte[])methodData); 128 | switch (methodName) 129 | { 130 | case "SetTelemetryInterval" : 131 | { 132 | int interval; 133 | try { 134 | int status = METHOD_SUCCESS; 135 | interval = Integer.parseInt(payload); 136 | System.out.println(payload); 137 | setTelemetryInterval(interval); 138 | deviceMethodData = new DeviceMethodData(status, "Executed direct method " + methodName); 139 | } catch (NumberFormatException e) { 140 | int status = INVALID_PARAMETER; 141 | deviceMethodData = new DeviceMethodData(status, "Invalid parameter " + payload); 142 | } 143 | break; 144 | } 145 | case TURN_ON: 146 | { 147 | int status = METHOD_SUCCESS; 148 | // 149 | switchStatus(true); 150 | // 151 | deviceMethodData = new DeviceMethodData(status, "Executed direct method " + methodName); 152 | break; 153 | } 154 | case TURN_OFF: 155 | { 156 | int status = METHOD_SUCCESS; 157 | // 158 | switchStatus(false); 159 | // 160 | deviceMethodData = new DeviceMethodData(status, "Executed direct method " + methodName); 161 | break; 162 | } 163 | default: 164 | { 165 | int status = METHOD_NOT_DEFINED; 166 | deviceMethodData = new DeviceMethodData(status, "Non-defined direct method " + methodName); 167 | } 168 | } 169 | 170 | System.out.println("Executed direct method " + methodName); 171 | 172 | return deviceMethodData; 173 | } 174 | } 175 | 176 | private static class MessageSender implements Runnable { 177 | public void run() { 178 | try { 179 | // Initialize the simulated telemetry. 180 | double minTemperature = 20; 181 | double minHumidity = 60; 182 | Random rand = new Random(); 183 | 184 | while (true) { 185 | Thread.sleep(telemetryInterval); 186 | } 187 | } catch (InterruptedException e) { 188 | System.out.println("Finished."); 189 | } 190 | } 191 | } 192 | 193 | public static void main(String[] args) throws IOException, URISyntaxException { 194 | // Connect to the IoT hub. 195 | client = new DeviceClient(connString, protocol); 196 | client.open(); 197 | 198 | // Register to receive direct method calls. 199 | client.subscribeToDeviceMethod(new DirectMethodCallback(), null, new DirectMethodStatusCallback(), null); 200 | 201 | // Create new thread and start sending messages 202 | MessageSender sender = new MessageSender(); 203 | ExecutorService executor = Executors.newFixedThreadPool(1); 204 | executor.execute(sender); 205 | 206 | // Stop the application. 207 | System.out.println("Press ENTER to exit."); 208 | System.in.read(); 209 | executor.shutdownNow(); 210 | client.closeNow(); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | https://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS --------------------------------------------------------------------------------