Skip to content

Commit

Permalink
Merge pull request #45 from CCGSRobotics/remote-dynamixel-wizard
Browse files Browse the repository at this point in the history
Implement a remote Dynamixel control table interface
  • Loading branch information
Finchiedev authored Aug 26, 2019
2 parents 258b3cb + 94900be commit 4fb6560
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 41 deletions.
13 changes: 13 additions & 0 deletions App/CSS/wizard.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}

td, th, table {
border: 1px solid black;
}

table {
border-collapse: collapse;
}
121 changes: 82 additions & 39 deletions App/JS/main.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,82 @@
var net = require('net');
var dgram = require('dgram');
var client = new net.Socket();
var childProcess = require('child_process');
var clientSocket = new dgram.createSocket('udp4');
const settings = require('./JS/Resources/Settings/flipperBot.json')
const jointSpeed = settings["Client"]["Controller"]["Joints"].JointSpeed
const panDirectionDefault = settings["Client"]["Controller"]["Joints"].PanDirection
const tiltDirectionDefault = settings["Client"]["Controller"]["Joints"].TiltDirection
const leftCameraButton = settings["Client"]["Controller"]["Buttons"].CameraPanLeft
const rightCameraButton = settings["Client"]["Controller"]["Buttons"].CameraPanRight
const upCameraButton = settings["Client"]["Controller"]["Buttons"].CameraTiltUp
const downCameraButton = settings["Client"]["Controller"]["Buttons"].CameraTiltDown
var panDirection = panDirectionDefault
var tiltDirection = tiltDirectionDefault
var video;
var lastVals = [];
var multipliers = [[false, 1], [false, 1]]
var flipperSelect = true // true: front flippers | false: back flippers
let gamepad;

// Flipper limits are set inside the client, to avoid fake [Overload] Errors occuring on the server side.
var flipperJointLimits = {
5: [1323,3372], // ID: 5 | Model: MX-28
6: [1323,3372], // ID: 6 | Model: MX-28
7: [1023,3072], // ID: 7 | Model: MX-28
8: [1023,3072], // ID: 8 | Model: MX-28
9: [200,2560], // ID: 9 | Model: MX-28
10: [512, 1024], // ID: 10 | Model: AX-12
12: [0, 1023], // ID: 12 | Model: XL-320
13: [205, 816] // ID: 13 | Model: XL-320
// [0,1023], // ID: 10 | Model: AX-12
}
var b_button_state = false;
var grabberMultipler = 0;
var grabberStep = 1;
var grabberValue = 0;
var canMove = true;
var cameraStep = 2
const dgram = require('dgram');
const socket = new dgram.createSocket('udp4');
const fs = require('fs');

// Note: this should be made part of the settings later
const defaultPort = 5001;
const defaultIP = '127.0.0.1';

socket.on('error', function(err) {
console.error('UDP client error:\n' + err);
});

/**
* Sends information over UDP to the specified IP and port
* @param {String} data The data to send over UDP
* @param {*} port The port number of the server
* @param {*} ip The IP address of the server
*/
function sendData(data, port = defaultPort, ip = defaultIP) {
if (data !== null && data !== undefined) {
if (typeof(data) == 'string' && typeof(port) == 'number' &&
typeof(ip) == 'string') {
socket.send(data, port, ip);
} else {
console.error('Invalid argument types!');
}
} else {
console.error('Data to send is invalid!');
}
}

/**
* A class representing a single Dynamixel servo
*/
class Dynamixel {
/**
*
* @param {String} model The model name of the Dynamixel
* @param {Number} id The ID of the Dynamixel
* @param {Number} protocol The protocol used, either 1 or 2
*/
constructor(model, id, protocol) {
this.model = model;
this.id = id;
this.protocol = protocol;

const path = `./App/JS/Resources/Servos/${model}.csv`;
fs.readFile(path, (err, data) => {
if (err) {
if (err.code === 'ENOENT') {
console.error(`The file at path ${path} does not exist!`);
return;
}

throw err;
}

this.controlTable = {};

const fileData = data.toString().split('\n').filter(function(element) {
return element !== '';
});
this.controlTableFile = fileData;
const headings = fileData[0].split(', ');

for (let line = 1; line < fileData.length; line++) {
const columns = fileData[line].split(', ');
const index = columns[2];
this.controlTable[index] = {};

for (let col = 0; col < columns.length; col++) {
if (col !== 2) {
this.controlTable[index][headings[col]] = columns[col];
}
}
}
});
}
}

module.exports.sendData = sendData;
module.exports.Dynamixel = Dynamixel;
139 changes: 139 additions & 0 deletions App/JS/wizard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
const {sendData, Dynamixel} = require('./JS/main.js');
const dgram = require('dgram');
const server = dgram.createSocket('udp4');

server.on('error', (err) => {
console.log(`Server error:\n${err.stack}`);
server.close();
});

server.on('listening', () => {
const address = server.address();
console.log(`server listening ${address.address}:${address.port}`);
});

/**
* Creates a number input field for a given row if the address is writable
* @param {String} name The name of the row in the control table
* @param {Number} id The ID of the servo
* @param {Object} data The control table row for the data being modified
* @return {Node} The corresponding input element
*/
function createModifier(name, id, data) {
if (data['Access'].includes('W')) {
const input = document.createElement('input');

input.type = 'number';
input.value = data['InitialValue'];
input.addEventListener('change', function(event) {
sendData(`(modify)-${id}-${name}-${event.target.value}`);
});

if (data.hasOwnProperty('Min')) {
input.min = data['Min'];
}
if (data.hasOwnProperty('Max')) {
input.max = data['Max'];
}

return input;
}

return null;
}

/**
* Creates a row populated with the given columns
* @param {Array} items The items in the row
* @param {String} colTag The tag type of each column's element
* @return {Node} The corresponding row
*/
function createRow(items, colTag) {
const parent = document.createElement('tr');

let item;
for (let col = 0; col < items.length; col++) {
item = document.createElement(colTag);
item.innerHTML = items[col];
parent.appendChild(item);
}

return parent;
}

/**
* Creates an HTML table for the control table of a given servo
* @param {Dynamixel} servo The {@link Dynamixel} servo to be displayed
*/
function createTable(servo) {
const csv = servo.controlTableFile;

const table = document.getElementById('control_table');
const headings = csv[0].split(', ');
headings.push('Current Value');
headings.push('Modify');

table.appendChild(createRow(headings, 'th'));

for (let line = 1; line < csv.length; line++) {
const row = createRow(csv[line].split(', '), 'td');

const name = csv[line].split(', ')[2];
const data = servo.controlTable[name];

const value = document.createElement('td');
value.innerHTML = data['InitialValue'];
value.id = `${name} Value`;
row.appendChild(value);

const modifier = createModifier(name, servo.id, data);
if (modifier !== null) {
row.appendChild(modifier);
}

table.appendChild(row);
}
}

/**
* Initialises a given servo and displays its control table
* @param {Dynamixel} servo The {@link Dynamixel} servo to read/write from
*/
function initialiseTable(servo) {
sendData(`(init)-${servo.model}-${servo.id}-${servo.protocol}`);
createTable(servo);

server.on('message', (msg, rinfo) => {
const data = msg.toString().split(':');
const name = data[0];
const value = data[1];

// console.log(name, value)
const element = document.getElementById(`${name} Value`);
if (element == null || element == undefined) {
console.log(name);
}
element.innerHTML = value;
});

server.bind(5003);

setInterval(function() {
sendData(`(read)-${servo.id}-Present Position`);
}, 100);
}

/**
* Creates and initialises a {@link Dynamixel} servo using input from HTML
*/
function openTable() {
const model = document.getElementById('model').value;
const id = document.getElementById('id').value;
const protocol = document.getElementById('protocol').value;

const dyn = new Dynamixel(model, id, protocol);
setTimeout(function() {
initialiseTable(dyn);
document.getElementById('select').style.display = 'none';
}, 50);
}
18 changes: 18 additions & 0 deletions App/wizard.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Remote Dynamixel Wizard</title>
<script src="JS/wizard.js"></script>
<link rel="stylesheet" href="CSS/wizard.css">
</head>
<body>
<div id="select">
<input type="text" id="model" placeholder="Model">
<input type="text" id="id" placeholder="ID">
<input type="text" id="protocol" placeholder="Protocol #">
<button onclick="openTable()">Go!</button>
</div>
<table id="control_table"></table>
</body>
</html>
2 changes: 1 addition & 1 deletion Server/Driving/dynamixels.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def read_value(self, name):
return str(PROTOCOL_ONE.read1ByteTxRx(PORT_HANDLER, self.dynamixel_id,\
self.query(name, 'Address'))[0])
return str(PROTOCOL_TWO.read1ByteTxRx(PORT_HANDLER, self.dynamixel_id,\
self.query(name, 'Address')))
self.query(name, 'Address'))[0])
except TERMIOS_ERROR as err:
print('An error occured when reading from Dynamixel {}'\
.format(self.dynamixel_id))
Expand Down
2 changes: 1 addition & 1 deletion main.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function createWindow() {
});
// win.loadFile('App/loading.html');
// setTimeout(function() {
win.loadFile('App/createServo.html');
win.loadFile('App/wizard.html');
// }, 3000)
// win.loadURL('http://localhost:8080')
win.webContents.openDevTools();
Expand Down

0 comments on commit 4fb6560

Please sign in to comment.