Approximately 8.9 million US and 1.8 million Canadian households own an RV. These are vehicles that provide many of the comforts of home, and correspondingly, RV owners expect the same conveniences.
However, most “smart home” products just don’t fit into this environment. In some cases that’s literally true: a Google Home is bulky by RV standards, and is built to stand on a stationary shelf or table (which RVs don’t have). In other cases, it’s a more specific incompatibility, as most smart home devices assume:
- constant AC power
- always-on Wifi
- unlimited, fast Internet
...none of which can be taken for granted in an RV. It’s certainly possible to design around these requirements, but currently, few smart devices do.
So, opportunities exist in adapting standard smart home use cases for RV compatibility. Additionally, RVs have some specific needs that could be well served by new, specialized smart devices, outside of existing product categories. I’m calling this potential family of devices SmartRig.
Vehicle LevelingOne such specialized use case is leveling. Many RV systems (such as refrigeration and plumbing) work better when the vehicle is level - not to mention the importance of level floors and beds for occupant comfort. So when arriving at a site, the RV operator needs to assess the lay of the land - traditionally via bubble levels attached to the vehicle - then compensate for any slope with blocks under the tires, or jacks attached to the frame.
I decided that I could do better. There’s not much opportunity to make plastic blocks smarter, but everything upstream of that point can be improved. My plan was to build an Android Things level sensor, interfacing an accelerometer mounted in the RV to an app running on my phone. This app would optimize block placement to compensate for the slope of the vehicle, and display that leveling solution on-screen.
Hardware BuildThe first thing that a level sensor needs is a good accelerometer to read the tilt of the vehicle from the apparent angle of gravity. I selected Analog Devices’ ADXL345 part, sold by SparkFun pre-mounted to a breakout board for easy assembly. The 2g range of this chip gives good resolution for a leveling application, and the I2C interface works well with Android Things.
The bare breakout board isn’t quite ready to build on, though, so the first step is to install straight-through headers for connection to a breadboard.
It’s also important that an accelerometer be securely mounted; if you just stick it into your board, it’ll wobble - and throw off your readings. To overcome this issue for my prototype, I cut a 12x15mm rectangle of 2mm-thick plastic stock as a spacer (this part would also be easy to 3D-print). I used a glue dot to secure it to the underside of the ADXL345 board, and another dot will secure it to the breadboard.
Which brings us to that breadboard. As mentioned, I’m going to use I2C to interface with this chip - and conveniently, SparkFun has a schematic just for this in their ADXL345 guide. I followed it almost exactly for my own board:
Yeah, it’s really simple - but it’s sufficient for attachment to the Android Things board that’ll tie the sensor module together. I’m using an Intel Edison that I previously had, but virtually any Things board should work fine. [FYI, this photo shows the final hardware configuration available in this project’s schematic.]
You might notice that the breadboard has also sprouted two LEDs at this stage, because… well, what’s a hardware build without some LEDs? Seriously, though: the plan is for this sensor module to be mounted somewhere out-of-the-way in the RV, with its main UI on the linked phone app, so it won’t have a display of its own as such. However, it might still be handy to have some sense of what it’s up to, and a couple of status LEDs should suffice for that.
To facilitate that mounting, the boards need an enclosure. For this prototype, I’m just using a small food-storage box; it’ll protect the components without obscuring the status LEDs, and should be easy enough to mount in the tight quarters of an RV. Any fairly sturdy (not cardboard) box about 6"x3.5"x2" should work fine, though if you want to see the LEDs, it'll need to be at least partially transparent.
And of course, both boards need to be secured within the box. The breadboard is easy - like most of its kind, it’s self-adhesive on the bottom. For the Edison board, I drilled 4 small holes in the bottom of the box, through which #4-40 machine screws fit nicely into the ends of the board’s standoff “legs”. [Use a ⅛” drill bit for this size screw.]
The module will also need power, and true to SmartRig's guiding principles, this will come directly from the RV’s automotive-standard 12VDC system. The Edison board has a power input that will accept anywhere from 4 to 15 volts DC, so I considered wiring straight into that (and in fact, I built a cable to do this during development). However, the board can also be powered through its micro-USB port, and I decided that this made more sense for the prototype: it means that I have the USB interface easily available for reprogramming later. So, I’ll be using a 12VDC USB outlet for power - in this case, one purchased off the shelf at an O’Reilly auto parts store.
The micro end of the cable enters the module box through a ½” hole aligned with the Edison board’s J16 port.
We’re now ready to install everything. A full discussion of RV electrical systems is beyond the scope of this document (though here’s one I prepared earlier), and the details will vary from rig to rig, so I’m not going to delve too deeply into sensor installation here. But to summarize:
- Disconnect all 12VDC power sources in the RV before you begin.
- Here’s what mine looks like when finished:
One note for installation: the sensor box (or more specifically, the ADXL345) must be mounted in parallel with the vehicle’s X, Y, and Z axes. It can’t be mounted at any significant angle, in any axis, or none of the software calculations below will work. I'll discuss sensor orientation more later (in the Additional Notes section), but for now, just be aware of this requirement.
Software OverviewThere are two software components to this project:
- An Android Things app, running on the module’s main board (in my case, an Intel Edison).
- A conventional Android app, running on a phone or tablet to provide the user interface.
Since an RV will often travel far beyond the reach of Internet connectivity, there’s no cloud component to this project. The two apps connect directly to each other; I chose the Bluetooth Low Energy (BLE) protocol for this connection. Note that this means that your Android phone will need to support BLE, which implies a minimum SDK level of 18 (Android 4.3).
The source code for both apps is available on Bitbucket; there's a lot more than the snippets shown here.
Sensor Module AppThe app running on the Android Things board is mostly just providing a BLE interface to the accelerometer, so it’s pretty simple.
I was fortunate to find an open-source ADXL345 driver for Android Things, which gave me a head start on that side of the code. The driver author had done most of the nitty-gritty of setting up the ADXL345 interface, so reading the data became a simple matter of pulling bytes from the chip:
public int getAccelerationX() throws IOException {
return mDevice.readRegWord(ADXL345_RA_DATAX0);
}
public int getAccelerationY() throws IOException {
return mDevice.readRegWord(ADXL345_RA_DATAY0);
}
public int getAccelerationZ() throws IOException {
return mDevice.readRegWord(ADXL345_RA_DATAZ0);
}
public float[] getAccelerations() throws IOException {
float[] values = new float[3];
values[0] = getAccelerationX();
values[1] = getAccelerationY();
values[2] = getAccelerationZ();
return values;
}
The getAccelerations() method feeds directly into a UserSensorDriver subclass, so that the values from the ADXL345 appear when the containing app gets an onSensorEvent() call. The only issue here is that the accelerometer SensorEvent.values are supposed to be in SI units (m/s^2), while this driver returns the raw, unscaled data from the chip instead. I’ll probably sort this in a later version, but for this prototype, it’s fine as long as we know that’s what’s happening.
The other side of this app is the BLE interface, and for that, I drew heavily from Google’s BLE server sample for Android Things. The BLE standard doesn’t appear to have a GATT profile for accelerometer data, so I created a custom one that simply declares a characteristic for the 3-dimensional acceleration vector:
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId,
int offset, BluetoothGattCharacteristic characteristic) {
if (CHARACTERISTIC_ACCELS_UUID.equals(characteristic.getUuid())) {
mBluetoothGattServer.sendResponse(device,
requestId,
BluetoothGatt.GATT_SUCCESS,
0,
AccelProfile.getAccels());
}
}
My custom profile also allows a client to subscribe to a continuous stream of this data, which will be handy for showing real-time vehicle tilt in the phone app. When such a stream is being sent, my code uses a standard low-pass filter to reduce noise in the accelerometer data.
The only other functionality in the Android Things app is driving the status LEDs, and again I worked directly from Google-provided sample code. You’ll recall that I built two LEDs onto the board; I light an orange one when the module is ready to receive a connection (visible in the previous photo), and a blue one when that connection has been made (and the client is being sent accelerometer data).
This is where the rubber meets the road (or actually, meets the leveling blocks) for this project: the phone app is where it all comes together, using the accelerometer data to recommend how many blocks are under needed under each wheel.
It starts with the BLE interface to the sensor, and the first step here is for the app to find the sensor. I’ve built a small BleScanner class to look for local devices advertising the accelerometer profile of our sensor module (again based on a Google sample). Most of this class is boilerplate housekeeping - checking that the phone’s BT adapter is on, BLE is supported, the app has required permissions - exciting stuff like that. The real scan happens in just one line:
mBluetoothAdapter.startLeScan(new UUID[]{ACCEL_SERVICE_UUID}, callback);
where ACCEL_SERVICE_UUID identifies the GATT profile we defined in the sensor app.
Once the sensor is found, the connection is handed off to a GATT client connection-management class inspired by a great tutorial from Gautier Mechling. I suggest you go read it if you’re not comfortable with BLE operations; it’s the component that makes the connection with our sensor module and subscribes to its accelerometer data stream.
From there, we’re mostly in the UI. The app’s MainActivity listens to that data stream and transforms the raw data into numbers the user can understand. This happens in several stages:
First, the native accelerometer data is converted into radians of tilt, by applying these equations from the ADXL345 manufacturer's Application Notes:
In our application, θ is longitudinal (front-rear) tilt, and ψ is lateral (left-right) tilt.
The next step is to convert those radian values into how many inches low or high the various vehicle wheels are, using basic trigonometry:
double inchesLon = Math.tan(theta) * (wheelbase * 12);
double inchesLat = Math.tan(psi) * 90;
where wheelbase is a user-configured value for the distance between the vehicle’s axles, and 90 is simply a typical average RV track (width between wheels). Right now, the app is hardcoded for Imperial measurements (feet and inches), but adding metric wouldn’t be hard.
At the end of this, what we have is the same sort of number that common RV bubble levels are marked in: how many inches low or high the wheels are in each axis (front/rear or left/right):
These are the numbers that an RV owner would use to decide how many blocks to put under each wheel for leveling. For example, if the RV is one inch low in the front and two inches low on the right:
- put 1 block under the left front tire
- put 2 blocks under the right rear tires
- put 3 blocks under the right front tire (1 for being in front + 2 for being on the right)
My app does this approach one better: some of these blocks effectively cancel each other out, and can be eliminated. In the example above, we don’t really want blocks under the left-front or the right-rear, because both the left and rear are too high already; what we actually want to do is just raise the right-front. So it’s fairly straightforward to apply that sort of logic to display an “optimized” leveling solution (that uses fewer blocks) as a final result:
The user interface is basic Android: SeekBars to display the levels, TextViews for the blocks under each wheel, and so on. There’s also quite a bit more going on in this app - controls for the sensor interface, use of the phone's camera flash as a torch (for ease of placing leveling blocks at night), a day/night UI toggle, a PreferenceScreen for configuration of various options, and a few other features - but all these aspects are peripheral to the main function of leveling.
Additional NotesIn applying the accelerometer sensor to a real vehicle, there are a couple of other considerations you should be aware of.
The first is sensor module orientation: how you mount the sensor box will obviously affect which accelerometer axis corresponds to which vehicle axis. By default, my code is set up for the orientation in which I’ve installed my own sensor: ADXL345 board right-side up, with its header pins on the side facing the rear of the vehicle:
public void onAccelRead(final int[] values) {
final float rawX = -values[1];
final float rawY = values[0];
final float rawZ = values[2];
...
}
If you install your sensor in a different orientation, you’ll need to adjust these assignments accordingly; you’ll find them toward the end of MainActivity.java. Tweaking the code like this is fine for purposes of a prototype, but if this device were to be commercially produced, a procedure would need to be developed for non-technical users to align this orientation themselves.
The second issue is calibration. After installation, it’s unlikely that your ADXL345 will be exactly aligned with your vehicle’s frame; most likely, some physical error will have crept in along the way. So, I’ve included a calibration function in the app: with the vehicle level, select Calibrate from the overflow menu, and it’ll save the current raw X, Y, and Z numbers as the sensor’s “zero point” for each axis. Thereafter, the app will subtract these numbers from the raw data before performing its leveling calculations, ensuring that you really are working toward true level.
The FutureThis prototype is in currently in use in my own RV and working well. It’s ready to scale up to a commercial product, for use in any RV of a comparable configuration.
One obvious extension to this project would be to bypass plastic blocks and have the app control vehicle leveling jacks directly. It's not a direction that I've had opportunity to explore as yet, but it clearly has potential.
And of course, leveling is only the first module in a potential SmartRig product line. Other possibilities for taking the smart home revolution on the road include:
- RV-specific thermostats
- 12-volt smart light bulbs
- Amp-hour metering
- Water use metering
- Refrigerator control
- and more!
All of these modules have potential both as retail products and in OEM sales, the latter to RV manufacturers for integration into their vehicles as built-in equipment.
Comments