A while ago I had an idea about a Node.js IoT project: Creating a discovery rover to experiment a little bit with LEGO & Bluetooth. So I modded a Technic vehicle, attached a camera and started playing with the communication.
The result is a solution to control the vehicle from the browser and also my first npm package!
In this article, I’ll guide you through the whole process so that you can build yours too!
What do you need for this Node.js IoT project?
1. A LEGO Technic vehicle with batteries and motors
You can be creative on this one, I have a Volvo L350F (42030), but now I think the best deal is the Tracked Racer (42065). But if you decide to build one, remember that you need a battery box and at least two motors for moving and turning.
This clever thing replaces the LEGO infrared remote control system with Bluetooth Low Energy protocol, so the range is increased and works behind walls too. It’s compatible with all LEGO Technic motors and battery boxes.
3. Raspberry Pi
This will act as the server and control the vehicle. I suggest using a Pi3, because in my experience the Pi2 was unusably slow, and setting up a Bluetooth USB stick was a bit pain in the ass.
4. Phone/action camera with WiFi
You need to see where that thing goes! I’ve used an old Android phone for this purpose. Unfortunately, I could not find a device with Bluetooth video streaming support, the Bluetooth bandwidth may be too low for this, so I think WiFi is your only option.
5. Lots of batteries
You’ll need them for sure :)
6. Bluetooth LE support on your computer
It’s faster to test during development. My ancient ThinkPad had only Bluetooth 2.0, so I bought a LogiLink BT0037 - it works well with the SBrick.
Let’s build something!
You can build anything for the first experiments, just use the SBrick, set up the default application on your phone and play for a few minutes.
You’ll learn which channel controls which motor, whether you have to invert any of the directions, and just get the feeling of the whole thing.
Understanding Bluetooth LE & SBrick functions
Bluetooth LE is available since Bluetooth 4.0, and it uses much less power compared to traditional Bluetooth. In a nutshell, it has a client-server architecture, the BLE device (server) broadcasts advertisement data until a client (notebook, raspberry pi, phone, etc.) connects.
From that point, the client chooses a service and a characteristic (imagine this like an “API endpoint”), where it reads and writes data. The server could also send notifications to the client, like sensor readings.
If you want to dig deeper into this topic, I recommend this article as a start.
In case of SBrick, the protocol is open and available on the manufacturer’s website.
The device has a few services:
- Generic GAP
- Device Information
- OTA services for firmware upgrade
- Remote control service
We’re looking for the remote control service, which has two characteristics:
- Quick Drive: allows remote control with small data packets. Very limited functionality.
- Remote control commands: allows full control, more verbose and slower than quick drive.
For this project I’ve used the full package, Quick Drive is a bit more challenging.
After studying the protocol, you can see that there are more than 40 commands, a few for controlling the drive channels, some others for setting up time limits, device name, reading battery voltage, unit temperature, etc.
The SBrick has security features too, but they are unusable from the mobile application. Fortunately, you can play with them if you want. There are two users, owner and guest, which both can have passwords. In case you don't set a password, everyone connecting to the brick is an owner.
Some commands can only be used by the owner, and you can only set a guest password if you set an owner password before. If you try to execute a command which you are not authorized to, the SBrick disconnects from the Bluetooth client.
A bit about Bluetooth in Node.js
In the node world, the de-facto package for BLE (Bluetooth Low Energy) handling is noble.
This package can scan for peripherals, discover services and characteristics and handle notifications. Writing to a characteristic is pretty straightforward, just use the
write() function, but reading is a bit unusual: you have to call
write() with your “read command” asynchronously, and after succeeding, call
read(). Finally, in the callback function you can access the result.
To install this package, you need to set up Bluetooth correctly. I don’t want to dive into these problems in this article; I’m just suggesting to follow these links if you are stuck:
After setting this up and installing noble, just run this to see your SBrick recognized:
If it works, you’re over the most difficult part, congratulations!
Based on the SBrick protocol description, I’ve implemented many functions and published it as my first npm package, sbrick-protocol
Let's go through the main features:
1. Recognizing whether a discovered Bluetooth device is an SBrick
This is done in
SBrickAdvertisementData.js: parses Bluetooth advertisement data and returns an object with UUID, software and hardware versions and security status, if it’s an SBrick - error otherwise.
2. Handle connection, disconnection, authentication
The advertisement data contains the information, if the device is password-protected, or not. If it is, tries to log in with the specified password. It’s a bit guess-game on my behalf because the protocol description does not explain how to encrypt passwords to fill the 8-byte space available.
SBrickPasswordGeneratorMD5.js file, I’m using the first half of an MD5 hash, but you can implement your own too.
3. Creating a promise-based interface to SBrick functions
Currently only the “important” ones are covered, which were necessary to drive, or were easy to implement & test :)
4. Running a loop for drive commands
It’s a bit like a game loop, necessary because if we don’t issue any command for a while after connecting, the SBrick’s watchdog timeout disconnects the device. The idea is to set up a command queue (with promise-queue), and periodically add the drive commands of the four channels, if it’s empty (usually it is).
That keeps the show rolling with the ability to issue a command without waiting too much for the execution. The current interval (200 msec) is a result of my experience, it may be too fast for your setup so feel free to adjust.
5. Query ADC (analogue-to-digital) data
There are voltage and temperature sensors in the device, and if we want to display the current values, we also need to read these. In previous protocol versions, this was done automatically with Bluetooth notifications, but unfortunately since protocol version 17, we have to read them manually. I hope they’ll fix this soon.
So, if you’re ready to test some things, try the example code here.
The protocol implementation works, but not too usable in a standalone form, so I’ve created a fully-featured client too, the
sbrick controller. You can clone it from here.
The heart of this solution is an express server, using the sbrick-protocol for SBrick communications and socket.io for real-time controls from the browser.
The client is password protected, the default is admin/adminPass - but you can change this with environment variables.
After connecting to your SBrick, you can set up your keyboard shortcuts for every channel, and the configuration will be saved on the server. There is also a custom winston log handler, which emits the log from the server to the browser console via websockets. The red graph is the unit temperature in Celsius, the green represents battery voltage.
It’s time to attach a camera to your vehicle! I’ve used an old Android phone with an app called IP Webcam, works pretty well for this purpose.
Be careful, you will not see the vehicle from the outside, so it’s easy to bump into the wall or furniture, protect your camera, and attach it firmly!
After experimenting a bit, I’d suggest you to add a few more motors to turn and tilt the camera, it’s extremely helpful.
If you enter the address of the live feed (MJPG stream with IP Webcam) into the stream URL input box, it’ll show the output of your camera. You can enter a local address too; the server will act as a proxy to access the feed from the internet.
You are ready to do some serious play now, test how far it can go, whether you have to adjust the main loop interval, how stable is the live feed. If you’re brave enough, start the server on your Raspberry with a node process manager like pm2, open up its port on your router, and log in form your workplace.
But be careful, this software is just a proof-of-concept, does not have much security. I also cannot guarantee that you won’t run over your cat, you have been warned!
Have fun connecting the physical world to the virtual, and don’t forget to submit a PR if you made some improvements to any of these components :)