Fiware IoT Agent UL: Send command_value via mqtt to device - fiware

I am using the IoT-agent for ultra light and I'd like to send a command (including a specific value) back to my IoT-Device. If I follow the example for a smart door here I can send something like:
curl -iX PATCH \
'http://localhost:1026/v2/entities/urn:ngsi-ld:pysense:002/attrs' \
-H 'Content-Type: application/json' \
-H 'fiware-service: openiot' \
-H 'fiware-servicepath: /' \
-d '{
"color": {
"type" : "command",
"value" : "100"
}
}'
Fiware publishes the payload "urn:ngsi-ld:pysense:002#color|" to my mqtt-broker. But I would expect the value of "100" to be passed as well?
I also found this documentation on Git regarding mqtt-commands. But that one does not specify where to send that and how? I suppose it uses the the same URL as this request: http://localhost:4041/v1/updateContext
?? Is that the up-to-date way to issue commands?
I am using Orion-ld and the iotagent-ul:1.8.0.
Can somebody help me getting the value of "color" into the mqtt-message as well?
Thank you!
EDIT1:
I just tried orion:2.2.0 and reregistered the command like this:
{
"description": "Setting LED",
"dataProvided": {
"entities": [
{
"id": "urn:ngsi-ld:pysense:002", "type": "pysense"
}
],
"attrs": ["color", "brightness"]
},
"provider": {
"http": {"url": "http://orion:1026/v2"},
"legacyForwarding": true
}
}
If i try to send the command orion-logs go crazy (keep scrolling) with the following text:
fiware-orion | INFO#06:42:33 logMsg.h[1844]: Starting transaction from 172.18.1.1:35800/v2/entities/urn:ngsi-ld:pysense:002/attrs
fiware-orion | INFO#06:42:33 rest.cpp[885]: Service Path 0: '/dresden'
fiware-orion | INFO#06:42:33 connectionOperations.cpp[239]: Database Operation Successful (count: { _id.id: "urn:ngsi-ld:pysense:002", _id.servicePath: { $in: [ /^/dresden$/ ] } })
fiware-orion | INFO#06:42:33 connectionOperations.cpp[94]: Database Operation Successful (query: { _id.id: "urn:ngsi-ld:pysense:002", _id.servicePath: { $in: [ /^/dresden$/ ] } })
fiware-orion | INFO#06:42:33 connectionOperations.cpp[94]: Database Operation Successful (query: { $or: [ { entities.id: "urn:ngsi-ld:pysense:002", $or: [ { entities.type: "pysense" }, { entities.type: { $exists: false } } ], entities.isPattern: "false", entities.isTypePattern: { $ne: true }, expiration: { $gt: 1559716953 }, status: { $ne: "inactive" }, servicePath: { $in: [ /^$|^/#$|^/dresden/#$|^/dresden$/, null ] } }, { entities.isPattern: "true", entities.isTypePattern: { $ne: true }, expiration: { $gt: 1559716953 }, status: { $ne: "inactive" }, servicePath: { $in: [ /^$|^/#$|^/dresden/#$|^/dresden$/, null ] }, $where: function(){for (var i=0; i < this.entities.length; i++) {if (this.enti... }, { entities.isPattern: "false", entities.isTypePattern: true, expiration: { $gt: 1559716953 }, status: { $ne: "inactive" }, servicePath: { $in: [ /^$|^/#$|^/dresden/#$|^/dresden$/, null ] }, $where: function(){for (var i=0; i < this.entities.length; i++) {if (this.enti... }, { entities.isPattern: "true", entities.isTypePattern: true, expiration: { $gt: 1559716953 }, status: { $ne: "inactive" }, servicePath: { $in: [ /^$|^/#$|^/dresden/#$|^/dresden$/, null ] }, $where: function(){for (var i=0; i < this.entities.length; i++) {if (this.enti... } ] })
fiware-orion | INFO#06:42:33 connectionOperations.cpp[177]: Database Operation Successful (query: { query: { $or: [ { contextRegistration.entities: { $in: [ { id: "urn:ngsi-ld:pysense:002", type: "pysense" }, { type: "pysense", id: "urn:ngsi-ld:pysense:002" } ] } }, { contextRegistration.entities.id: { $in: [] } } ], expiration: { $gt: 1559716953 }, contextRegistration.attrs.name: { $in: [ "color" ] }, servicePath: { $in: [ /^/dresden$/ ] } }, orderby: { _id: 1 } })
fiware-orion | ERROR#06:42:33 postUpdateContext.cpp[553]: Internal Error (attribute 'color' not found)
fiware-orion | INFO#06:42:33 logMsg.h[1844]: Starting transaction to http://orion:1026/v1/updateContext
fiware-orion | INFO#06:42:33 httpRequestSend.cpp[592]: Sending message 8 to HTTP server: sending message of 433 bytes to HTTP server
fiware-orion | INFO#06:42:33 logMsg.h[1844]: Starting transaction from 172.18.1.7:46738/v1/updateContext
fiware-orion | INFO#06:42:33 rest.cpp[885]: Service Path 0: '/dresden'
fiware-orion | INFO#06:42:33 connectionOperations.cpp[94]: Database Operation Successful (query: { _id.id: "urn:ngsi-ld:pysense:002", _id.type: "pysense", _id.servicePath: { $in: [ /^/dresden$/ ] } })
fiware-orion | INFO#06:42:34 connectionOperations.cpp[94]: Database Operation Successful (query: { $or: [ { entities.id: "urn:ngsi-ld:pysense:002", $or: [ { entities.type: "pysense" }, { entities.type: { $exists: false } } ], entities.isPattern: "false", entities.isTypePattern: { $ne: true }, expiration: { $gt: 1559716953 }, status: { $ne: "inactive" }, servicePath: { $in: [ /^$|^/#$|^/dresden/#$|^/dresden$/, null ] } }, { entities.isPattern: "true", entities.isTypePattern: { $ne: true }, expiration: { $gt: 1559716953 }, status: { $ne: "inactive" }, servicePath: { $in: [ /^$|^/#$|^/dresden/#$|^/dresden$/, null ] }, $where: function(){for (var i=0; i < this.entities.length; i++) {if (this.enti... }, { entities.isPattern: "false", entities.isTypePattern: true, expiration: { $gt: 1559716953 }, status: { $ne: "inactive" }, servicePath: { $in: [ /^$|^/#$|^/dresden/#$|^/dresden$/, null ] }, $where: function(){for (var i=0; i < this.entities.length; i++) {if (this.enti... }, { entities.isPattern: "true", entities.isTypePattern: true, expiration: { $gt: 1559716953 }, status: { $ne: "inactive" }, servicePath: { $in: [ /^$|^/#$|^/dresden/#$|^/dresden$/, null ] }, $where: function(){for (var i=0; i < this.entities.length; i++) {if (this.enti... } ] })
fiware-orion | INFO#06:42:34 connectionOperations.cpp[177]: Database Operation Successful (query: { query: { $or: [ { contextRegistration.entities: { $in: [ { id: "urn:ngsi-ld:pysense:002", type: "pysense" }, { type: "pysense", id: "urn:ngsi-ld:pysense:002" } ] } }, { contextRegistration.entities.id: { $in: [] } } ], expiration: { $gt: 1559716954 }, contextRegistration.attrs.name: { $in: [ "color" ] }, servicePath: { $in: [ /^/dresden$/ ] } }, orderby: { _id: 1 } })
fiware-orion | INFO#06:42:34 logMsg.h[1844]: Starting transaction to http://orion:1026/v1/updateContext
...
And I get the following error as a reply in Postman:
{
"error": "NotFound",
"description": "The requested entity has not been found. Check type and id"
}
When orion registers the command by itself it looks like this with the iot-agent as provider instead of orion/v1:
{
"id": "5cf677b7c7ecc4737e1e0ec9",
"expires": "2020-06-04T13:52:55.00Z",
"dataProvided": {
"entities": [
{
"id": "urn:ngsi-ld:pysense:002",
"type": "pysense"
}
],
"attrs": [
"color",
"brightness"
]
},
"provider": {
"http": {
"url": "http://iot-agent:4041"
},
"supportedForwardingMode": "all",
"legacyForwarding": true
},
"status": "active"
}
With that I can actually send a command but it still misses the value. :(
EDIT2:
Something I found in the extensive debug logs of orion:
fiware-orion | INFO#07:35:09 connectionOperations.cpp[177]: Database Operation Successful (query: { query: { $or: [ { contextRegistration.entities: { $in: [ { id: "urn:ngsi-ld:pysense:002", type: "pysense" }, { type: "pysense", id: "urn:ngsi-ld:pysense:002" } ] } }, { contextRegistration.entities.id: { $in: [] } } ], expiration: { $gt: 1559720109 }, contextRegistration.attrs.name: { $in: [ "color" ] }, servicePath: { $in: [ /^/dresden$/ ] } }, orderby: { _id: 1 } })
fiware-orion | ERROR#07:35:09 postUpdateContext.cpp[553]: Internal Error (attribute 'color' not found)
After that the IoT-Agent updates the context but without a value.

There are two fixes to that problem. First thing that differs from the tutorial is the provider you need to register with a command. Instead of orion:1026/v1 register the IoT-Agent. It should look something like this with the IOTA_PROVIDER_URL from your docker-compose.yml as provider:
{
"description": "Setting LED",
"dataProvided": {
"entities": [
{
"id": "urn:ngsi-ld:pysense:002",
"type": "pysense"
}
],
"attrs": [
"color",
"brightness"
]
},
"provider": {
"http": {
"url": "http://iot-agent:4041"
},
"supportedForwardingMode": "all",
"legacyForwarding": true
},
"status": "active"
}
Now you can send a command in two ways. The first one is directly to the north port of the IoT Agent with a POST command to orion/v1/updateContext. The second one is to update the command in the context by sending a PATCH command to the entities attributes, providing the type of the entity to the request.
1: POST to updateContext
curl -iX POST \
'http://localhost:1026/v1/updateContext' \
-H 'Content-Type: application/json' \
-H 'fiware-service: openiot' \
-H 'fiware-servicepath: /' \
-d '{
"contextElements": [
{
"type": "pysense",
"isPattern": "false",
"id": "urn:ngsi-ld:pysense:002",
"attributes": [
{
"name": "color",
"type": "command",
"value": "25"
}
]
}
],
"updateAction": "UPDATE"
}
2: PATCH adding the type of the entity
curl -iX PATCH \
'http://localhost:1026/v2/entities/urn_ngsi-ld:pysense:002/attrs?type=pysense' \
-H 'Content-Type: application/json' \
-H 'fiware-service: openiot' \
-H 'fiware-servicepath: /' \
-d '{
"color": {
"type" : "command",
"value" : "90"
}
}
Thanks to fgalan for the help with that!

Related

How to access IoT core devices from Google Apps Script?

I use a script that runs a Nest thermostat. It works well for me, but when I share it with another person with multiple thermostats, it does not work even though I set up the script to run only on one of his thermostats.
This is the script I am talking about:
function changetemp() {
// enter the thermostat ID manually after running makerequesttest function
const THERMOSTAT = '';
const smartService = getSmartService();
const access_token = smartService.getAccessToken();
const url = 'https://smartdevicemanagement.googleapis.com/v1';
const endpoint = '/enterprises/' + PROJECT_ID + '/devices';
const headers = {
'Authorization': 'Bearer ' + access_token,
'Content-Type': 'application/json'
}
const params = {
'headers': headers,
'method': 'get',
'muteHttpExceptions': true
}
try {
const response = UrlFetchApp.fetch(url + endpoint, params);
const responseCode = response.getResponseCode();
const responseBody = JSON.parse(response.getContentText());
console.log("Response code: " + responseCode);
console.log(responseBody);
const devices = responseBody['devices'];
const device = devices.find(d => d.name === 'enterprises/' + PROJECT_ID + '/devices/' + THERMOSTAT)
if (!device) {
console.log("Thermostat with ID " + THERMOSTAT + " not found.")
return
}
}
The person with multiple thermostats gets the "not found" message, while it works for me.
The THERMOSTAT const is gotten from that function that we execute first:
function makeRequesttest() {
// get the smart service
const smartService = getSmartService();
// get the access token
const access_token = smartService.getAccessToken();
//console.log(access_token);
// setup the SMD API url
const url = 'https://smartdevicemanagement.googleapis.com/v1';
const endpoint = '/enterprises/' + PROJECT_ID + '/devices';
// setup the headers for the call
const headers = {
'Authorization': 'Bearer ' + access_token,
'Content-Type': 'application/json'
}
// set up params
const params = {
'headers': headers,
'method': 'get',
'muteHttpExceptions': true
}
// try calling API
try {
const response = UrlFetchApp.fetch(url + endpoint, params);
const responseBody = JSON.parse(response.getContentText());
Logger.log('response: ' + response);
return responseBody;
}
catch(e) {
console.log('Error: ' + e);
//throw e;
}
}
The makeRequesttest() function gives me that result:
response: {
"devices": [
{
"name": "enterprises/AAA/devices/BBB",
"type": "sdm.devices.types.THERMOSTAT",
"assignee": "enterprises/AAA/structures/CCC/rooms/DDD",
"traits": {
"sdm.devices.traits.Info": {
"customName": ""
},
"sdm.devices.traits.Humidity": {
"ambientHumidityPercent": 58
},
"sdm.devices.traits.Connectivity": {
"status": "ONLINE"
},
"sdm.devices.traits.Fan": {},
"sdm.devices.traits.ThermostatMode": {
"mode": "OFF",
"availableModes": [
"HEAT",
"OFF"
]
},
"sdm.devices.traits.ThermostatEco": {
"availableModes": [
"OFF",
"MANUAL_ECO"
],
"mode": "OFF",
"heatCelsius": 13.04544,
"coolCelsius": 24.44443
},
"sdm.devices.traits.ThermostatHvac": {
"status": "OFF"
},
"sdm.devices.traits.Settings": {
"temperatureScale": "CELSIUS"
},
"sdm.devices.traits.ThermostatTemperatureSetpoint": {},
"sdm.devices.traits.Temperature": {
"ambientTemperatureCelsius": 9.20999
}
},
"parentRelations": [
{
"parent": "enterprises/AAA/structures/EEE/rooms/FFF",
"displayName": "Office"
}
]
}
]
}
With that log, I manually update the THERMOSTAT const value.
The person with multiple thermostats gets that result with makeRequestest():
Logging output too large. Truncating output. response: {
"devices": [
{
"name": "enterprises/XXX/devices/AVPHwGGG",
"type": "sdm.devices.types.THERMOSTAT",
"assignee": "enterprises/XXX/structures/YYY/rooms/ZZZ",
"traits": {
"sdm.devices.traits.Info": {
"customName": ""
},
"sdm.devices.traits.Humidity": {
"ambientHumidityPercent": 28
},
"sdm.devices.traits.Connectivity": {
"status": "ONLINE"
},
"sdm.devices.traits.Fan": {},
"sdm.devices.traits.ThermostatMode": {
"mode": "HEAT",
"availableModes": [
"HEAT",
"OFF"
]
},
"sdm.devices.traits.ThermostatEco": {
"availableModes": [
"OFF",
"MANUAL_ECO"
],
"mode": "OFF",
"heatCelsius": 16.52971,
"coolCelsius": 24.44444
},
"sdm.devices.traits.ThermostatHvac": {
"status": "OFF"
},
"sdm.devices.traits.Settings": {
"temperatureScale": "FAHRENHEIT"
},
"sdm.devices.traits.ThermostatTemperatureSetpoint": {
"heatCelsius": 17.777779
},
"sdm.devices.traits.Temperature": {
"ambientTemperatureCelsius": 19.17
}
},
"parentRelations": [
{
"parent": "enterprises/XXX/structures/YYY/rooms/ZZZ",
"displayName": "Notinteresting1"
}
]
},
{
"name": "enterprises/XXX/devices/AVPHAAA",
"type": "sdm.devices.types.THERMOSTAT",
"assignee": "enterprises/XXX/structures/YYY/rooms/BBB",
"traits": {
"sdm.devices.traits.Info": {
"customName": ""
},
"sdm.devices.traits.Humidity": {
"ambientHumidityPercent": 27
},
"sdm.devices.traits.Connectivity": {
"status": "ONLINE"
},
"sdm.devices.traits.Fan": {},
"sdm.devices.traits.ThermostatMode": {
"mode": "HEAT",
"availableModes": [
"HEAT",
"OFF"
]
},
"sdm.devices.traits.ThermostatEco": {
"availableModes": [
"OFF",
"MANUAL_ECO"
],
"mode": "OFF",
"heatCelsius": 12.77776,
"coolCelsius": 24.44444
},
"sdm.devices.traits.ThermostatHvac": {
"status": "HEATING"
},
"sdm.devices.traits.Settings": {
"temperatureScale": "FAHRENHEIT"
},
"sdm.devices.traits.ThermostatTemperatureSetpoint": {
"heatCelsius": 18.98769
},
"sdm.devices.traits.Temperature": {
"ambientTemperatureCelsius": 19.03
}
},
"parentRelations": [
{
"parent": "enterprises/XXX/structures/YYY/rooms/BBB",
"displayName": "Notinteresting2"
}
]
},
{
"name": "enterprises/XXX/devices/CCC",
"type": "sdm.devices.types.THERMOSTAT",
"assignee": "enterprises/XXX/structures/YYY/rooms/DDD",
"traits": {
"sdm.devices.traits.Info": {
"customName": ""
},
"sdm.devices.traits.Humidity": {
"ambientHumidityPercent": 29
},
"sdm.devices.traits.Connectivity": {
"status": "ONLINE"
},
"sdm.devices.traits.Fan": {},
"sdm.devices.traits.ThermostatMode": {
"mode": "HEAT",
"availableModes": [
"HEAT",
"OFF"
]
},
"sdm.devices.traits.ThermostatEco": {
"availableModes": [
"OFF",
"MANUAL_ECO"
],
"mode": "OFF",
"heatCelsius": 17.062832,
"coolCelsius": 24.44444
},
"sdm.devices.traits.ThermostatHvac": {
"status": "OFF"
},
"sdm.devices.traits.Settings": {
"temperatureScale": "FAHRENHEIT"
},
"sdm.devices.traits.ThermostatTemperatureSetpoint": {
"heatCelsius": 16.71161
},
"sdm.devices.traits.Temperature": {
"ambientTemperatureCelsius": 19.67999
}
},
"parentRelations": [
{
"parent": "enterprises/XXX/structures/YYY/rooms/DDD",
"displayName": "Theone"
}
]
},
{
"name": "enterprises/XXX/devices/EEE",
"type": "sdm.devices.types.THERMOSTAT",
"assignee": "enterprises/XXX/structures/YYY/rooms/FFF",
"traits": {
"sdm.devices.traits.Info": {
"customName": ""
},
"sdm.devices.traits.Humidity": {
"ambientHumidityPercent": 26
},
"sdm.devices.traits.Connectivity": {
"status": "ONLINE"
},
"sdm.devices.traits.Fan": {},
"sdm.devices.traits.ThermostatMode": {
"mode": "HEAT",
"availableModes": [
"HEAT",
"OFF"
]
},
"sdm.devices.traits.ThermostatEco": {
"availableModes": [
"OFF",
"MANUAL_ECO"
],
"mode": "OFF",
"heatCelsius": 15.586624,
"coolCelsius": 24.444443
},
"sdm.devices.traits.ThermostatHvac": {
"status": "OFF"
},
"sdm.devices.traits.Settings": {
"temperatureScale": "FAHRENHEIT"
},
"sdm.devices.traits.ThermostatTemperatureSetpoint": {
"heatCelsius": 18.360092
},
"sdm.devices.traits.Temperature": {
"ambientTemperatureCelsius": 19.259995
}
},
"parentRelations": [
{
"parent": "enterprises/XXX/structures/YYY/rooms/AVPHw
The thermostat with display name "Theone" is the one that we are interested in.
We updated the THERMOSTAT const manually with "CCC" in the changetemp function. And we tried with other values he got like "GGG" or "DDD" but still got the log "device not found".
For that reason, I decided to create a "dummy" device in Google Cloud Console in order to simulate the fact that I have many thermostats running.
What is the way to acces this thermostat from Google Apps Script? How to make my script run like this fake device exists in my installation?
This is what I have at the moment in my registry:
dummydevice-details
dummydevice-configstate
dummydevice-authentication
Edit: public key added to the device:
dummydevice-authenticationpublickey

How to search for a string inside an object and replace it with another value on a condition

I have a Json file with below content, I am trying to replace serviceName string from ca-visual-node to ca-visual-node-canary if host name matches test4.analytics.io using jq utility. How can I get this done so that we will have json file updated with replace string
{
"apiVersion": "extensions/v1beta1",
"kind": "Ingress",
"metadata": {
"annotations": {
"alb.ingress.kubernetes.io/actions.ssl-redirect": "{\"Type\": \"redirect\", \"RedirectConfig\": { \"Protocol\": \"HTTPS\", \"Port\": \"443\", \"StatusCode\": \"HTTP_301\"}}"
},
"finalizers": [
"ingress.k8s.aws/resources"
],
"name": "ca-visual",
"namespace": "cloud-anaytics",
},
"spec": {
"rules": [
{
"host": "test.analytics.io",
"http": {
"paths": [
{
"backend": {
"serviceName": "ca-visual-play",
"servicePort": 9443
},
"path": "/apii/*",
"pathType": "ImplementationSpecific"
},
{
"backend": {
"serviceName": "ssl-redirect",
"servicePort": "use-annotation"
},
"path": "/*",
"pathType": "ImplementationSpecific"
},
{
"backend": {
"serviceName": "ca-visual-node",
"servicePort": 443
},
"path": "/*",
"pathType": "ImplementationSpecific"
}
]
}
},
{
"host": "test4.analytics.io",
"http": {
"paths": [
{
"backend": {
"serviceName": "ca-visual-play",
"servicePort": 9443
},
"path": "/apii/*",
"pathType": "ImplementationSpecific"
},
{
"backend": {
"serviceName": "ssl-redirect",
"servicePort": "use-annotation"
},
"path": "/*",
"pathType": "ImplementationSpecific"
},
{
"backend": {
"serviceName": "ca-visual-node",
"servicePort": 443
},
"path": "/*",
"pathType": "ImplementationSpecific"
}
]
}
}
]
},
"status": {
"loadBalancer": {
"ingress": [
{
"hostname": "16b3-cloudanaytics.us-xxxx-1.elb.amazonaws.com"
}
]
}
}
}
I am trying to replace serviceName string from ca-visual-node to ca-visual-node-canary if host name matches test4.analytics.io using jq utility. How can I get this done so that we will have json file updated with replace string.
Just walk the path from the root and use the update operator |= with the filter wrapped around (..)
(
.spec.rules[] |
select(.host == "test4.analytics.io")? |
.http.paths[].backend |
select(.serviceName == "ca-visual-node")? |
.serviceName
) |= "ca-visual-node-canary"
jqplay - demo

How to extract object from array by json_query?

I have got json from variable like below.
[
{
"policyName1": {
"resourceType": "Name1Type",
"id": "uuid-0000-000-0-1",
"extraAttr1":
[
{
"network11":
{
"allowAction": "yes",
"ipAddresses":
[
"192.168.29.193",
"192.168.29.196"
],
"disabled": false,
"ip_protocol": "IPV4_IPV6"
}
}
]
}
},
{
"policyName2": {
"resourceType": "name2Type",
"id": "uuid-0000-000-0-2",
"extraAttr12":
[
{
"network12":
{
"allowAction": "yes",
"ipAddresses":
[
"192.168.29.193",
"192.168.29.197"
],
"disabled": false,
"ip_protocol": "IPV4_IPV6"
}
}
]
}
},
{
"policyName3": {
"resourceType": "name3Type",
"id": "uuid-0000-000-0-3",
"extraAttr2":
[
{
"network13":
{
"allowAction": "yes",
"ipAddresses":
[
"192.168.29.191",
"192.168.29.195"
],
"disabled": false,
"ip_protocol": "IPV4_IPV6"
}
}
]
}
}
]
As you can see it is a normaln json. I need select (extract) whole object by id. For example, when I put id "uuid-0000-000-0-3" i would like receive object PolicyName3
{
"policyName3": {
"resourceType": "name3Type",
"id": "uuid-0000-000-0-3",
"extraAttr3":
[
{
"netowork13":
{
"allowAction": "yes",
"ipAddresses":
[
"192.168.29.191",
"192.168.29.195"
],
"disabled": false,
"ip_protocol": "IPV4_IPV6"
}
}
]
}
}
The digit are add automaticly by software and I can not remove them.
Also it is possible to add extra IP address childName.ipAddresses??
Thank you for help
Q: "Select (extract) the whole object by id."
A: Given the variable policy_list the tasks below
- set_fact:
ext1: "{{ policy_list|
map('dict2items')|list|flatten|
json_query(query)|
items2dict }}"
vars:
id: "uuid-0000-000-0-3"
query: "[?value.id == '{{ id }}']"
- debug:
var: ext1
give
"ext1": {
"policyName3": {
"extraAttr2": [
{
"network13": {
"allowAction": "yes",
"disabled": false,
"ipAddresses": [
"192.168.29.191",
"192.168.29.195"
],
"ip_protocol": "IPV4_IPV6"
}
}
],
"id": "uuid-0000-000-0-3",
"resourceType": "name3Type"
}
}

Test connect DHCP Kea / ISC DHCPDISCOVER PACKET_NAK_0001

The DHCP server is on a different network. I lifted the virtual machine Linux, there are two interfaces. Error DHCPDISCOVER PACKET_NAK_0001.
On a Linux virtual machine, I execute the commands:
dhcrelay ip_dhcp -i name_interface
dhclient -v name_interface -s ip_dhcp
An example of a config which I send through "curl":
{
"command": "config-set",
"service": [
"dhcp4"
],
"arguments": {
"Dhcp4": {
"option-def": [
{
"name": "configRevision",
"code": 254,
"type": "string",
"space": "dhcp4"
}
],
"interfaces-config": {
"interfaces": [
"*"
],
"dhcp-socket-type": "udp"
},
"control-socket": {
"socket-type": "unix",
"socket-name": "/tmp/kea-dhcp4-ctrl.sock"
},
"lease-database": {
"type": "postgresql",
"host": "host",
"name": "name",
"user": "name",
"password": "pass",
"port": 5432,
"lfc-interval": 600
},
"expired-leases-processing": {
"reclaim-timer-wait-time": 10,
"flush-reclaimed-timer-wait-time": 25,
"hold-reclaimed-time": 3600,
"max-reclaim-leases": 100,
"max-reclaim-time": 250,
"unwarned-reclaim-cycles": 5
},
"valid-lifetime": 3600,
"authoritative": true,
"hooks-libraries": [
{
"library": "/usr/local/lib/hooks/libdhcp_lease_cmds.so"
},
{
"library": "/usr/local/lib/hooks/libdhcp_stat_cmds.so"
}
],
"option-data": [
{
"name": "configRevision",
"code": 254,
"data": "1",
"always-send": false
},
{
"name": "domain-name-servers",
"data": "<IP>, <IP>",
"always-send": true
},
{
"name": "time-servers",
"data": "<IP>",
"always-send": true
},
{
"name": "ntp-servers",
"data": "<IP>",
"always-send": true
},
{
"name": "domain-name",
"data": "<DOMAIN>",
"always-send": true
},
{
"name": "dhcp-server-identifier",
"data": "<IP>"
}
],
"shared-networks": [
{
"name": "Zone 1",
"relay": {
"ip-addresses": [
"172.100.100.100",
"<IP>",
"<IP>",
"<IP>"
]
},
"option-data": [],
"subnet4": [
{
"id": 1314,
"subnet": "172.100.100.99/23",
"option-data": [
{
"name": "routers",
"data": "172.100.100.100"
}
],
"pools": [
{
"pool": "172.100.100.130-172.100.100.254",
"client-class": "UNKNOWN"
}
],
"valid-lifetime": 86400,
"reservations": []
}
]
}
]
}
}
Expected Result:
Successful issuance of IP address.
Actual result:
ERROR [kea-dhcp4.bad-packets/26218] DHCP4_PACKET_NAK_0001 [hwtype=1
], cid=[no info], tid=0x23acf436: failed to select a subnet for
incoming packet, src 172.100.100.100, type DHCPDISCOVER
Problem lies with client-class, not being known in time for subnet selection. Viz Kea Docs
The determination whether there is a reservation for a given client is made after a subnet is selected, so it is not possible to use “KNOWN”/”UNKNOWN” classes to select a shared network or a subnet.

Google actions fails to parse JSON response

I've been struggling with getting an assistant app to respond correctly.
This app is written with Google Actions SDK (there are no other components such as Dialogflow or Converse.ai).
The action package for the app looks something like this:
{
"actions":[
{
"description": "Default Welcome Intent",
"name": "MAIN",
"fulfillment": {
"conversationName": "ACTION_INTENT_MAIN"
},
"intent": {
"name": "actions.intent.MAIN",
"trigger": {
"queryPatterns": [
"talk to gactions integration"
]
}
}
}
],
"conversations":{
"ACTION_INTENT_MAIN": {
"name": "ACTION_INTENT_MAIN",
"url": "https://my.custom.end/point"
}
},
"locate":"en"
}
This is the JSON request that I recieve on https://my.custom.end/pont:
{
"user": {
"userId": "<a-user-id-sent-by-GOOG>",
"locale": "en-US",
"lastSeen": "2018-01-31T09:33:07Z"
},
"conversation": {
"conversationId": "1517393479793",
"type": "NEW"
},
"inputs": [
{
"intent": "actions.intent.MAIN",
"rawInputs": [
{
"inputType": "KEYBOARD",
"query": "Talk to Gactions Integration"
}
]
}
],
"surface": {
"capabilities": [
{
"name": "actions.capability.AUDIO_OUTPUT"
},
{
"name": "actions.capability.SCREEN_OUTPUT"
},
{
"name": "actions.capability.MEDIA_RESPONSE_AUDIO"
},
{
"name": "actions.capability.WEB_BROWSER"
}
]
},
"isInSandbox": true,
"availableSurfaces": [
{
"capabilities": [
{
"name": "actions.capability.AUDIO_OUTPUT"
},
{
"name": "actions.capability.SCREEN_OUTPUT"
}
]
}
]
}
An on the simulator, I end up getting this response: API Version 2: Failed to parse JSON response string with 'INVALID_ARGUMENT' error: ": Cannot find field.".
I can confirm that the incoming request has the google-actions-api-version set to 2.
This is the sharedDebugInfo section from the Actions on Google simulator:
[
{
"name": "ResponseValidation",
"subDebugEntry": [
{
"debugInfo": "API Version 2: Failed to parse JSON response string with 'INVALID_ARGUMENT' error: \": Cannot find field.\".",
"name": "UnparseableJsonResponse"
}
]
}
]
The oh-so-informative error message lacks the name of the field that cannot be found.
And this is when I'm doing everything described here
UPDATE - 01 February, 2018, 11:52 AM
These are the full contents of the debug tab:
{
"audioResponse": "//NExAAQaE...",
"conversationToken": "GidzaW11bG...",
"debugInfo": {
"agentToAssistantDebug": {
"agentToAssistantJson": "{\"conversationToken\":\"{\\\"state\\\":null,\\\"data\\\":{}}\",\"expectUserResponse\":true,\"expectedInputs\":[{\"inputPrompt\":{\"noInputPrompts\":[],\"richInitialPrompt\":{\"items\":[{\"simpleResponse\":{\"textToSpeech\":\"You're now talking to GActions Integration\",\"displayText\":\"You're now talking to GActions Integration\"}},{\"basicCard\":{\"buttons\":[{\"title\":\"Some Reddit to chill\",\"openUrlAction\":{\"url\":\"https://www.reddit.com\"}}],\"formattedText\":\"Here's some simp-wave to relax and chill out to\",\"image\":{\"url\":\"http://tracks.arte.tv/sites/default/files/styles/jscrop_1007x566/public/c_simpsons_2.jpg?itok=INzKpsvK\",\"accessibilityText\":\"A World of Simpsonwave\"},\"title\":\"A World of Simpsonwave\",\"imageDisplayOptions\":\"CROPPED\"}}],\"suggestions\":[]}},\"possibleIntents\":[{\"intent\":\"actions.intent.TEXT\"}]}],\"resetUserStorage\":false,\"userStorage\":\"{}\",\"finalResponse\":null,\"isInSandbox\":true,\"customPushMessage\":null,\"speech\":\"You're now talking to GActions Integration. \",\"displayText\":\"You're now talking to GActions Integration. \"}"
},
"assistantToAgentDebug": {
"assistantToAgentJson": "{\"user\":{\"userId\":\"ABwppHHHw9N9TYh-scJ5GhZtmpfFcQU2xbQBAgW1qhdllI45fimQ5QKFEVRfs2iMm6uCDJIQMApo1UZLmmnif8wqlNARnsVH744\",\"locale\":\"en-US\",\"lastSeen\":\"2018-02-01T06:04:59Z\"},\"conversation\":{\"conversationId\":\"1517465629416\",\"type\":\"NEW\"},\"inputs\":[{\"intent\":\"actions.intent.MAIN\",\"rawInputs\":[{\"inputType\":\"VOICE\",\"query\":\"Talk to Gactions Integration\"}]}],\"surface\":{\"capabilities\":[{\"name\":\"actions.capability.WEB_BROWSER\"},{\"name\":\"actions.capability.AUDIO_OUTPUT\"},{\"name\":\"actions.capability.SCREEN_OUTPUT\"},{\"name\":\"actions.capability.MEDIA_RESPONSE_AUDIO\"}]},\"isInSandbox\":true,\"availableSurfaces\":[{\"capabilities\":[{\"name\":\"actions.capability.SCREEN_OUTPUT\"},{\"name\":\"actions.capability.AUDIO_OUTPUT\"}]}]}",
"curlCommand": "curl -v https://firedev.arrowai.com/integrations/goog-actions/messages/59f6b4bf8d16126f008b456a/5a619bf72c971189008b4569 -H 'Content-Type: application/json;charset=UTF-8' -H 'Google-Actions-API-Version: 2' -H 'Authorization: eyJhbGciOiJSUzI1NiIsImtpZCI6IjI2YzAxOGIyMzNmZTJlZWY0N2ZlZGJiZGQ5Mzk4MTcwZmM5YjI5ZDgifQ.eyJhdWQiOiJnYWN0aW9ucy1hcnJvd2FpLWludGVncmF0ZSIsImF6cCI6IjQ2NDA5MDk1NDc2Ny0xOWl2bnUxdjFwYXFpdWdodDJqYXJwcTJwaGtmNGRyMS5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImV4cCI6MTUxNzQ2NTc0OSwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwianRpIjoiZTgxNzlmN2FkZGYwYWZmYjYwNzk0MDg5MzIyN2E0MWU1NDZkZWMyOCIsImlhdCI6MTUxNzQ2NTYyOSwibmJmIjoxNTE3NDY1MzI5fQ.rmUxBhIirV0UnZvTLYYw4AtRQfNDF-3O6CaoXxD2BDZSufgfKFHz7aIUgJzUillBm4zOLFiV6SnXndIoYCG6JK60YK5tHLpqbV-P3C-5U3N05RuY7hpj1Q_B027bgUv-p7QWlg-7DmPkROmN3b3a_FsUeWqTxx5Bu5M551k_CpKvSSBzPMEB2Cw9TzGkiY9avYrISTLLxWl2JE7qKAT6P0zBbE6cdO04CxGddGNmEPkckRICsdSJ5j_wmjy3I-ItAgx3dKoDfFLW6gCI4y5MFsGFJY6cFc2e01nodslf9GlQzAhR_a4aHRRIDl47HT_b9aKpyULV3kiRR41mUkyYZw' -A 'Mozilla/5.0 (compatible; Google-Cloud-Functions/2.1; +http://www.google.com/bot.html)' -X POST -d '{\"user\":{\"userId\":\"ABwppHHHw9N9TYh-scJ5GhZtmpfFcQU2xbQBAgW1qhdllI45fimQ5QKFEVRfs2iMm6uCDJIQMApo1UZLmmnif8wqlNARnsVH744\",\"locale\":\"en-US\",\"lastSeen\":\"2018-02-01T06:04:59Z\"},\"conversation\":{\"conversationId\":\"1517465629416\",\"type\":\"NEW\"},\"inputs\":[{\"intent\":\"actions.intent.MAIN\",\"rawInputs\":[{\"inputType\":\"VOICE\",\"query\":\"Talk to Gactions Integration\"}]}],\"surface\":{\"capabilities\":[{\"name\":\"actions.capability.WEB_BROWSER\"},{\"name\":\"actions.capability.AUDIO_OUTPUT\"},{\"name\":\"actions.capability.SCREEN_OUTPUT\"},{\"name\":\"actions.capability.MEDIA_RESPONSE_AUDIO\"}]},\"isInSandbox\":true,\"availableSurfaces\":[{\"capabilities\":[{\"name\":\"actions.capability.SCREEN_OUTPUT\"},{\"name\":\"actions.capability.AUDIO_OUTPUT\"}]}]}'"
},
"sharedDebugInfo": [
{
"name": "ResponseValidation",
"subDebugEntry": [
{
"debugInfo": "API Version 2: Failed to parse JSON response string with 'INVALID_ARGUMENT' error: \": Cannot find field.\".",
"name": "UnparseableJsonResponse"
}
]
}
]
},
"response": "Gactions integration isn't responding right now. Try again soon.",
"visualResponse": {
"visualElements": []
}
}
This the agentToAssistantDebug object:
{
"conversationToken": "{\"state\":null,\"data\":{}}",
"expectUserResponse": true,
"expectedInputs": [
{
"inputPrompt": {
"noInputPrompts": [],
"richInitialPrompt": {
"items": [
{
"simpleResponse": {
"textToSpeech": "You're now talking to GActions Integration",
"displayText": "You're now talking to GActions Integration"
}
},
{
"basicCard": {
"buttons": [
{
"title": "Some Reddit to chill",
"openUrlAction": {
"url": "https://www.reddit.com"
}
}
],
"formattedText": "Here's some simp-wave to relax and chill out to",
"image": {
"url": "http://tracks.arte.tv/sites/default/files/styles/jscrop_1007x566/public/c_simpsons_2.jpg?itok=INzKpsvK",
"accessibilityText": "A World of Simpsonwave"
},
"title": "A World of Simpsonwave",
"imageDisplayOptions": "CROPPED"
}
}
],
"suggestions": []
}
},
"possibleIntents": [
{
"intent": "actions.intent.TEXT"
}
]
}
],
"resetUserStorage": false,
"userStorage": "{}",
"finalResponse": null,
"isInSandbox": true,
"customPushMessage": null,
"speech": "You're now talking to GActions Integration. ",
"displayText": "You're now talking to GActions Integration. "
}
Additionally, this is the JSON response that is being generated:
{
"conversationToken": "{\"state\":null,\"data\":{}}",
"expectUserResponse": true,
"expectedInputs": [
{
"inputPrompt": {
"noInputPrompts": [],
"richInitialPrompt": {
"items": [
{
"simpleResponse": {
"textToSpeech": "You're now talking to GActions Integration",
"displayText": "You're now talking to GActions Integration"
}
},
{
"basicCard": {
"buttons": [
{
"title": "Some Reddit to chill",
"openUrlAction": {
"url": "https://www.reddit.com"
}
}
],
"formattedText": "Here's some simp-wave to relax and chill out to",
"image": {
"url": "http://tracks.arte.tv/sites/default/files/styles/jscrop_1007x566/public/c_simpsons_2.jpg?itok=INzKpsvK",
"accessibilityText": "A World of Simpsonwave"
},
"title": "A World of Simpsonwave",
"imageDisplayOptions": "CROPPED"
}
}
],
"suggestions": []
}
},
"possibleIntents": [
{
"intent": "actions.intent.TEXT"
}
]
}
],
"resetUserStorage": false,
"userStorage": "{}",
"finalResponse": null,
"isInSandbox": true,
"customPushMessage": null,
"speech": "You're now talking to GActions Integration. ",
"displayText": "You're now talking to GActions Integration. "
}
The issue seems to be related with your response JSON. As you can see in the documentation regarding the response JSON.
"speech": "You're now talking to GActions Integration. ",
"displayText": "You're now talking to GActions Integration. "
The above are not valid parameters. Instead your response JSON should look like:
{
"conversationToken": "{\"state\":null,\"data\":{}}",
"expectUserResponse": true,
"expectedInputs": [
{
"inputPrompt": {
"noInputPrompts": [],
"richInitialPrompt": {
"items": [
{
"simpleResponse": {
"textToSpeech": "You're now talking to GActions Integration",
"displayText": "You're now talking to GActions Integration"
}
},
{
"basicCard": {
"buttons": [
{
"title": "Some Reddit to chill",
"openUrlAction": {
"url": "https://www.reddit.com"
}
}
],
"formattedText": "Here's some simp-wave to relax and chill out to",
"image": {
"url": "http://tracks.arte.tv/sites/default/files/styles/jscrop_1007x566/public/c_simpsons_2.jpg?itok=INzKpsvK",
"accessibilityText": "A World of Simpsonwave"
},
"title": "A World of Simpsonwave",
"imageDisplayOptions": "CROPPED"
}
}
],
"suggestions": []
}
},
"possibleIntents": [
{
"intent": "actions.intent.TEXT"
}
]
}
],
"resetUserStorage": false,
"userStorage": "{}",
"finalResponse": null,
"isInSandbox": true,
"customPushMessage": null
}
Edit if you use the nodejs sdk check if the response from the google sdk function is correct json and if your server answer with correct json. maybe its change the format etc?
Try it with an action.json style like the first. And the more important question how is your response to the request looking? try that json in the style the last code below creates
{
"locale": "en",
"actions": [
{
"name": "text",
"intent": {
"name": "actions.intent.TEXT",
"trigger": {
"queryPatterns": [
"some text"
]
}
},
"fulfillment": {
"conversationName": "conv name"
}
},
{
"description": "Default Welcome Intent",
"name": "MAIN",
"fulfillment": {
"conversationName": "conv name"
},
"intent": {
"name": "actions.intent.MAIN",
"trigger": {
"queryPatterns": [
"open my test app",
"open hi fish"
]
}
}
}
],
"types": [],
"conversations": {
"conv name": {
"name": "conv name",
"url": "https://yourendpoint.com/something",
"fulfillmentApiVersion": 2,
"in_dialog_intents": [
{
"name": "actions.intent.NO_INPUT"
},
]
}
}
}
this here is a function that create an valid response:
/**
message = string
slots = array
state = object
**/
function answerWithMessage(message,slots,state){
let display = message.replace(/<[^>]*>/g, '');
let voicemessage = message.toLowerCase();
let jsonResponse = {
conversationToken: JSON.stringify(state),
expectUserResponse: true,
expectedInputs: [
{
inputPrompt: {
richInitialPrompt: {
items: [
{
simpleResponse: {
ssml: voicemessage,
displayText: display,
},
}
],
}
},
possibleIntents: [
{
intent: "actions.intent.TEXT"
}
],
speechBiasingHints: slots
}
]
};
return JSON.stringify(jsonResponse,null, 4);
}
And here is a repro with example google actions sdk: https://github.com/haukedau/googleActionsInit but the answer there is not completly correct but it will do the work as well above response message style is better.
Hi I think your issue is just you call an object
"inputPrompt": {
and an array but both are in the same position look in the explanation below.
"noInputPrompts": [],
https://developers.google.com/actions/assistant/helpers#calling_the_helper_1
I hope I could help you.
Best regards
Patrick