Call OPCUA method with struct input argument using node-opcua - node-opcua

I am trying to interface with an RFID reader which implements an OPC-UA server according to this specification.
I am trying to call the method ScanStart which takes the ScanSettings struct as an input argument (an AutoID datatype) but despite reading through the examples and documentation I can't figure out a way to do this.
Using UAExpert I can call the method and enter the values for the struct using the GUI which produces the following dump in wireshark:
ArraySize: 1
[0]: Variant
Variant Type: ExtensionObject (0x16)
Value: ExtensionObject
TypeId: ExpandedNodeId
EncodingMask: 0x01, EncodingMask: Four byte encoded Numeric
.... 0001 = EncodingMask: Four byte encoded Numeric (0x1)
.0.. .... = has server index: False
0... .... = has namespace uri: False
Namespace Index: 3
Identifier Numeric: 5015
EncodingMask: 0x01, has binary body
.... ...1 = has binary body: True
.... ..0. = has xml body: False
ByteString: 0000000000000000000000000000000000
Has anyone successfully managed to register an ExtensionObject for passing to a method call using node-opcua? At this point I am happy to just send the ByteString above without needing to encode/decode the struct as it is always static.
Apparently there is a constructExtensionObject method. The client code I have for this is:
(async () => {
const client = OPCUAClient.create({ endpoint_must_exist: false});
client.on("backoff", () => console.log("Backoff: trying to connect to ", endpointUri));
await client.withSessionAsync(endpointUri, async (session) => {
let scanSettings = {
Duration: 0,
Cyles: 0,
DataAvailble: false
};
const nodeID = new NodeId(NodeIdType.STRING, "rfr310.ScanStart.InputArguments", 4);
const extObj = session.constructExtensionObject(nodeID, scanSettings);
const methodsToCall = [
{
objectId: "ns=4;s=rfr310",
methodId: "ns=4;s=rfr310.ScanStart",
inputArguments: [extObj]
}
];
extObj.then(() => {
session.call(methodsToCall,(err,results) => {
if (err) {
console.log(err);
} else {
console.log(results);
}
});
}).catch(() => {
})
});
})();
produces the error "dispose when pendingTransactions is not empty", which is caught by the extObj.catch()
What am I doing wrong? I'm fairly certain this is a promise handling issue on my part...
Any help is appreciated!

OK so I finally got there. Here is the method to call an OPC-UA method with a struct input argument using node-opcua:
const { OPCUAClient, NodeId, NodeIdType, DataType} = require("node-opcua");
const endpointUri = "opc.tcp://<your-endpoint>:<your-port>";
(async () => {
const client = OPCUAClient.create({ endpoint_must_exist: false});
client.on("backoff", () => console.log("Backoff: trying to connect to ", endpointUri));
await client.withSessionAsync(endpointUri, async (session) => {
// Scan settings value input
const scanSettingsParams = {
duration: 0,
cycles : 0,
dataAvailable : false,
locationType: 0
};
try {
// NodeID for InputArguments struct type (inherits from ScanSettings)
const nodeID = new NodeId(NodeIdType.NUMERIC, 3010, 3);
// Create ExtensionObject for InputArguments
const scanSettingsObj = await session.constructExtensionObject(nodeID, scanSettingsParams);
// Populate Method call with ExtensionObject as InputArgument
const methodToCall = {
objectId: "ns=4;s=rfr310",
methodId: "ns=4;s=rfr310.ScanStart",
inputArguments: [
{
dataType: DataType.ExtensionObject,
value: scanSettingsObj
}
]
};
// Call method, passing ScanSettings as input argument
session.call(methodToCall,(err,results) => {
if (err) {
console.log(err);
} else {
console.log(results);
}
});
} catch (err) {
console.log(err);
}
});
})();

Related

When I try to override a core function in converse.js I get an error

I am using converse.js 7.0.6 version. I am trying to set the subject topic button to be by default hidden, currently, it is visible by default. I have investigated I need to override this function toggleSubjectHiddenState, when I try to use _converse.api.user. I get an error TypeError: Cannot read properties of undefined (reading 'user')
This is how I tried, maybe I am doing wrong something.
export const addModifyHideTopicPlugin = () => {
window.converse.plugins.add('modify-hide-topic', {
overrides: {
ChatRoom: {
toggleSubjectHiddenState: async function () {
const _converse: any = this;
// const muc_jid = this.get('jid');
const jids = await _converse.api.user.settings.get(
'mucs_with_hidden_subject',
[],
);
return _converse.toggleSubjectHiddenState
.apply(_converse, arguments)
.then((response: any) => {
console.log(response, 'response');
return response;
});
},
},
},
});
};
This is the toggleSubjectHiddenState function I am trying to override because this function is handleing the show topic or hide topic.
async toggleSubjectHiddenState () {
const muc_jid = this.get('jid');
const jids = await api.user.settings.get('mucs_with_hidden_subject', []);
if (jids.includes(this.get('jid'))) {
api.user.settings.set('mucs_with_hidden_subject', jids.filter(jid => jid !== muc_jid));
} else {
api.user.settings.set('mucs_with_hidden_subject', [...jids, muc_jid]);
}
},

Redux toolkit createAsyncThunk using Parameter

I need your help.
We implemented createAsyncThunk using the Redux Toolkit.
However, as shown in the picture, I need a function to change ChannelId flexibly. Can't I use the parameter in createAsyncThunk?
How can I use it if I can?
Or is there any other way?
I am sorry that the quality of the question is low because I am Korean using a translator.
enter image description here
// ACTION
export const getYoutubeList_PlayList = createAsyncThunk(
"GET/YOUTUBE_PLAYLIST",
async (data, thunkAPI) => {
try {
const { data } = await axios.get<youtubeResponse>(
`https://www.googleapis.com/youtube/v3/playlists?key=${youTubeAcsses.apiKey}&channelId=${channelId}&part=snippet&maxResults=30`
)
return data
} catch (err: any) {
return thunkAPI.rejectWithValue({
errorMessage: '호출에 실패했습니다.'
})
}
}
);
// SLICE
const youtube_PlaylistSlice = createSlice({
name: "YOUTUBE_PLAYLIST",
initialState,
reducers: {},
// createAsyncThunk 호출 처리 = extraReducers
extraReducers(builder) {
builder
.addCase(getYoutubeList_PlayList.pending, (state, action) => {
state.loading = true;
})
.addCase(getYoutubeList_PlayList.fulfilled, (state, action: PayloadAction<youtubeResponse>) => {
state.loading = false;
state.data = action.payload;
})
.addCase(getYoutubeList_PlayList.rejected, (state, action: PayloadAction<any>) => {
state.error = action.payload;
});
},
});
You named both the incoming argument data as well as the result of your axios call. That will "shadow" the original data and you cannot access it any more. Give those two variables different names.
Here I called it arg, which allows you to access arg.channelId.
export const getYoutubeList_PlayList = createAsyncThunk(
"GET/YOUTUBE_PLAYLIST",
async (arg, thunkAPI) => {
try {
const { data } = await axios.get<youtubeResponse>(
`https://www.googleapis.com/youtube/v3/playlists?key=${youTubeAcsses.apiKey}&channelId=${arg.channelId}&part=snippet&maxResults=30`
)
return data
} catch (err: any) {
return thunkAPI.rejectWithValue({
errorMessage: '호출에 실패했습니다.'
})
}
}
);
You would now dispatch this as dispatch(getYoutubeList_PlayList({ channelId: 5 }))

Failed to add new elements when set initialState as an empty object

I try to use redux toolkit and I have this as menu-slice.js
I try to use property accessors to add a new property to fileItems, its initial value is an empty object.
import { createSlice } from "#reduxjs/toolkit";
const menuSlice = createSlice({
name: "ui",
initialState: {
fileItems: {},
},
reducers: {
setFileDate: (state, action) => {
state.FileDate = action.payload;
},
replaceFileItems: (state, action) => {
const filesList = action.payload.map((fileName) =>
fileName.slice(fileName.indexOf("/") + 1)
);
state.fileItems[state.FileDate] = filesList;
console.log(`filesList: ${filesList}`);
console.log(`state.fileItems: ${JSON.stringify(state.fileItems)}`);
console.log(`state.FileDate: ${state.FileDate}`);
state.fileContents = null;
},
I call dispatch with the api return value ( dispatch(menuActions.replaceFileItems(fileResponse.data));)
in menu-action.js:
the return value is an array of strings.
export const fetchFiles = (fileDate) => {
return async (dispatch) => {
const fetchFilesList = async () => {
const response = await fetch(
"some url" +
new URLSearchParams({
env: "https://env.com",
date: fileDate,
})
);
if (!response.ok) {
throw new Error("Fail to fetch files list!");
}
const data = await response.json();
return data;
};
try {
const fileResponse = await fetchFilesList();
dispatch(menuActions.setFileDate(FileDate));
dispatch(menuActions.replaceFileItems(fileResponse.data));
} catch (error) {
dispatch(
menuActions.showNotification({....
})
);
}
};
};
But it never prints console logs and didn't display where went wrong in the console or in the chrome redux extension.
I want to add data into state.fileItems on each click that triggers fetchFiles() when it returns a new array:
from state.fileItems = {}
check if state.fileItems already has the date as key,
if not already has the date as key,
change to ex: state.fileItems = {"2022-01-01": Array(2)}
and so on..
ex: state.fileItems = { "2022-01-01": Array(2), "2022-01-02": Array(2) }
I also tried to set state.fileItems as an empty array, and use push, but it didn't work either, nothing printed out, state.fileItems value was always undefined.
Can anyone please tell me why this didn't work?
Thanks for your time to read my question.

I18Next TFunction translation not working in EJS template render

I have method that renders EJS templates and pass in the i18next.t function for the EJS template to do translations by setting the i18next t function as an attribute on the data object:
const data = {
email: user.email,
id: user.id,
t: i18nT
};
The data object is passed into ejs.renderFile(). The only way I can get the translation in the EJS template to work is when I set the i18nT variable to the t function from the i18next.init() functions call back. Otherwise it comes out blank. I see from the console output that the t function of the i18next instance, i18nInstance, is different to the t function set by the callback when initializing i18next.
function t() {
var _this$translator;
return this.translator && (_this$translator =
this.translator).translate.apply(_this$translator, arguments);
}
versus:
function () {
return _this4.t.apply(_this4, arguments);
}
Why is the t function of the i18nInstance object obtained from calling i18next.createInstance() different than the one from the callback? The one from the instance object doesn not work in the EJS template render.
The full code sample:
let i18nInstance: i18n;
let i18nT;
const i18nextInitOptions = {
backend: {
loadPath: path.join(__dirname, '/locales/{{lng}}/{{ns}}.json'),
addPath: path.join(__dirname, '/locales/{{lng}}/{{ns}}.missing.json')
},
debug: true,
fallbackLng: 'da',
preload: ['da', 'en', 'nl'],
returnEmptyString: false,
returnNull: false,
saveMissing: true
};
i18nInstance = await i18next
.createInstance();
await i18nInstance
.use(i18nextBackend)
.init(i18nextInitOptions, async function (error, t) {
if (error) {
console.log(error)
}
i18nT = t;
});
console.log("i18nT: " + i18nT)
/*
The console.log outputs below show that i18nT when set from the callback is different to the
i18nInstance.t.
i18nT: function () {
return _this4.t.apply(_this4, arguments);
}
*/
console.log("i18nInstance.t: " + i18nInstance.t)
/*
console output:
i18nInstance.t: function t() {
var _this$translator;
return this.translator && (_this$translator =
this.translator).translate.apply(_this$translator, arguments);
}
*/
const data1 = {
email: user.email,
id: user.id,
t: i18nT
};
// Calling htmlFromTemplate with data1 with t = i18nT the translation in the EJS template works.
html = await this.htmlFromTemplate('ejsTemplateName.ejs', data1);
const data2 = {
email: user.email,
id: user.id,
t: i18nextInstance.t
};
// Calling htmlFromTemplate with data2 with t = i18nextInstance.t the translation in the EJS is empty.
html = await this.htmlFromTemplate('ejsTemplateName.ejs', data2);
private htmlFromTemplate(templateName: string, data: Object): Promise<String> {
if (!templateName) return;
const htmlPath = path.join(__dirname, '../assets/mail-templates/' + templateName);
return new Promise((resolve, reject) => {
ejs.renderFile(htmlPath, data,(renderErr, str) => {
if (renderErr) {
appLogger.error('MAIL_RENDER: ' + renderErr, { templateName, data });
reject(renderErr);
} else resolve(str);
});
});
}
As soon as you pass the t function like this:
const data2 = {
email: user.email,
id: user.id,
t: i18nextInstance.t
};
the t function is not bound to its original "this" anymore...
pass it this way:
const data2 = {
email: user.email,
id: user.id,
t: i18nextInstance.t.bind(i18nextInstance)
};
more information here: https://github.com/i18next/i18next/issues/1528#issuecomment-748263313

How to invoke openwhisk action within openwhisk platform on bluemix?

I have created two actions on OpenWhisk on Bluemix. Both independently work fine when I can call them from outside the OpenWhisk platform. But I want to call action1 from within action2, and am using the following syntax:
var openwhisk = require('openwhisk');
function main(args){
const name = 'action2';
const blocking = true;
const params = { param1: 'sthing'};
var ow = openwhisk();
ow.actions.invoke({name, blocking, params})
.then(result => {
console.log('result: ', result);
return result; // ?
}).catch(err => {
console.error('failed to invoke actions', err);
});
}
But I get an empty result and no console messages. Some help would be great.
Update1:
When adding as suggested the return option, to return the Promise of OpenWhisk, as follows:
return ow.actions.invoke({name, blocking, params})
.then(result => {
console.log('result: ', result);
return result;
}).catch(err => {
console.error('failed to invoke actions', err);
throw err;
});
the response value of action2 is not as expected but contains:
{ "isFulfilled": false, "isRejected": false }
where I expect the return message of action2 (which reads a Google Sheets API) and parses the result:
{
"duration": 139,
"name": "getEventCfps",
"subject": "me#email.com",
...
"response": {
"result": {
"message": [
{
"location": "Atlanta, GA",
"url": "https://werise.tech/",
"event": "We RISE Women in Tech Conference",
"cfp-deadline": "3/31/2017",
...
}
]
},
"success": true,
"status": "success"
},
...
}
So I am expecting I am not parsing the '.then(result' variable in action1 correctly? cause when I test action2 separately, from outside OpenWhisk via Postman or API Connect, or directly by 'Run this action' in OpenWhisk/Bluemix it returns the correct values.
Update2:
Alright solved. I was calling the ow.actions.invoke to action2 in a function that was called within the action1, this nesting of returns, caused the issue. When I moved the invoke code directly in the main function, all resolved as expected. Double trouble when nesting promises and returns. Mea culpa. Thanks everyone
You need to return a Promise in your function try this
var openwhisk = require('openwhisk');
function main(args){
const name = '/whisk.system/utils/echo';
const blocking = true;
const params = { param1: 'sthing'};
var ow = openwhisk();
return ow.actions.invoke({name, blocking, params})
.then(result => {
console.log('result: ', result);
return result;
}).catch(err => {
console.error('failed to invoke actions', err);
throw err;
});
}
If you just want to invoke the action:
var openwhisk = require('openwhisk');
function main(args) {
var ow = openwhisk();
const name = args.action;
const blocking = false
const result = false
const params = args;
ow.actions.invoke({
name,
blocking,
result,
params
});
return {
statusCode: 200,
body: 'Action ' + name + ' invoked successfully'
};
}
If you want to wait for the result of the invoked action:
var openwhisk = require('openwhisk');
function main(args) {
var ow = openwhisk();
const name = args.action;
const blocking = false
const result = false
const params = args;
return ow.actions.invoke({
name,
blocking,
result,
params
}).then(function (res) {
return {
statusCode: 200,
body: res
};
});
}