BluTerm is an terminal interface application for RS232 & RS485 serial interfaces. BluTerm application is versatile in the sense that it can function with the BluTerm bluetooth adapter as well as the RS232 & RS485 ports on the gateway.
Prerequisites- Familiarity with Javascript, HTML, CSS
- Gateway or BluTerm adapter
Let's take a look at the terminal.js file which acts as the driver for the device. We define a variable Terminal which is set the the return of the self induced function. In the function, we define the actual terminal object. You will see that all our drivers follow a similar format when it comes to defining device attributes and functions to interface with.
var TERM_SERVICE_UUID = "50270001-df25-45b0-8ad9-b27ceba6622f";
var WRITE_CHAR_UUID = "50270002-df25-45b0-8ad9-b27ceba6622f";
var NOTIFY_CHAR_UUID = "50270003-df25-45b0-8ad9-b27ceba6622f";
var CONFIG_CHAR_UUID = "50270004-df25-45b0-8ad9-b27ceba6622f";
var GATT_INFO_SERVICE = 0x1800;
var DEVICE_NAME_CHAR = 0x2a00;
var INFO_SERVICE = 0x180a;
var HW_CHAR = 0x2a27;
var SW_CHAR = 0x2a28;
We first define UUIDs for our devices services and characteristics. We interact with 3 services in our application:
TERM_SERVICE_UUID is set to the UUID of what we call the terminal service. It is the parent of two characteristics, one for input text and one for output text. Because of BLE restrictions, the maximum number of bytes received or sent is 20 at a time.
GATT_INFO_SERVICE is set to the UUID to the Generic Access Service. This is a specification defined service in BLE that contains the device name. Our device allows the name to be set. We will need the UUID to take advantage of that feature.
INFO_SERVICE is set to the UUID of the Device Information Service which is also defined by the bluetooth specification. Here, our device stores version numbers of the hardware and firmware.
These UUIDs allow us to identify where data is coming from as well as have us right data using the devices Gatt Server.
The file has the following functions defined:
function Terminal(bluetooth)
Terminal.prototype.connect = function connect()
Terminal.prototype.disconnect = function disconnect()
Terminal.prototype.readDeviceName = function readDeviceName(char)
Terminal.prototype.setDeviceName = function setDeviceName(sendData)
Terminal.prototype.writeData = function writeData(sendData)
Terminal.prototype.writeConfigData = function writeConfigData(cfgData)
We will go over these functions individually. We can see, the functions show what we can do with the device.
The most complex looking function is the connect function. In reality, it's really just a call to connect to the device followed by a series of callbacks used to gather all the necessary information post-connection.
var self = this;
var options = {filters: [{services: [TERM_SERVICE_UUID]}]};
return navigator.bluetooth.requestDevice(options)
First we define the filters to find the device and use the requestdevice call to start scanning. We set the filter to look for devices with the terminal service UUID. We also create a variable self to hold the reference of the current object as to not lose that reference in a callback function.
.then(function (device) {
self.bluetoothDevice = device;
return device.gatt.connect();
})
Once we get a result, we get the device object from the requestDevice call. We connect to the device that receive.
.then(function (server) {
return Promise.all([
.
.
.
])
})
For those who don't know, the Promise.all call takes input an array of functions. All this functions are called upon success of the connect call. In our case, we have a function call for every service. Since reading services and characteristics are asynchronous calls, we define the function to be called once we have successfully received what we are asking for.
server.getPrimaryService(GATT_INFO_SERVICE)
.then(function (service) {
return service.getCharacteristic(DEVICE_NAME_CHAR)
.then(function (characteristic) {
self.deviceNameCharacteristic = characteristic;
characteristic.readValue()
.then(function (data) {
var value = '';
for (var i = 0; i < data.byteLength; i++) {
value = value + String.fromCharCode(data.getUint8(i)); }
value = value.trim();
self.deviceName = value;
});
});
}, function (error) {
console.warn('GATT Info Service not found');
Promise.resolve(true);
})
We start by getting a service for the UUID given. In this case, it's the generic access service. On retrieval of the service, we then try to get the characteristic that contains the device name data. Once we receive the device name characteristic, we ask to read it. Finally, once the data is received, we then massage the data so that it's usable in our application and assign it to our parent object for future use. We finish off with the definition of a function for error that we pass as a parameter to the then( ) call along with the success function.
All our promises in the array will have the same service - characteristic - value structure. The only thing that may change is the number of characteristics we try to read (dependent on the service) and how we handle the data we receive.
server.getPrimaryService(INFO_SERVICE)
.then(function (service) {
service.getCharacteristic(HW_CHAR)
.then(function (characteristic) {
self.hardwareCharacteristic = characteristic;
characteristic.readValue()
.then(function (data) {
var value = '';
for (var i = 0; i < data.byteLength; i++) {
value = value + String.fromCharCode(data.getUint8(i));
}
value = value.trim();
self.hardwareVersion = value;
});
});
service.getCharacteristic(SW_CHAR)
.then(function (characteristic) {
self.softwareCharacteristic = characteristic;
characteristic.readValue()
.then(function (data) {
var value = '';
for (var i = 0; i < data.byteLength; i++) {
value = value + String.fromCharCode(data.getUint8(i));
}
value = value.trim();
self.softwareVersion = value;
});
});
}, function (error) {
console.warn('Info Service not found');
Promise.resolve(true);
})
This time, we try to get the device information service. Since we are interested in two characteristics, we try to receive and read from the characteristic that has the firmware version and the hardware version. Upon receiving of the data, we set it to variables in our parent object for the application to use.
server.getPrimaryService(TERM_SERVICE_UUID)
.then(function (service) {
self.connected = true;
service.getCharacteristic(CONFIG_CHAR_UUID)
.then(function (characteristic) {
self.configCharacteristic = characteristic;
return self.configCharacteristic.readValue()
.then(function (value) {
self.configBuffer = value;
});
}),
service.getCharacteristic(WRITE_CHAR_UUID)
.then(function (characteristic) {
self.writeCharacteristic = characteristic;
}),
service.getCharacteristic(NOTIFY_CHAR_UUID)
.then(function (characteristic) {
return characteristic.startNotifications()
.then(function () {
characteristic.addEventListener('characteristicvaluechanged',
function (event) {
//todo: generate event
if (self.updateUI) {
self.updateUI(event.target.value);
}
}
);
});
})
}, function (error) {
console.warn('TERM_SERVICE_UUID Service not found');
Promise.resolve(true);
})
The last service, we try to get the our custom terminal service. Here we have three characteristics that we try to receive and read from. We have a characteristic to configure the device, one to write data, and one to read data. Unlike the other two services we have a characteristic that request to get notifications. What that means is, our function in characteristic.addEventListener will get called any time the device sends data to us. It may be dependent on input depending on the protocol that's being used.
.then(function () {
self.connected = true;
});
Once that is all done, we set our connected flag to true. This can let the application know we are ready.
The remaining functions are fairly simple. All but disconnect being I/O functions with the device.
Terminal.prototype.disconnect = function disconnect() {
var self = this;
if (!self.bluetoothDevice) {
return Promise.reject();
}
return self.bluetoothDevice.disconnect()
.then(function () {
self.connected = false;
self.writeCharacteristic = undefined;
self.configCharacteristic = undefined;
self.configBuffer = undefined;
return Promise.resolve();
});
}
The disconnect function, I assume is very straight forward. If we are not connected, we reject the call. If we are, we call disconnect on that device. The fulfillment of that call clears up any data detrimental to the app to avoid any errors and getting the app properly into a disconnected state.
Terminal.prototype.readDeviceName = function readDeviceName(char) {
return char.readValue()
.then(function (data) {
var value = '';
for (var i = 0; i < data.byteLength; i++) {
value = value + String.fromCharCode(data.getUint8(i));
}
value = value.trim();
Terminal.deviceName = value;
return Promise.resolve();
});
}
This simply tries to read the device name for the device name characteristic in the generic access service. We call the read value similarly to how we do in our connection sequence. This is not much different from the code above.
Terminal.prototype.setDeviceName = function setDeviceName(sendData) {
if (this.deviceNameCharacteristic) {
return this.deviceNameCharacteristic.writeValue(sendData);
}
return Promise.resolve();
}
We can set the device name for this device. The function above does that by calling writeValue on the device name characteristic. The data being passed is a _______.
Terminal.prototype.writeConfigData = function writeConfigData(cfgData) {
var self = this;
if (this.configCharacteristic) {
return this.configCharacteristic.writeValue(cfgData)
.then(function () {
return self.configCharacteristic.readValue()
.then(function (value) {
self.configBuffer = value;
return Promise.resolve();
});
});
}
}
The BluTerm has a configuration characteristic to set up it's interface. Being a UART interface, things like baud rate and stop bit are set with this call. We write the new config string into the config characteristic.
Terminal.prototype.writeData = function writeData(sendData) {
if (this.writeCharacteristic) {
return this.writeCharacteristic.writeValue(sendData);
}
return Promise.reject();
}
Lastly, the writeData function writes a string of data into the characteristic using the write value call.
Comments
Please log in or sign up to comment.