Install the required software
You will need a text editor like Visual Studio Code and the Node.js runtime environment.
For the Windows operating system, it is recommended to install the official 18.16.0 LTS. However, for Linux and Mac, installing the Node Version Manager (NVM) and then Node.js has proven to be easier. Here's how to do it:
❯ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
❯ nvm install 18
Reopen your terminal and check if the installation was successful:
❯ node -v
v18.16.0
Create the project
You will use a modern version of the React framework for the web called Next.js Once you run the command for creating the project, it will be installed automatically.
Run the following command:
❯ npx create-next-app@latest
Please provide the following answers when creating the project:
Need to install the following packages:
create-next-app@13.4.6
Ok to proceed? (y) y
? What is your project named? › bgd10y-workshop
? Would you like to use TypeScript with this project? › Yes
? Would you like to use ESLint with this project? › Yes
? Would you like to use Tailwind CSS with this project? › Yes
? Would you like to use `src/` directory with this project? › No
? Use App Router (recommended)? › Yes
? Would you like to customize the default import alias? › No
Once the project has been created, open it with Visual Studio Code (File / Open Folder...).
Run the project
Open your terminal, navigate to the bgd10y-workshop
directory, and run the following
command:
❯ npm run dev
The previous command will start a web server, and your app will be accessible at http://localhost:3000.
Clean up the generated code and set up the styling
Open the app/layout.tsx
and change its metadata object to:
export const metadata = {
title: "bgd10y-workshop",
description: "Workshop for the Synapticon@Belgrade 10-year anniversary",
};
Open the app/page.tsx
file and replace its content with this:
"use client";
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center p-24 gap-y-8">
<h2 className="text-5xl font-thin">bgd10y-workshop</h2>
</main>
);
}
Then, replace the content of the app/globals.css
with this file.
Create the client object
You will use the client object to make requests to the Motion Master server. In order to create it, you
first need to install the motion-master-client
library as a dependency. From the
bgd10y-workshop
directory, run the following command:
› npm install motion-master-client
Then, create the app/client.ts
file and add the following code to it:
import { createMotionMasterClient } from "motion-master-client";
export const client = createMotionMasterClient("192.168.200.253");
client.whenReady().then(() => {
client.request.download(0, 0x6060, 0, 1);
});
The previous code will create the client object and, in doing so, connect to the running instance of the Motion Master server at 192.168.200.253. When the client is ready, we set the modes of operation to the profile position mode.
Create your first component and test the client object
The first component you will create will use the client object to get the system version, where the system refers to Motion Master.
Create the app/system-version.tsx
file and set its content to:
"use client";
import { useEffect, useState } from "react";
import { client } from "./client";
export default function SystemVersion() {
const [systemVersion, setSystemVersion] = useState("0.0.0");
useEffect(() => {
client.request.getSystemVersion(3000).subscribe((status) => {
setSystemVersion(status.version ?? "0.0.0");
});
}, []);
return (
<div>
<strong>System Version</strong>: <span className="font-mono">v{systemVersion}</span>
</div>
);
}
Code explanation
We create the systemVersion
state
variable. Simple variables won't suffice because they don't persist the data between renders. The
component will initally display '0.0.0', but when setSystemVersion
is called, it will
trigger a re-render and display the actual system version. Functions to make requests to Motion
Master
reside in the client.request
object. All requests have a client timeout, such as 3
seconds
for the getSystemVersion
function. They typically return an observable that you need to
subscribe to in order to receive a response sometime in the future. All request functions are
asynchronous.
Now that you have your first component, you need to display it. Open the app/page.tsx
and
import it:
"use client";
import SystemVersion from "./system-version";
Then, add it to the main element of the Home component:
…
<main className="flex min-h-screen flex-col items-center p-24 gap-y-8">
<h2 className="text-5xl font-thin">bgd10y-workshop</h2>
<SystemVersion />
</main>
…
This is what you should see:

Set up monitoring
Monitoring refers to a feature in which the client requests from Motion Master to send parameter values at regular intervals. Once it starts sending the data, the client can subscribe to the data emissions and later unsubscribe when they no longer need that data, effectively requesting Motion Master to stop sending it.
You will now create the Monitoring component that will begin monitoring the target and actual positions, velocity, and torque parameters. You will display the data in a table.
Create the app/monitoring.tsx
file and set its content to:
"use client";
import { useEffect, useState } from "react";
import { DeviceParameterIds, ParameterValueType } from "motion-master-client";
import { client } from "./client";
const ids: DeviceParameterIds = [
[0, 0x607a, 0],
[0, 0x6064, 0],
[0, 0x606c, 0],
[0, 0x6077, 0],
];
export default function Monitoring() {
const [values, setValues] = useState<ParameterValueType[]>(Array(ids.length).fill(0));
useEffect(() => {
const subscription = client.startMonitoring(ids, 500000).subscribe(setValues);
return () => {
subscription.unsubscribe();
};
}, []);
const rows = values.map((value, index) => <td key={index}>{value}</td>);
return (
<table className="table">
<thead>
<tr>
<th>0x607A: Target Position</th>
<th>0x6064: Actual Position</th>
<th>0x607A: Actual Velocity</th>
<th>0x6077: Actual Torque</th>
</tr>
</thead>
<tbody>
<tr>{rows}</tr>
</tbody>
</table>
);
}
Code explanation
With useEffect
, the code inside the block runs only once, the first time
this component renders. In it, we start monitoring a list of tuples where each tuple consists of the
device position, parameter index, and subindex. When parameter values are emitted, we change the
state of the component using setValues
, which triggers a re-render of the table. Since
the monitoring interval is set to half a second, the table will be re-rendered every half a second.
Import and display the Monitoring component in app/page.tsx
below the
SystemVersion
component:
…
import SystemVersion from "./system-version";
import Monitoring from "./monitoring";
…
<SystemVersion />
<Monitoring />
</main>
…
This is what you should see:

Change parameter values
You are now going to create a reusable component that will allow you to change the value of any device parameter. Once you have this component, you will create four instances of it in order to be able to change the values of the position profile-related parameters.
Create the app/parameter.tsx
file and set its content to:
"use client";
import { useEffect, useState } from "react";
import { makeParameterId } from "motion-master-client";
import { client } from "./client";
interface ParameterProps {
name: string;
index: number;
subindex: number;
}
export default function Parameter({ name, index, subindex }: ParameterProps) {
const [value, setValue] = useState(0);
const id = makeParameterId(index, subindex);
useEffect(() => {
client.request.upload(0, index, subindex).then(setValue);
}, []);
const text = (
<span>
<strong>{makeParameterId(index, subindex)}</strong>: {name}
</span>
);
function handleInputChange(e: React.FormEvent<HTMLInputElement>) {
const newValue = parseInt(e.currentTarget.value, 10);
if (!isNaN(newValue)) {
setValue(newValue);
}
}
async function handleOnSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
await client.request.download(0, index, subindex, value);
console.info(`${id} set to ${value}`);
}
return (
<form onSubmit={handleOnSubmit}>
<div className="flex gap-x-2 items-end">
<label className="flex flex-col">
{text}
<input className="control" type="number" defaultValue={value} onChange={handleInputChange} />
</label>
<button type="submit" className="btn">
SET
</button>
</div>
</form>
);
}
Code explanation
The Parameter component receives three properties: name, index, and subindex. This allows it to be reusable for any parameter. Initially, it fetches the current parameter value and displays it. When the form is submitted, it takes the value from the input and sends a request to Motion Master to set the new parameter value.
Import and create four instances of the Parameter component in app/page.tsx
below the
Monitoring
component:
…
import Monitoring from "./monitoring";
import Parameter from "./parameter";
…
<Monitoring />
<div className="flex gap-x-8 items-end">
<Parameter name="Profile velocity" index={0x6081} subindex={0} />
<Parameter name="Profile acceleration" index={0x6083} subindex={0} />
<Parameter name="Profile deceleration" index={0x6084} subindex={0} />
<Parameter name="Target position" index={0x607a} subindex={0} />
</div>
…
This is what you should see:

One way to test if changing the parameter values works is to modify the "0x607A:00: Target position" and observe if the change is reflected in the monitoring table above.
Control operation
Now, you will proceed to create several buttons that will enable you to control the operation and CiA402 state machine. These buttons will allow you to perform actions such as enabling operation, quick stopping the device, resetting faults, applying set points, and halting the position profile motion.
Create the app/quick-stop.tsx
file and set its content to:
"use client";
import { client } from "./client";
export default function QuickStop() {
return (
<button className="btn" type="button" onClick={() => client.request.quickStop(0)}>
QUICK STOP
</button>
);
}
Create the app/reset-fault.tsx
file and set its content to:
"use client";
import { client } from "./client";
export default function ResetFault() {
return (
<button className="btn" type="button" onClick={() => client.request.resetFault(0)}>
RESET FAULT
</button>
);
}
Create the app/enable-operation.tsx
file and set its content to:
"use client";
import { Cia402State } from "motion-master-client";
import { client } from "./client";
export default function EnableOperation() {
function handleClick() {
client.request.transitionToCia402State(0, Cia402State.OPERATION_ENABLED);
}
return (
<button className="btn" type="button" onClick={handleClick}>
ENABLE OPERATION
</button>
);
}
Create the app/apply-set-point.tsx
file and set its content to:
"use client";
import { client } from "./client";
export default function ApplySetPoint() {
return (
<button className="btn" type="button" onClick={() => client.request.applySetPoint(0)}>
APPLY SET POINT
</button>
);
}
Create the app/halt.tsx
file and set its content to:
"use client";
import { client } from "./client";
export default function Halt() {
return (
<div className="flex items-center gap-x-2 ml-12">
<span>HALT</span>
<button className="btn" type="button" onClick={() => client.request.setHaltBit(0, true)}>
ON
</button>
<button className="btn" type="button" onClick={() => client.request.setHaltBit(0, false)}>
OFF
</button>
</div>
);
}
Now, import and display all the buttons you have created in the app/page.tsx
file, below
the parameters:
…
import QuickStop from "./quick-stop";
import ResetFault from "./reset-fault";
import EnableOperation from "./enable-operation";
import ApplySetPoint from "./apply-set-point";
import Halt from "./halt";
…
<div className="flex gap-x-2">
<QuickStop />
<ResetFault />
<EnableOperation />
<ApplySetPoint />
<Halt />
</div>
…
This is what you should see:

Run the position profile
You now have all the necessary components to execute the position profile. Set the profile's velocity, acceleration, deceleration, and target position. Then, enable the operation and apply the set point. You can set another target position and apply the set point again. At any point during the motion, you can halt it. Take the time to interact with the system and ensure that it functions as expected.
Say it
This is the final component you will create. Begin by downloading the provided file and
placing it in the app
directory. Next, create a file called app/say.tsx
and
set its content as follows:
"use client";
import { say } from "./mem";
interface SayProps {
message: string;
}
export default function Say({ message }: SayProps) {
return (
<button className="btn" type="button" onClick={() => say(message)}>
SAY {message.toUpperCase()}
</button>
);
}
Import and display both the Say component that you have created and the Mem component from the
previously downloaded file. In the app/page.tsx
file, place them below the operation
buttons.
…
import Say from "./say";
import Mem from "./mem";
…
<Say message="Synapticon" />
<Mem />
…
Click on the Say button and observe what it does!
Write your own message
Now that you have the full user interface at your disposal, with which you can operate the drive in the position profile mode, your task is to write a single word, a person's name, or a thing that makes your life joyful.
If you're up for a challenge, can you figure out how the message is encoded? What cipher are we using?
Finito
You've made it this far, and that's awesome! Thank you for having participated in this workshop, you nice human being 🤗, not a robot 🤖.