JavaScript/TypeScript SDK
This article provides an all-in-one example for JavaScript/TypeScript EVA ICS service.
Contents
JavaScript runtime
Note
EVA ICS has no built-in tools to install Node.js or other JavaScript server runtime. It must be installed manually.
JavaScript server runtime engines supported:
Other compatible engines can be used as well.
SDK module
The module can be found at: https://www.npmjs.com/package/@eva-ics/sdk and added to a project as:
npm i --save @eva-ics/sdk
API reference: https://pub.bma.ai/dev/docs/eva4-js-sdk/
See also: BUS/RT JavaScript/TypeScript client
Service example
The full example can be found at https://github.com/eva-ics/eva4/tree/main/bindings/js/example
Service code (TypeScript)
import {
createService,
ServiceInfo,
ServiceMethod,
Service,
OID,
pack,
unpack,
EventKind,
Action,
XCall,
XCallData,
EapiTopic,
noRpcMethod,
RpcEvent,
Logger,
sleep,
exit
} from "@eva-ics/sdk";
let service: Service;
/** The service can log messages to stdout (info) and stderr (error) via
* console.log methods but it is highly recommended to use the bus logger which
* is available automatically after the service bus has been initialized */
let log: Logger;
/** Bus frame handler */
const onFrame = async (e: RpcEvent): Promise<void> => {
if (!service.isActive()) {
return;
}
const payload = unpack(e.frame.getPayload());
// payloads may contain data, unserializable by the default JSON, so better
// log them to stdout
console.log(
"sender:",
e.frame.primary_sender,
"topic:",
e.frame.topic,
"payload:",
payload
);
if (e.frame.topic?.startsWith(EapiTopic.LocalStateTopic)) {
const oid = new OID(
e.frame.topic.slice(EapiTopic.LocalStateTopic.length),
true
);
log.info(
"received item state for",
oid.asString(),
`status: ${payload.status}`,
`value: ${payload.value}`
);
}
};
/** RPC calls handler */
const onRpcCall = async (e: RpcEvent): Promise<Buffer | undefined> => {
service.needReady();
const method = e.method?.toString();
const payload = unpack(e.getPayload());
switch (method) {
// process HTTP x-calls
case "x":
const xcall = new XCall(payload as XCallData);
xcall.acl.requireAdmin();
console.log(xcall);
return pack({ ok: true });
// process lmacro executions
case "run":
const lAction = new Action(payload);
console.log(lAction);
// mark action running
await service.controller.eventRunning(lAction);
// mark action failed
await service.controller.eventFailed(
lAction,
"lmacro execution",
"execution failed: not supported",
-15
);
return;
// process unit actions
case "action":
const uAction = new Action(payload);
console.log(uAction);
// mark action running
await service.controller.eventRunning(uAction);
// mark action completed
await service.controller.eventCompleted(uAction, "all fine");
// announce new unit state
const path = uAction.oid.asPath();
await service.bus.publish(
`${EapiTopic.RawStateTopic}${path}`,
pack({ status: 1, value: uAction.params?.value })
);
return;
// example RPC method which deals with the params payload
case "hello":
return payload?.name ? pack(`hello ${payload.name}`) : pack("hi there");
// get service initial payload
case "config":
return pack(service.initial);
default:
noRpcMethod();
}
};
const main = async () => {
// service info and RPC help
const info = new ServiceInfo({
author: "Bohemia Automation",
description: "Example JS service",
version: "0.0.1"
})
.addMethod(new ServiceMethod("hello").optional("name"))
.addMethod(new ServiceMethod("config"));
// create a service
service = await createService({
info,
onFrame,
onRpcCall
});
// set the global logger
log = service.logger;
// subscribe to item events
await service.subscribeOIDs(["#"], EventKind.Any);
// block the service while active
log.warn("ready");
await service.block();
log.warn("exiting");
// wait a bit to let tasks finish
await sleep(500);
// terminate the process with no error
exit();
};
main();
package.json
{
"name": "eva-ics-sdk-test",
"version": "0.0.1",
"main": "svc-example.ts",
"scripts": {
"build": "mkdir -p dist && npx esbuild svc-example.ts --bundle --platform=node --outfile=dist/out.js",
"build-release": "mkdir -p dist && npx esbuild svc-example.ts --bundle --platform=node --minify --outfile=dist/out-release.js"
},
"license": "MIT",
"dependencies": {
"@eva-ics/sdk": "^0.0.8"
},
"devDependencies": {
"@types/node": "^20.9.0",
"esbuild": "^0.19.5"
}
}
The service can be built as:
npm i
npm run build
Service template
The following template can be used to quickly create a service instance with eva-shell:
eva svc create my.svc.js_test svc-tpl.yml
bus:
path: var/bus.ipc
command: node /opt/eva4/bindings/js/example/dist/out.js
config:
a: 2
b: 3
user: nobody
HTTP API call example
The service responds to the following API calls (httpie call example):
(
cat <<EOF
{
"jsonrpc": "2.0",
"id": 1,
"method": "x::my.svc.js_test::some.method",
"params": {
"k": "mykey",
"name": "value"
}
}
EOF
) | http :7727
If using EVA ICS WebEngine, the call can be made as:
eva.call(
'x::my.svc.js_test::some.method',
{ param: "value" }
);
Running with Deno
Installation
Deno can be installed directly into /opt/eva4 folder with the following command:
curl -fsSL https://deno.land/x/install/install.sh | DENO_INSTALL=/opt/eva4/deno sh
Deno caches all modules in $HOME/.cache/deno folder. The cached modules can be purged if required, e.g. when migrating to a newer SDK version.
SDK imports
To run services, built with EVA ICS JS SDK, with, it is possible to modify imports as the following:
import {
// required imports
} from "npm:@eva-ics/sdk";
Service command
Run TypeScript service files directly with the following command field:
deno/bin/deno run --allow-read --allow-write --allow-ffi --allow-env --allow-net --unstable /path/to/svc.ts
It is recommended to start the service for the first time manually to let the runtime download all required modules or increase service startup timeout.