diff --git a/example/cmd/device-simple/res/configuration.toml b/example/cmd/device-simple/res/configuration.toml index 9b18514f6..c6a120f18 100644 --- a/example/cmd/device-simple/res/configuration.toml +++ b/example/cmd/device-simple/res/configuration.toml @@ -74,6 +74,7 @@ SecretName = "redisdb" PublishTopicPrefix = "edgex/events/device" # /// will be added to this Publish Topic prefix CommandRequestTopic = "edgex/device/command/request/device-simple/#" # subscribing for inbound command requests CommandResponseTopicPrefix = "edgex/device/command/response" # publishing outbound command responses; /// will be added to this publish topic prefix + SystemEventTopic = "edgex/system-events/core-metadata/device/#/device-simple/#" [MessageBus.Optional] # Default MQTT Specific options that need to be here to enable environment variable overrides of them # Client Identifiers diff --git a/internal/controller/http/callback.go b/internal/controller/http/callback.go index e7bc0d4ee..130d6f3dc 100644 --- a/internal/controller/http/callback.go +++ b/internal/controller/http/callback.go @@ -1,6 +1,6 @@ // -*- Mode: Go; indent-tabs-mode: t -*- // -// Copyright (C) 2020-2021 IOTech Ltd +// Copyright (C) 2020-2023 IOTech Ltd // // SPDX-License-Identifier: Apache-2.0 @@ -19,61 +19,6 @@ import ( "github.com/edgexfoundry/device-sdk-go/v3/internal/application" ) -func (c *RestController) DeleteDevice(writer http.ResponseWriter, request *http.Request) { - vars := mux.Vars(request) - name := vars[common.Name] - - err := application.DeleteDevice(name, c.dic) - if err == nil { - res := commonDTO.NewBaseResponse("", "", http.StatusOK) - c.sendResponse(writer, request, common.ApiDeviceCallbackNameRoute, res, http.StatusOK) - } else { - c.sendEdgexError(writer, request, err, common.ApiDeviceCallbackNameRoute) - } -} - -func (c *RestController) AddDevice(writer http.ResponseWriter, request *http.Request) { - defer request.Body.Close() - - var addDeviceRequest requests.AddDeviceRequest - - err := json.NewDecoder(request.Body).Decode(&addDeviceRequest) - if err != nil { - edgexErr := errors.NewCommonEdgeX(errors.KindServerError, "failed to decode JSON", err) - c.sendEdgexError(writer, request, edgexErr, common.ApiDeviceCallbackRoute) - return - } - - edgexErr := application.AddDevice(addDeviceRequest, c.dic) - if edgexErr == nil { - res := commonDTO.NewBaseResponse(addDeviceRequest.RequestId, "", http.StatusOK) - c.sendResponse(writer, request, common.ApiDeviceCallbackRoute, res, http.StatusOK) - } else { - c.sendEdgexError(writer, request, edgexErr, common.ApiDeviceCallbackRoute) - } -} - -func (c *RestController) UpdateDevice(writer http.ResponseWriter, request *http.Request) { - defer request.Body.Close() - - var updateDeviceRequest requests.UpdateDeviceRequest - - err := json.NewDecoder(request.Body).Decode(&updateDeviceRequest) - if err != nil { - edgexErr := errors.NewCommonEdgeX(errors.KindServerError, "failed to decode JSON", err) - c.sendEdgexError(writer, request, edgexErr, common.ApiDeviceCallbackRoute) - return - } - - edgexErr := application.UpdateDevice(updateDeviceRequest, c.dic) - if edgexErr == nil { - res := commonDTO.NewBaseResponse(updateDeviceRequest.RequestId, "", http.StatusOK) - c.sendResponse(writer, request, common.ApiDeviceCallbackRoute, res, http.StatusOK) - } else { - c.sendEdgexError(writer, request, edgexErr, common.ApiDeviceCallbackRoute) - } -} - func (c *RestController) UpdateProfile(writer http.ResponseWriter, request *http.Request) { defer request.Body.Close() diff --git a/internal/controller/http/restrouter.go b/internal/controller/http/restrouter.go index 3f91ffaea..4aee88de9 100644 --- a/internal/controller/http/restrouter.go +++ b/internal/controller/http/restrouter.go @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- // // Copyright (C) 2017-2018 Canonical Ltd -// Copyright (C) 2018-2022 IOTech Ltd +// Copyright (C) 2018-2023 IOTech Ltd // Copyright (c) 2019 Intel Corporation // // SPDX-License-Identifier: Apache-2.0 @@ -68,9 +68,6 @@ func (c *RestController) InitRestRoutes() { c.addReservedRoute(common.ApiDeviceNameCommandNameRoute, c.GetCommand).Methods(http.MethodGet) c.addReservedRoute(common.ApiDeviceNameCommandNameRoute, c.SetCommand).Methods(http.MethodPut) // callback - c.addReservedRoute(common.ApiDeviceCallbackRoute, c.AddDevice).Methods(http.MethodPost) - c.addReservedRoute(common.ApiDeviceCallbackRoute, c.UpdateDevice).Methods(http.MethodPut) - c.addReservedRoute(common.ApiDeviceCallbackNameRoute, c.DeleteDevice).Methods(http.MethodDelete) c.addReservedRoute(common.ApiProfileCallbackRoute, c.UpdateProfile).Methods(http.MethodPut) c.addReservedRoute(common.ApiWatcherCallbackRoute, c.AddProvisionWatcher).Methods(http.MethodPost) c.addReservedRoute(common.ApiWatcherCallbackRoute, c.UpdateProvisionWatcher).Methods(http.MethodPut) diff --git a/internal/controller/messaging/callback.go b/internal/controller/messaging/callback.go new file mode 100644 index 000000000..8a939c83d --- /dev/null +++ b/internal/controller/messaging/callback.go @@ -0,0 +1,108 @@ +// +// Copyright (C) 2023 IOTech Ltd +// +// SPDX-License-Identifier: Apache-2.0 + +package messaging + +import ( + "context" + "encoding/json" + + bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container" + "github.com/edgexfoundry/go-mod-bootstrap/v3/di" + "github.com/edgexfoundry/go-mod-core-contracts/v3/common" + "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos" + "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/requests" + "github.com/edgexfoundry/go-mod-core-contracts/v3/errors" + "github.com/edgexfoundry/go-mod-messaging/v3/pkg/types" + + "github.com/edgexfoundry/device-sdk-go/v3/internal/application" + "github.com/edgexfoundry/device-sdk-go/v3/internal/container" +) + +const SystemEventTopic = "SystemEventTopic" + +func DeviceCallback(ctx context.Context, dic *di.Container) errors.EdgeX { + lc := bootstrapContainer.LoggingClientFrom(dic.Get) + messageBusInfo := container.ConfigurationFrom(dic.Get).MessageBus + systemEventTopic := messageBusInfo.Topics[SystemEventTopic] + + messages := make(chan types.MessageEnvelope) + messageErrors := make(chan error) + topics := []types.TopicChannel{ + { + Topic: systemEventTopic, + Messages: messages, + }, + } + + messageBus := bootstrapContainer.MessagingClientFrom(dic.Get) + err := messageBus.Subscribe(topics, messageErrors) + if err != nil { + return errors.NewCommonEdgeXWrapper(err) + } + + go func() { + for { + select { + case <-ctx.Done(): + lc.Infof("Exiting waiting for MessageBus '%s' topic messages", systemEventTopic) + return + case err = <-messageErrors: + lc.Error(err.Error()) + case msgEnvelope := <-messages: + lc.Debugf("System event received on message queue. Topic: %s, Correlation-id: %s ", systemEventTopic, msgEnvelope.CorrelationID) + + var systemEvent dtos.SystemEvent + err := json.Unmarshal(msgEnvelope.Payload, &systemEvent) + if err != nil { + lc.Errorf("failed to JSON decoding system event: %s", err.Error()) + continue + } + + serviceName := container.DeviceServiceFrom(dic.Get).Name + if systemEvent.Owner != serviceName { + lc.Errorf("unmatched system event owner %s with service name %s", systemEvent.Owner, serviceName) + continue + } + + if systemEvent.Type != common.DeviceSystemEventType { + lc.Errorf("unknown system event type %s", systemEvent.Type) + continue + } + + var device dtos.Device + err = systemEvent.DecodeDetails(&device) + if err != nil { + lc.Errorf("failed to decode system event details: %s", err.Error()) + continue + } + + switch systemEvent.Action { + case common.DeviceSystemEventActionAdd: + err = application.AddDevice(requests.NewAddDeviceRequest(device), dic) + if err != nil { + lc.Error(err.Error(), common.CorrelationHeader, msgEnvelope.CorrelationID) + } + case common.DeviceSystemEventActionUpdate: + deviceModel := dtos.ToDeviceModel(device) + updateDeviceDTO := dtos.FromDeviceModelToUpdateDTO(deviceModel) + err = application.UpdateDevice(requests.NewUpdateDeviceRequest(updateDeviceDTO), dic) + if err != nil { + lc.Error(err.Error(), common.CorrelationHeader, msgEnvelope.CorrelationID) + } + case common.DeviceSystemEventActionDelete: + err = application.DeleteDevice(device.Name, dic) + if err != nil { + lc.Error(err.Error(), common.CorrelationHeader, msgEnvelope.CorrelationID) + } + default: + lc.Errorf("unknown device system event action %s", systemEvent.Action) + } + } + } + }() + + return nil +} diff --git a/openapi/v3/changes.txt b/openapi/v3/changes.txt new file mode 100644 index 000000000..ecb336174 --- /dev/null +++ b/openapi/v3/changes.txt @@ -0,0 +1,3 @@ +API changes from v2 + +* Remove /callback/device, /callback/device/name/{name} endpoints diff --git a/openapi/v3/device-sdk.yaml b/openapi/v3/device-sdk.yaml new file mode 100644 index 000000000..fc211d83d --- /dev/null +++ b/openapi/v3/device-sdk.yaml @@ -0,0 +1,1298 @@ +openapi: 3.0.0 +info: + title: EdgeX Foundry - Device Service API + description: This is the definition of the API for Device services in the EdgeX Foundry IOT microservice platform. Device Services are responsible for obtaining Readings from target devices, and for writing values (settings) to them. + version: 3.0.0 + +servers: + - url: http://0:49999/api/v3 + description: URL for local development and testing + +components: + schemas: + AutoEvent: + description: "Identifies a resource which will be read automatically by the device service." + type: object + properties: + interval: + description: "Specifies a time interval between reading requests. This may be in a combination of hours, minutes, seconds and milliseconds (h/m/s/ms)" + type: string + example: "1m30s" + onChange: + description: "If true, new Events will only be generated if successive readings differ in value." + type: boolean + sourceName: + description: "SourceName indicates the name of the resource or device command in the device profile which describes the event to generate" + type: string + required: + - sourceName + - frequency + NewBaseReading: + description: "A base reading type containing common properties from which more specific reading types inherit. This definition should not be implemented but is used elsewhere to indicate support for a mixed list of simple/binary readings in a single event." + type: object + properties: + device: + description: "The name of the device from which the reading originated" + type: string + origin: + description: "A Unix timestamp indicating when the reading was originated at the source device (can support nanoseconds)" + type: integer + BaseRequest: + description: "Defines basic properties which all use-case specific request DTO instances should support." + type: object + properties: + requestId: + description: "Uniquely identifies this request. For implementation, recommend this value be generated by the type's constructor." + type: string + format: uuid + example: "e6e8a2f4-eb14-4649-9e2b-175247911369" + apiVersion: + description: "A version number shows the API version in DTOs." + type: string + example: "v2" + NewBinaryReading: + description: "An event reading for a binary data type" + allOf: + - $ref: '#/components/schemas/NewBaseReading' + - type: object + properties: + binaryValue: + description: "The value of the reading will be found in this property as a byte array" + type: string + format: binary + mediaType: + description: "E.g. MIME Type, indicates what the content type of the binaryValue property is." + type: string + BaseResponse: + description: "Defines basic properties which all use-case specific response DTO instances should support" + type: object + properties: + apiVersion: + description: "A version number shows the API version in DTOs." + type: string + example: "v2" + requestId: + description: "Uniquely identifies the request that resulted in this response." + type: string + format: uuid + example: "e6e8a2f4-eb14-4649-9e2b-175247911369" + statusCode: + description: "A numeric code signifying the operational status of the response." + type: integer + message: + description: "A field that can contain a free-form message, such as an error message." + type: string + Device: + description: "A device is an IoT object represented in EdgeX." + type: object + properties: + id: + type: string + format: uuid + description: "The unique identifier of the device" + created: + type: integer + description: "A Unix timestamp indicating when the object was initially persisted to a database" + modified: + type: integer + description: "A Unix timestamp indicating when the object was last modified" + name: + type: string + description: "The name of the device" + description: + type: string + description: "A free-format descriptive field for additional device information" + adminState: + type: string + enum: [LOCKED, UNLOCKED] + description: "Administrative state of the device, allowing it to be locked for policy reasons" + operatingState: + type: string + enum: [UP, DOWN, UNKNOWN] + description: "Operating state of the device, indicating whether it is currently responsive" + lastConnected: + type: integer + description: "Time (milliseconds) that the device last provided any feedback or responded to any request" + lastReported: + type: integer + description: "Time (milliseconds) that the device reported data to the core microservice" + labels: + type: array + items: + type: string + description: "Other labels applied to the device to help with searching" + location: + type: object + description: "Device service specific location information" + serviceId: + type: string + description: "Associated device service referenced by id" + serviceName: + type: string + description: "Associated device service referenced by name" + profileId: + type: string + description: "Associated device profile referenced by id" + profileName: + type: string + description: "Associated device profile referenced by name" + autoEvents: + type: array + items: + $ref: '#/components/schemas/AutoEvent' + description: "Events which the service should generate automatically from this device." + protocols: + type: object + additionalProperties: + $ref: '#/components/schemas/ProtocolProperties' + DeviceProfile: + description: "A profile defining a class of device to be onboarded, including its capabilities and data format." + type: object + properties: + id: + type: string + format: uuid + description: ID uniquely identifies the device profile, a UUID for example + name: + type: string + description: Non-database identifier (must be unique) + description: + type: string + description: Description. + created: + type: integer + description: Created is a timestamp indicating when the entity was created. + modified: + type: integer + description: Modified is a timestamp indicating when the entity was last modified. + manufacturer: + type: string + description: Manufacturer of the device + model: + type: string + description: Model of the device + labels: + type: array + description: Labels used to search for groups of profiles + items: + type: string + deviceResources: + type: array + items: + $ref: '#/components/schemas/DeviceResource' + deviceCommands: + type: array + items: + $ref: '#/components/schemas/DeviceCommand' + UpdateDeviceService: + description: "A DeviceService is responsible for proxying connectivity between a set of devices and the EdgeX Foundry core services." + type: object + properties: + id: + type: string + format: uuid + description: ID uniquely identifies the device, a UUID for example + name: + type: string + description: Non-database identifier(must be unique) + labels: + type: array + description: tags or other labels applied to the device service for search or other identification needs + items: + type: string + baseAddress: + type: string + description: BaseAddress is a fully qualified URI, e.g. :\\:/ + adminState: + type: string + enum: [LOCKED, UNLOCKED] + description: Device Service Admin State + DeviceResource: + description: "DeviceResource represents a value on a device that can be read or written." + type: object + properties: + description: + type: string + name: + type: string + isHidden: + type: boolean + description: "Indicate the visibility of the DeviceResource via a CoreCommand." + tag: + type: string + properties: + $ref: '#/components/schemas/ResourceProperties' + attributes: + type: object + description: Each Device Service should define required and optional keys + additionalProperties: + type: string + required: + - name + BaseReading: + description: "A base reading type containing common properties from which more specific reading types inherit. This definition should not be implemented but is used elsewhere to indicate support for a mixed list of simple/binary readings in a single event." + type: object + properties: + apiVersion: + description: "A version number shows the API version in DTOs." + type: string + id: + description: "The unique identifier for the reading" + type: string + format: uuid + created: + description: "A Unix timestamp indicating when (if) the reading was initially persisted to a database." + type: integer + origin: + description: "A Unix timestamp indicating when the reading was originated at the source device (can support nanoseconds)" + type: integer + deviceName: + description: "The name of the device from which the reading originated" + type: string + resourceName: + description: "The device resource name for the reading" + type: string + profileName: + description: "The device profile name for the reading" + type: string + valueType: + description: "Indicates the datatype of the value property" + type: string + required: + - deviceName + - resourceName + - profileName + - origin + - valueType + SimpleReading: + description: "An event reading for a simple data type" + allOf: + - $ref: '#/components/schemas/BaseReading' + - type: object + properties: + value: + description: "A string representation of the reading's value" + type: string + required: + - value + BinaryReading: + description: "An event reading for a binary data type" + allOf: + - $ref: '#/components/schemas/BaseReading' + - type: object + properties: + binaryValue: + description: "If the value of the reading is binary, it will be found in this property as a byte array" + type: string + format: byte + mediaType: + description: "E.g. MIME Type, indicates what the content type of the binaryValue property is if it's populated." + type: string + required: + - binaryValue + - mediaType + Event: + description: "A discrete event containing one or more readings" + properties: + apiVersion: + description: "A version number shows the API version in DTOs." + type: string + id: + description: "The unique identifier for the event" + type: string + format: uuid + deviceName: + description: "The name of the device from which the event originated" + type: string + profileName: + description: "The name of the device profile from which the event originated" + type: string + created: + description: "A Unix timestamp indicating when (if) the event was initially persisted to a database." + type: integer + origin: + description: "A Unix timestamp indicating when the event was originated at the source device (can support nanoseconds)" + type: integer + readings: + description: "One or more readings captured at the time of the event" + type: array + items: + $ref: '#/components/schemas/SimpleReading' + tags: + description: "List of zero or more Tags attached to the Event which give more context to the Event" + title: tags + type: object + example: { + "Gateway-id": "HoustonStore-000123", + "Latitude": "29.630771", + "Longitude": "-95.377603", + } + required: + - id + - deviceName + - profileName + - origin + - readings + EventResponse: + allOf: + - $ref: '#/components/schemas/BaseResponse' + description: "A response type for returning an Event to the caller." + type: object + properties: + event: + $ref: '#/components/schemas/Event' + ErrorResponse: + allOf: + - $ref: '#/components/schemas/BaseResponse' + description: "A response type for returning a generic error to the caller." + type: object + ConfigResponse: + allOf: + - $ref: '#/components/schemas/BaseResponse' + description: "Provides a response containing the configuration for the targeted service." + type: object + properties: + serviceName: + description: "Outputs the name of the service the response is from" + type: string + config: + description: "An object containing the service''s configuration. Please refer to Core Data''s configuration documentation for more details at [EdgeX Foundry Documentation](https://docs.edgexfoundry.org)." + type: object + MetricsResponse: + allOf: + - $ref: '#/components/schemas/BaseResponse' + description: "A response from the /metrics endpoint providing memory and cpu utilization stats." + type: object + properties: + serviceName: + description: "Outputs the name of the service the response is from" + type: string + metrics: + type: object + properties: + memAlloc: + description: "Alloc is bytes of allocated heap objects which is a uint64 type integer." + type: integer + memFrees: + description: "Frees is the cumulative count of heap objects freed which is a uint64 type integer." + type: integer + memLiveObjects: + description: "The uint64 type integer of live objects is Mallocs - Frees." + type: integer + memMallocs: + description: "The cumulative count of heap objects allocated which is a uint64 type integer." + type: integer + memSys: + description: "The total bytes of memory obtained from the OS which is a uint64 type integer." + type: integer + memTotalAlloc: + description: "Cumulative bytes allocated for heap objects which is a uint64 type integer." + type: integer + cpuBusyAvg: + description: "A uint8 type integer indicates the average level of CPU utilization" + type: number + NewDeviceRequest: + allOf: + - $ref: '#/components/schemas/BaseRequest' + description: "Notification that a new device associated with this device service has been created." + type: object + properties: + device: + $ref: '#/components/schemas/Device' + required: + - device + NewProfileRequest: + allOf: + - $ref: '#/components/schemas/BaseRequest' + description: "Notification that a new device profile has been created." + type: object + properties: + profile: + $ref: '#/components/schemas/DeviceProfile' + required: + - profile + NewProvisionWatcherRequest: + allOf: + - $ref: '#/components/schemas/BaseRequest' + description: "Notification that a new provision watcher associated with this device service has been created." + type: object + properties: + provisionWatcher: + $ref: '#/components/schemas/ProvisionWatcher' + required: + - provisionWatcher + NewEventResponse: + description: "A discrete event containing one or more readings" + properties: + apiVersion: + description: "A version number shows the API version in DTOs." + type: string + example: "v2" + device: + description: "The name of the device from which the event originated" + type: string + origin: + description: "A Unix timestamp indicating when the event was originated at the source device (can support nanoseconds)" + type: integer + readings: + description: "One or more readings captured at the time of the event" + type: array + items: + $ref: '#/components/schemas/NewBaseReading' + PingResponse: + description: "A response from the /ping endpoint indicating that the service is functioning." + allOf: + - $ref: '#/components/schemas/BaseResponse' + type: object + properties: + timestamp: + description: "Outputs the current server timestamp in RFC1123 format" + example: "Mon, 02 Jan 2006 15:04:05 MST" + type: string + serviceName: + description: "Outputs the name of the service the response is from" + type: string + DeviceCommand: + description: "Defines read/write capabilities native to the device" + type: object + properties: + name: + type: string + isHidden: + type: boolean + description: "Indicate the visibility of the DeviceCommand via a CoreCommand." + readWrite: + type: string + description: "Read/Write Permissions set for this DeviceCommand. The value can be R, W, or RW. R enables GET command, and W enables SET command in the Core Command Service." + resourceOperations: + type: array + items: + $ref: '#/components/schemas/ResourceOperation' + required: + - name + - readWrite + - resourceOperations + ResourceProperties: + description: "Defines constraints with regard to the range of acceptable values assigned to an event reading and defined as a property within a device profile." + type: object + properties: + valueType: + type: string + description: "ValueDescriptor Type of property after transformations. Optional: uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64, bool, string, binary, uint8array, uint16array, uint32array, uint64array, int8array, int16array, int32array, int64array, float32array, float64array, boolarray." + readWrite: + type: string + description: "Read/Write Permissions set for this ResourceProperties. The value can be R, W, or RW. R enables GET command, and W enables SET command in the Core Command Service." + units: + type: string + description: A string which describes the measurement units associated with a property value Examples include "deg/s", "degreesFarenheit", "G", or "% Relative Humidity" + minimum: + type: string + description: Minimum value that can be get/set from this property + maximum: + type: string + description: Maximum value that can be get/set from this property + defaultValue: + type: string + description: Default value set to this property if no argument is passed. If present, should be compatible with the Type field + mask: + type: string + description: Mask to be applied prior to get/set of property. Only valid where Type is one of the integer types. + shift: + type: string + description: Shift to be applied after masking, prior to get/set of property. Only valid where Type is one of the integer types + scale: + type: string + description: Multiplicative factor to be applied after shifting, prior to get/set of property. Only valid where Type is one of the integer or float types + offset: + type: string + description: Additive factor to be applied after multiplying, prior to get/set of property. Only valid where Type is one of the integer or float types + base: + type: string + description: 'Base for property to be applied to, leave 0 for no power operation (i.e. base ^ property: 2 ^ 10). Only valid where Type is one of the integer or float types.' + assertion: + type: string + description: Required value of the property, set for checking error state. Failing an assertion condition will mark the device with an error state + mediaType: + type: string + description: A string value used to indicate the type of binary data if Type=binary + required: + - valueType + - readWrite + ProtocolProperties: + description: "The properties required to address a device via a particular protocol." + example: {"Host":"floor4.hq.acme.com", "Port":"8080"} + type: object + additionalProperties: + type: string + ProvisionWatcher: + description: "A Provision Watcher assigns a subset of discoverable devices to a specified device service, and specifies the device profile which should be used with them, and their initial adminState. The subset is defined by the Identifiers and BlockingIdentifiers, which are matched against a discovered device's protocol properties - the subset contains only devices which match all of the Identifiers and none of the BlockingIdentifiers." + type: object + properties: + id: + type: string + format: uuid + description: "The unique identifier for the provision watcher" + name: + type: string + description: "The name of the provision watcher" + created: + type: integer + description: "A Unix timestamp indicating when the object was initially persisted to a database" + modified: + type: integer + description: "A Unix timestamp indicating when the object was last modified" + labels: + type: array + description: "Labels applied to the provision watcher to help with searching" + items: + type: string + identifiers: + type: object + additionalProperties: + type: string + description: "Properties of a new device which must match in order for this provision watcher to match the new device" + blockingIdentifiers: + type: object + additionalProperties: + type: array + items: + type: string + description: "Properties of a new device which must not match in order for this provision watcher to match the new device" + serviceName: + type: string + description: "The device service to which to associate devices which match this watcher" + profileName: + type: string + description: "The device profile to which to associate devices which match this watcher" + adminState: + type: string + enum: [LOCKED, UNLOCKED] + description: "The initial administrative state to apply to devices which match this watcher" + autoEvents: + type: array + description: "Autoevents that allow device service to automatically start generating data from new devices" + items: + $ref: '#/components/schemas/AutoEvent' + ResourceOperation: + description: "Defines an operation of which a device is capable." + type: object + properties: + deviceResource: + type: string + description: The replacement of Object field. Must name a DeviceResource in this profile + defaultValue: + type: string + description: If present, should be compatible with the Type field of the named DeviceResource + mappings: + type: object + description: Only valid where the Type of the named DeviceResource is String + additionalProperties: + type: string + required: + - deviceResource + NewSimpleReading: + description: "An event reading for a simple data type" + allOf: + - $ref: '#/components/schemas/NewBaseReading' + - type: object + properties: + floatEncoding: + description: "Indicates how a float value is encoded, if the value property contains a float." + type: string + valueType: + description: "Indicates the datatype of the value property" + type: string + value: + description: "A string representation of the reading's value" + type: string + SecretRequest: + allOf: + - $ref: '#/components/schemas/BaseRequest' + description: Defines the secret data to be stored + type: object + properties: + path: + description: Specifies the location within the secret to store + type: string + example: "credentials" + secretData: + description: A list of the key/value pairs of secret data to store + type: array + items: + $ref: '#/components/schemas/SecretDataKeyValue' + required: + - path + - secretData + SecretDataKeyValue: + description: Defines a key/value pair of the secret data + type: object + properties: + key: + description: The key to identify the secret + type: string + example: "username" + value: + description: The value of the secret + type: string + example: "mqtt-user" + required: + - key + - value + SettingRequest: + description: "Defines new values to be written to device resources, as part of an actuation (put) command to a device" + additionalProperties: + type: string + title: Setting + type: object + example: {"AHU-TargetTemperature": "28.5", "AHU-TargetBand": "4.0"} + UpdateDeviceRequest: + allOf: + - $ref: '#/components/schemas/BaseRequest' + description: "Notification that an existing device associated with this device service has been updated." + type: object + properties: + device: + $ref: '#/components/schemas/Device' + required: + - device + UpdateProfileRequest: + allOf: + - $ref: '#/components/schemas/BaseRequest' + description: "Notification that an existing device profile has been updated." + type: object + properties: + profile: + $ref: '#/components/schemas/DeviceProfile' + required: + - profile + UpdateProvisionWatcherRequest: + allOf: + - $ref: '#/components/schemas/BaseRequest' + description: "Notification that an existing provision watcher associated with this device service has been updated." + type: object + properties: + provisionWatcher: + $ref: '#/components/schemas/ProvisionWatcher' + required: + - provisionWatcher + UpdateDeviceServiceRequest: + allOf: + - $ref: '#/components/schemas/BaseRequest' + description: "A request to update an existing device service definition. 'id' and 'name' must be populated in order to identify the service. Any other property that is populated in the request will be updated. Empty/blank properties will not be considered." + type: object + properties: + service: + $ref: '#/components/schemas/UpdateDeviceService' + required: + - service + VersionResponse: + description: "A response returned from the /version endpoint whose purpose is to report out the latest version supported by the service." + allOf: + - $ref: '#/components/schemas/BaseResponse' + type: object + properties: + version: + description: "The latest version supported by the service." + type: string + sdk_version: + description: "The version of the SDK with which the service was built." + type: string + serviceName: + description: "Outputs the name of the service the response is from" + type: string + + parameters: + correlatedRequestHeader: + in: header + name: X-Correlation-ID + description: "A unique identifier correlating a request to its associated response, facilitating tracing through being included on requests originating from the initiating request." + schema: + type: string + format: uuid + required: true + example: "14a42ea6-c394-41c3-8bcd-a29b9f5e6835" + + headers: + correlatedResponseHeader: + description: "A response header that returns the unique correlation ID used to initiate the request." + schema: + type: string + format: uuid + required: true + example: "14a42ea6-c394-41c3-8bcd-a29b9f5e6835" + + +paths: + /callback/profile: + parameters: + - $ref: '#/components/parameters/correlatedRequestHeader' + put: + description: "This call is used by core-metadata to inform the device service that a Device Profile has been updated" + responses: + '200': + description: "Callback successful" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/BaseResponse' + '400': + description: Invalid callback request. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: The device profile doesn't exist. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateProfileRequest' + required: true + + /callback/watcher: + parameters: + - $ref: '#/components/parameters/correlatedRequestHeader' + post: + description: "This call is used by core-metadata to inform the device service of the creation of a new Provision Watcher" + responses: + '200': + description: "Callback successful" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/BaseResponse' + '400': + description: Invalid callback request. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/NewProvisionWatcherRequest' + required: true + put: + description: "This call is used by core-metadata to inform the device service that a Provision Watcher's characteristics have been updated" + responses: + '200': + description: "Callback successful" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/BaseResponse' + '400': + description: Invalid callback request. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: The provision watcher doesn't exist. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateProvisionWatcherRequest' + required: true + + /callback/service: + parameters: + - $ref: '#/components/parameters/correlatedRequestHeader' + put: + description: "This call is used by core-metadata to inform the device service that its characteristics have been updated" + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateDeviceServiceRequest' + required: true + responses: + '200': + description: "Callback successful" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/BaseResponse' + '400': + description: Invalid callback request. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: The device doesn't exist. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /callback/watcher/name/{name}: + parameters: + - $ref: '#/components/parameters/correlatedRequestHeader' + - name: name + in: path + required: true + schema: + type: string + description: "The unique name of a provision watcher" + delete: + description: "This call is used by core-metadata to inform the device service that a Provision Watcher has been deleted" + responses: + '200': + description: "Callback successful" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/BaseResponse' + '404': + description: No provision watcher exists for the ID provided. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /device/name/{name}/{command}: + get: + description: Request the device/sensor by its name to return the current (or in some cases new) event/reading values for the command or device resource specified. The device service may have cached the latest event/reading for the sensor(s) or it may immediate request new event/reading values depending on the implementation and the device(s)/sensor(s) capability. + parameters: + - $ref: '#/components/parameters/correlatedRequestHeader' + - in: path + name: name + required: true + schema: + type: string + example: sensor01 + description: "A name uniquely identifying a device." + - in: path + name: command + required: true + schema: + type: string + example: command01 + description: "A name uniquely identifying a command." + - in: query + name: ds-pushevent + schema: + type: string + enum: + - yes + - no + default: no + example: yes + description: "If set to yes, a successful GET will result in an event being pushed to the EdgeX system" + - in: query + name: ds-returnevent + schema: + type: string + enum: + - yes + - no + default: yes + example: no + description: "If set to no, there will be no Event returned in the http response" + responses: + '200': + description: String as returned by the device/sensor through the device service. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + 'application/json': + schema: + $ref: '#/components/schemas/EventResponse' + '404': + description: If no device exists by the name provided or the command is unknown. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '405': + description: If the requested command exists but not for GET, or the resource is marked as write-only. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '423': + description: If the device or service is locked (admin state) or disabled (operating state). + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: The device driver is unable to process the request, or too many values were returned. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + put: + description: Request the actuator by its name to trigger a action or set a current value for the command or device resource specified. + parameters: + - $ref: '#/components/parameters/correlatedRequestHeader' + - in: path + name: name + required: true + schema: + type: string + example: sensor + - in: path + name: command + required: true + schema: + type: string + example: allValues + responses: + '200': + description: The PUT command was successful. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/BaseResponse' + '404': + description: If no device exists for the name provided or the command is unknown. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '405': + description: If the requested command exists but not for PUT, or the resource is marked as read-only. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '423': + description: If the device or service is locked (admin state) or disabled (operating state). + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: The device driver is unable to process the request. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SettingRequest' + required: true + + /secret: + parameters: + - $ref: '#/components/parameters/correlatedRequestHeader' + post: + summary: Stores a secret to the secure Secret Store + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/SecretRequest' + required: true + responses: + '201': + description: "Created" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/BaseResponse' + '400': + description: "Invalid request." + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: "An unexpected error happened on the server." + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /validate/device: + post: + summary: Run the device-specific validation for a Device's Properties. + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/NewDeviceRequest' + required: true + responses: + '200': + description: Validation succeed or validator implicitly not implemented + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/BaseResponse' + '400': + description: Invalid validation request. + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Validation failed. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /discovery: + post: + description: Run the discovery request for a Device Service. No request body is required. + responses: + '202': + description: The service is running the discovery request. + '423': + description: The service is disabled or administratively locked. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: An unexpected error occurred on the server + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '501': + description: The device driver does not implement discovery. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '503': + description: Discovery is disabled by configuration. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /config: + get: + summary: "Returns the current configuration of the service." + responses: + '200': + description: "OK" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ConfigResponse' + '500': + description: Interval Server Error + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + /metrics: + get: + summary: "An endpoint that can be used to obtain CPU/Memory usage stats for a given service." + responses: + '200': + description: "OK" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/MetricsResponse' + '500': + description: Interval Server Error + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /ping: + get: + summary: "A simple 'ping' endpoint that can be used as a service healthcheck" + responses: + '200': + description: "OK" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/PingResponse' + '500': + description: Interval Server Error + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /version: + get: + summary: "A simple 'version' endpoint that will return the current version of the service" + responses: + '200': + description: "OK" + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/VersionResponse' + '500': + description: Interval Server Error + headers: + X-Correlation-ID: + $ref: '#/components/headers/correlatedResponseHeader' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' diff --git a/pkg/service/main.go b/pkg/service/main.go index 8972faf51..e6881736b 100644 --- a/pkg/service/main.go +++ b/pkg/service/main.go @@ -1,6 +1,6 @@ // -*- Mode: Go; indent-tabs-mode: t -*- // -// Copyright (C) 2020-2022 IOTech Ltd +// Copyright (C) 2020-2023 IOTech Ltd // // SPDX-License-Identifier: Apache-2.0 @@ -112,12 +112,20 @@ func messageBusBootstrapHandler(ctx context.Context, wg *sync.WaitGroup, startup if !handlers.MessagingBootstrapHandler(ctx, wg, startupTimer, dic) { return false } + + lc := bootstrapContainer.LoggingClientFrom(dic.Get) + err := messaging.SubscribeCommands(ctx, dic) if err != nil { - lc := bootstrapContainer.LoggingClientFrom(dic.Get) lc.Errorf("Failed to subscribe internal command request: %v", err) return false } + + err = messaging.DeviceCallback(ctx, dic) + if err != nil { + lc.Errorf("Failed to subscribe Metadata system event: %v", err) + return false + } } return true