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);
}
});
})();
I am new to testing and having difficulty with Sinon stubs and mocks.
How can I write test for 'quote.list_quote' for following senario.
Here is the routes file : quotes.js
const express = require('express');
const request = require('request');
const async = require('async');
const validator = require('validator');
const quote_router = express.Router();
const confg = require("../../confg/confg");
const quote = require("../models/mquotes");
const quotes_model = quote.quotes;
// host name - needs to be set up using the environment variable
const hostname = confg.hostname;
// route for "quotes/"
quote_router.route("/")
// get route : display the random quote
.get((req, res) => {
// display random quote
async.waterfall([
(callback) => {callback(null, {res});},
quote.count_quotes
], check_quote_exist
);
})
// post route : create a new quote
.post((req, res) => {
const doc_json = {author : validator.escape(req.body.quote_author), quote_text : validator.escape(req.body.quote_text)};
const params = {res, doc_json, quote_action : quote.create_quote};
add_edit_quote(params);
})
// put route : edit the quote
.put((req, res) => {
const doc_json = {author : validator.escape(req.body.quote_author), quote_text : validator.escape(req.body.quote_text)};
const params = {res, doc_json, quote_action : quote.update_quote, qid : req.body.quote_id};
add_edit_quote(params);
})
// delete quote : delete the quote
.delete((req, res) => {
const qid = req.body.qid;
const condition = {_id : qid};
async.waterfall([
(callback) => {callback(null, {res, condition});},
quote.delete_quote
], request_quote_list
);
});
// route for "quotes/list" : display quotes list
quote_router.get("/list/", (req, res) => {
// mention the main operation
let operation;
if(req.body.operation != 'undefined') {
operation = req.body.operation;
} else {
operation = "list_quotes";
}
async.waterfall([
(callback) => {callback(null, {res, operation});},
quote.list_quote
], display_quotes_list
);
});
// display the quotes list
const display_quotes_list = (err, params, quotes_list) => {
if (err) {return console.log(err);}
const res = params.res;
const operation = params.operation;
const header_msg = "List of all the quotes";
let alert_msg;
if(operation == "list_quotes") {
alert_msg = null;
} else if(operation == "delete_quote") {
alert_msg = "Quote has been deleted";
}
const params_out = {
page: "quote_list",
title: 'Quotes Manager',
host: hostname,
header_msg,
alert_msg,
quotes_list
};
res.render('index', params_out);
};
// send http request for quote list page
const request_quote_list = (err, params) => {
if (err) {return console.log(err);}
const res = params.res;
const operation = "delete_quote";
request.get('http://' + hostname + '/quotes/list/', {json:{operation}},
(error, response, body) => {
if (!error && response.statusCode == 200) {
res.send(body);
}
});
};
module.exports = quote_router;
This is not complete file. I have included only a portion of it.
And her is the model file : mquotes.js
const mongoose = require('mongoose');
// Define quote schema
const quoteSchema = new mongoose.Schema({
author: String,
quote_text: {type: String, required: true}
},
{timestamps: true}
);
const quote = {};
// Define quotes model
quote.quotes = mongoose.model('quotes', quoteSchema);
// error handler
error_handler = (callback, err, params, return_value) => {
if(err) { return callback(err);}
else {callback(null, params, return_value);}
};
// add quote - create
quote.create_quote = (params, callback) => {
const res = params.res;
const doc_json = params.doc_json;
quote.quotes.create(doc_json, (err, quotes_detail) => {
error_handler(callback, err, {res, operation : 'create_quote'}, quotes_detail);
});
};
// count the number of quotes
quote.count_quotes = (params, callback) => {
quote.quotes.count({}, (err, quotes_count) => {
error_handler(callback, err, params, quotes_count);
});
};
// delete quote - delete - id
quote.delete_quote = (params, callback) => {
quote.quotes.remove(params.condition, (err, query) => {
error_handler(callback, err, params);
});
};
// list quote - find
quote.list_quote = (params, callback) => {
quote.quotes.find({}, (err, quotes_list) => {
error_handler(callback, err, params, quotes_list);
});
};
// find quote by id
quote.quote_by_id = (params, callback) => {
quote.quotes.findById(params.qid, (err, quotes_detail) => {
error_handler(callback, err, params, quotes_detail);
});
};
// returns the detail of random quote
quote.random_qoute = (params, callback) => {
const random_number = params.random_number;
// select one quote after skipping random_number of times
quote.quotes.findOne({}, (err, quotes_detail) => {
error_handler(callback, err, params, quotes_detail);
}).skip(random_number);
};
// update quote - update - id
quote.update_quote = (params, callback) => {
const options = {new: true};
const qid = params.qid;
const update_json = params.doc_json;
quote.quotes.findByIdAndUpdate(qid, {$set: update_json}, options, (err, quotes_detail) => {
params.operation = 'update_quote';
error_handler(callback, err, params, quotes_detail);
});
};
module.exports = quote;
I have installed mocha globally. Now, I want to test the model. Lets take the quote.list_quote function for example.
const mongoose = require('mongoose');
const chai = require('chai');
const sinon = require('sinon');
const expect = chai.expect; // use the "expect" style of Chai
const mquotes = require('./../../app/models/mquotes');
describe('Tests for quote models', () => {
describe("List quote", () => {
it('list_quote() should return list of quotes', () => {
});
});
});
Can anyone tell me about my coding practice too. I mean the way I use functions and modules.
First of all, you should try to use statics methods. And after that, you should use sinon-mongoose and sinon-as-promised if you want to use Promise in mongoose.
And this is my sample code and test with mocha, chai, and sinon. Hope useful for you.
model.js
var Schema = new mongoose.Schema({
name: String,
created_at: {
type: Date,
default: Date.now
},
updated_at: {
type: Date,
default: Date.now
}
});
Schema.statics.findByName = function(name, cb) {
this.findOne({
name: name
})
.exec()
.then(function getTemplate(template) {
if (!template) {
var error = new Error('Not found template by name: "' + name + '"');
error.status = 404;
return cb(error);
}
return cb(null, template);
})
.catch(function catchErrorWhenFindByTemplateName(error) {
error.status = 500;
return cb(error);
});
}
module.exports = mongoose.model('model', Schema);
test.js
var expect = require('chai').expect,
sinon = require('sinon'),
mongoose = require('mongoose');
require('sinon-as-promised');
require('sinon-mongoose');
var Model = require('../../app/models/model');
describe('Model', function () {
describe('static methods', function () {
describe('#findByName', function () {
var ModelMock;
beforeEach(function () {
ModelMock = sinon.mock(Model);
});
afterEach(function () {
ModelMock.restore();
});
it('should get error status 404 if not found template', function (done) {
var name = 'temp';
ModelMock
.expects('findOne').withArgs({name: name})
.chain('exec')
.resolves(null);
Model.findByName(name, function (error) {
expect(error.status).to.eql(404);
ModelMock.verify();
done();
});
});
it('should get message not found template if name is not existed', function (done) {
var name = 'temp';
ModelMock
.expects('findOne').withArgs({name: name})
.chain('exec')
.resolves(null);
Model.findByName(name, function (error) {
expect(error.message).to.match(/Not found template by name/gi);
ModelMock.verify();
done();
});
});
it('should get template when name is existed', function (done) {
var name = 'temp';
ModelMock
.expects('findOne').withArgs({name: name})
.chain('exec')
.resolves('SUCCESS');
Model.findByName(name, function (error) {
expect(error).to.be.null;
ModelMock.verify();
done();
});
});
it('should get error status 500 when model crashed', function (done) {
var name = 'temp';
ModelMock
.expects('findOne').withArgs({name: name})
.chain('exec')
.rejects(new Error('Oops! Crashed'));
Model.findByName(name, function (error) {
expect(error.status).to.eql(500);
ModelMock.verify();
done();
});
});
});
});
});
Is there a way to set a global api_root attribute instead of repeating the declaration over and over in the codebase?
So instead of:
var UserModel = Backbone.Model.extend({
urlRoot: '/user',
defaults: {
name: '',
email: ''
}
});
var user = new UserModel();
user.save(userDetails, {
success: function (user) {
alert(user.toJSON());
}
});
I could have set an app-wide attribute like:
app.api_root = 'https://api.ltmo.com/';
And then just map according to convention:
var UserModel = Backbone.Model.extend({ // maps to https://api.ltmo.com/users/
defaults: {
name: '',
email: ''
}
});
var user = new UserModel();
user.save(userDetails, {
success: function (user) {
alert(user.toJSON());
}
});
you could solve this on the jQuery level with ajaxprefilter:
$.ajaxPrefilter(function(options) {
options.url = 'https://api.ltmo.com/' + options.url;
});
I read the documentation but found nothing related to setting parameters in dataSource urls. Is it possible to achieve that?
Thx in advance.
Yes, it is possible. The urls defined in the DataSource.transport might be a function. This function receives (for update) as first argument the data being updated (the model) and returns the string that should be used as URL.
Composing the URL for what you want to do is:
var ds = new kendo.data.DataSource({
transport: {
read: {
url: function () {
return 'read';
}
},
update: {
url : function (item) {
return 'update/' + item.id;
}
}
}
});
The answer seems to be vague on 'item.'
Just note that 'item' is an object. In fact anything passed in to read has to be an object, that's what Kendo expects. If you pass anything else into read, like a string, it will convert it into an object which isn't what you want. So, the solution is as follows:
_viewModel: kendo.observable({
items: new kendo.data.DataSource({
transport: {
read: {
url: function (args) {
var urlParm = '?take=' + 1 + '&skip=0&page=1&pageSize=' + 1;
return CGI_ISD._base + 'api/executionsummary/executiondetails/' + args.msgId + urlParm;
},
dataType: "json"
},
},
schema: {
data: function (response) {
return response.AggregateData.Data;
}
}
}),
}),
_reload: function (msgId) {
this._viewModel.items.read({msgId: msgId});
}
Short answer:
Nope.
Long answer:
Parameters are passed either inline with the url parameter of the transport object...
var id = 'abc123';
var ds = new kendo.data.DataSource({
transport: {
read: {
url: 'api/employees?id=' + id
}
}
});
...or they are passed in the data parameter of the transport object.
var id = 'abc123';
var ds = new kendo.data.DataSource({
transport: {
read: {
url: 'api/employees',
data: {
id: id;
}
}
}
});
or
var id = 'abc123';
var ds = new kendo.data.DataSource({
transport: {
read: {
url: 'api/employees',
data: function () {
return { id : id };
}
}
}
});
I've this javascript viewmodel defined:
function PersonViewModel() {
// Data members
this.Name = ko.observable();
this.Function_Id = ko.observable();
this.SubFunction_Id = ko.observable();
this.Functions = ko.observableArray();
this.SubFunctions = ko.observableArray();
// Whenever the Function changes, update the SubFunctions selection
this.Function_Id.subscribe(function (id) {
this.GetSubFunctions(id);
}, this);
// Functions to get data from server
this.Init = function () {
this.GetFunctions();
this.Function_Id('#(Model.Function_Id)');
};
this.GetFunctions = function () {
var vm = this;
$.getJSON(
'#Url.Action("GetFunctions", "Function")',
function (data) {
vm.Functions(data);
}
);
};
this.GetSubFunctions = function (Function_Id) {
var vm = this;
if (Function_Id != null) {
$.getJSON(
'#Url.Action("GetSubFunctions", "Function")',
{ Function_Id: Function_Id },
function (data) {
vm.SubFunctions(data);
}
);
}
else {
vm.SubFunction_Id(0);
vm.SubFunctions([]);
}
};
this.Save = function () {
var PostData = ko.toJSON(this);
var d = $.dump(PostData);
alert(d);
$.ajax({
type: 'POST',
url: '/Person/Save',
data: PostData,
contentType: 'application/json',
success: function (data) {
alert(data);
}
});
};
}
$(document).ready(function () {
var personViewModel = new PersonViewModel();
personViewModel.Init();
ko.applyBindings(personViewModel);
});
When the Submit button is clicked, the data from the select lists is posted, but NOT the 'Function_Id'.
When I choose a different value in the Function dropdown list, and the click the Submit button, the value for 'Function_Id' is correctly posted.
How to fix this ?
It's because the scope of the this keyword in javascript
this.Init = function () {
this.GetFunctions(); // this === PersonViewModel.Init
this.Function_Id('#(Model.Function_Id)'); // calls PersonViewModel.Init.Function_Id(...)
};
You should store the refrence to the PersonViewModel instance.
var self = this;
self.Init = function () {
self.GetFunctions();
self.Function_Id('#(Model.Function_Id)'); // calls PersonViewModel.Function_Id(...)
};