I want to report the device status to Google Home Graph via RPC communication. In the program, I use @grpc/grpc-js library and refer to the data structure agreed in Google official document (https://developers.home.google.com/reference/home-graph/rpc/google.home.graph.v1#google.home.graph.v1.ReportStateAndNotificationRequest) to call reportStateAndNotification function, but it always prompts me that the parameters are wrong. The error message is "Error: 3 INVALID_ARGUMENT: Request contains an invalid argument."
The code I use is the following :
const grpc = require('@grpc/grpc-js');
const { GoogleAuth } = require('google-auth-library');
const protoLoader = require('@grpc/proto-loader');
const protos = require('google-proto-files');
const uuid = require('uuid');
async function getCredentials() {
const sslCredentials = grpc.credentials.createSsl();
const googleAuth = new GoogleAuth({
scopes: 'https://www.googleapis.com/auth/homegraph'
});
const authClient = await googleAuth.getClient();
const callCredentials = grpc.credentials.createFromGoogleCredential(
authClient
);
const credentials = grpc.credentials.combineChannelCredentials(
sslCredentials,
callCredentials
);
return credentials;
}
async function getHomegraph() {
const homegraphProto = await protoLoader.load(
protos.getProtoPath('home/graph', 'v1', 'homegraph.proto'), {
includeDirs: [protos.getProtoPath('..')]
}
);
const homegraph = grpc.loadPackageDefinition(
homegraphProto
).google.home.graph.v1;
return homegraph;
}
(async function () {
const credentials = await getCredentials();
const homegraph = await getHomegraph();
const homegraphService = new homegraph.HomeGraphApiService(
'homegraph.googleapis.com', credentials
);
const changeReportData = {
"requestId": uuid.v4(),
"agentUserId": "51f82f14-aba6-417e-8ca8-5827dc5b3de6",
"payload": {
"devices": {
"states": {
"10002b10b4": {
"on": true,
"online": true
}
}
}
}
};
homegraphService.reportStateAndNotification(changeReportData, function (err, result) {
if (err) {
console.error(err);
} else {
console.log(result);
}
});
})();
Data sent
test1
{
"requestId": "43c81c50-7628-44e7-9668-14ef3ea5e759",
"agentUserId": "51f82f14-aba6-417e-8ca8-5827dc5b3de6",
"payload": {
"devices": {
"states": {
"10002b10b4": {
"on": true,
"online": true
}
}
}
}
}
test2
{
"request_id": "d887f9ee-81eb-480b-9bf3-412132ec7cec",
"agent_user_id": "51f82f14-aba6-417e-8ca8-5827dc5b3de6",
"payload": {
"devices": {
"states": {
"10002b10b4": {
"on": true,
"online": true
}
}
}
}
}
This error show all the time no matter data I sended with name agent_user_id or agentUserId like above. But I was written according to the official example ReportStateAndNotificationRequest.
In the request, payload.devices.states
has the type Struct
. The representation of a Struct
in protobuf is not a simple JSON object, but a map of field names to Value
objects. For example, in your test, the states object
{
"10002b10b4": {
"on": true,
"online": true
}
}
would be represented like this:
{
"10002b10b4": {
"struct_value": {
"on": {
"bool_value": true
},
"online": {
"bool_value": true
}
}
}
}
It is possible to perform this transformation automatically, using a function like this one in @grpc/grpc-js-xds
:
function validateValue(obj: any): Value {
if (Array.isArray(obj)) {
return {
kind: 'listValue',
listValue: {
values: obj.map((value) => validateValue(value)),
},
};
} else {
switch (typeof obj) {
case 'boolean':
return {
kind: 'boolValue',
boolValue: obj,
};
case 'number':
return {
kind: 'numberValue',
numberValue: obj,
};
case 'string':
return {
kind: 'stringValue',
stringValue: obj,
};
case 'object':
if (obj === null) {
return {
kind: 'nullValue',
nullValue: 'NULL_VALUE',
};
} else {
return {
kind: 'structValue',
structValue: getStructFromJson(obj),
};
}
default:
throw new Error(`Could not handle struct value of type ${typeof obj}`);
}
}
}
function getStructFromJson(obj: any): Struct {
if (typeof obj !== 'object' || obj === null) {
throw new Error('Invalid JSON object for Struct field');
}
const fields: { [key: string]: Value } = {};
for (const [fieldName, value] of Object.entries(obj)) {
fields[fieldName] = validateValue(value);
}
return {
fields,
};
}