Smart RV Project

Hi dirk.

Good to know people are still using the python lib and that it still works. I’ve also fixed the hostname.

There’ll probably no further development from my side on the python lib. I started implementing a server part but run out of time.

In the past we were using the nRF too and I build a tiny usb interface which already includes the network part. On USB side you simply got a serial device and you’re able to direct send and receive stuff.
http://ed-solutions.de/dokuwiki/hardware:nrf:usb:at90usb

However nowadays we decided to play a bit with Bluetooth BLE, which should be usable via the python gattlib.

Have fun and success with your project :slight_smile:

Thanks for the well wishes and information @erazor. It’s cool that you’re playing with BLE. I may end up using some of that. However most BLE devices are rather expensive so I have my doubts that they will be a developers go to solution for a new network. That said, there are a lot of devises out there with it and some that do cool things and have no other wireless protocol. I’d like to talk over the CAN bus to my RV system with a BLE device.

Anyhow, I’ll check out the usb library for nRF and see if I can use it. Do you know of any hardware that you can buy to directly link the USB to the nRF? It would save time if I can buy it rather than make it.

BLE / CAN shouldn’t be a problem. There are some cheap serial BLE modules on eBay which can be used to transport CAN.

Concerning the nRF, the easiest thing would be some kind of SPI/USB converter and a library written in python or whatever you find on the net. Our stuff was very specific and is badly documented.

Also take care about the voltage levels on the nRF. Using 5V on the inputs is generally a bad idea - sometimes it works but it really results in bad wireless communication. We also noticed a bad and unsteady spectrum with the modules with printed PCB antennas.

With the raspberry you already have SPI with 3.3V levels so that’d be the easiest way.

very interesting project and you are documenting it nicely, congrats!
(and thank you for an interesting read)
Gustavo.

1 Like

Thoughts on Controlling other major systems in the RV

Most RV’s have some kind of control panel near the entry door. Mine is very similar to this one:

There is 12V power in this panel and ground so I have the juice I need to run what I want. The rocker switches along the bottom are simple hot side switches meaning that on one side of the switch there is 12V and the other is the leg that runs to the circuit. For momentary switches like the generator stop/start switch, slidouts, and awnings, I can connect normally open relay switches in parallel to gain control. It would be good to put a two way switch in place on the hot legs to switch control from manual to automatic. I can use a two way relay with the normally closed leg connected to the manual switch. There is room behind this panel (I think) to fit a relay board. At any rate, this is a central location at which i could put a single node on my mesh network and control most major systems.

Thoughts on how to do it best?

A tablet running blynk connected via wifi to the rasp-pi that runs the blynk server on the RV?

1 Like

yes. but I am using my phone.

Placing the Current Sensor

I got some parts in the mail yesterday. I got the CSLA2DC sensor and the 16bit ADC. The sensor was a little smaller than I was expecting so I wanted to see if it was going to fit. I didn’t want to have to rip a lot of the RV apart to put the sensor on and I wanted to keep it inside the RV as much as I could. I found a good place to put it next to the power distribution panel.

The main line coming up from the middle bottom just to the right of my hand is the trunk line from the battery. It connects to the copper colored post on the left. From that, a jumper runs to the main power contact. From the main power contact, the power goes into the panel. If I put the sensor on the line that I’m holding, I will be able to sense the important loads and the charging from the generator.

This isn’t the most perfect spot to put the current sensor because there are a couple of things that branch off before this: 1) I put in a battery maintainer that connects directly to the positive lead of the battery. 2) there is a line that jumps from the house power through a switch at the drivers seat to the starter battery. 3) A third line that I am not sure what it does. I think it is a return line from the alternator to charge the house batteries when the engine is running, but I am not sure. I posted on the RV forum to ask about it and we will see what they say.

In order to get the lead that is connected to the house battery, I would have to put it under the steps and expose it to weather which would mean that I would have to weatherize the sensor. Plus, there are big terminal lugs that I would have to cut and reinstall to get the sensor on there. I don’t want to do that for a number of reasons. Further, the power leads coming off the house battery before this point are not really that important. The purpose is to measure the current you use in the house system and minimize it when dry camping.

I originally wanted to measure amp-hour use in and out of the batteries. But after thinking about it that doesn’t tell me a lot of useful information. Particularly because battery capacity drops over time so integrating total current use is not that useful for telling how charged your batteries are. It is useful for tracking how the capacity changes with time though. I may still measure it on the discharge side and keep a record of the voltage verses amp hour use to track the performance of the batteries which I can do with the current planned placement.

The important thing is to know when your batteries hit 11.9V which is 40% charge and what is safely considered dead. Technically, you can go all the way down to 10.5V, but that is not really good for them and you can start to sulfate the batteries. It’s best to stick above 12V if you can.

Getting the sensor on that wire is no big deal either. I disconnected the silver lead on the left. It was ever so slightly too large to fit through the hole in the sensor. I used a pliers to squeeze the terminal end just a little bit and then re drilled the opening to fit again. I can slip the sensor on and off easily now.

ADC Over I2C

Got the ADC up and running on the same pi that runs the server/client. Was pretty easy.

So the ADC is that little blue board suspended up in the air by the connection wires. The Pi is that little green board sitting on the table. It’s a temporary setup. I’ll eventually get the pi and the ADC mounted to a support board.

The first thing to do is get the I2C setup and enabled on the Pi

  1. uncomment #device_tree_param=i2c_arm=on in the /boot/config.txt

  2. add bcm2708.vc_i2c_override=1 to the end of /boot/cmdline.txt (make sure to adhere to the single space b/t commands and watch the formatting, this file is really sensitive)

  3. in the /etc/modules-load.d/ directory, there will be a *.conf that should be responsible for loading various modules. Add i2c-bcm2708 and i2c-dev as separate lines in the file

  4. reboot the pi

  5. login and install i2c-tools: sudo apt-get install i2c-tools

  6. hook up the i2c device and check that you have communication via: 2cdetect -y 1
    you should end up with some feedback that looks like this:
    0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- 27 -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --

  7. Next get the necessary python libraries with: sudo apt-get install build-essential python-dev python-smbus python-pip
    and
    sudo pip install adafruit-ads1x15

  8. code away. This is what I did for a test:

# -*- coding: utf-8 -*-
"""
	example hardware
"""
__author__	= """Dirk Van Essendelft"""
__date__ 		= "2016-07-09"
__version__	= "0.2.0"
__credits__	= """Copyright, Dirk Van Essendelft"""
__license__	= "MIT"


import sys
import os
import random
import time
sys.path.append(
	os.path.join(
		os.path.dirname(__file__),
		'..'
	)
)
	
TOKEN			= 'yourtoken'

#Imports
import lib.hw as blynk_hw
import lib.client as blynk_client
import Adafruit_ADS1x15

# Blynk Setup
class myHardware(blynk_hw.Hardware):
	"""
		you'll probably have to overload the On* calls,
		see lib/hw.py
	"""
	def OnVirtualRead(self, pin):
		print('OnVirdualRead', pin, type(pin))
		if pin == 0:
			# Battery Voltage
			return 14.28
		elif pin == 1:
			# Battery Current
			return random.random()
		elif pin == 2:
			# Inside Temp
			return 73.4
		elif pin == 3:
			# Inside Humidity
			return 20.1
		elif pin == 4:
			# Ouside Temp
			return 89.1
		elif pin == 5:
			# Outside Humidity
			return 30.0
		else:
			return 0

cConnection=blynk_client.TCP_Client('localhost')
if not cConnection.connect():
	print('Unable to connect')
	sys.exit(-1)

if not cConnection.auth(TOKEN):
	print('Unable to auth')
	
cHardware=myHardware(cConnection)

#ADS1115 setup
adc = Adafruit_ADS1x15.ADS1115()
# Choose a gain of 1 for reading voltages from 0 to 4.09V.
# Or pick a different gain to change the range of voltages that are read:
#  - 2/3 = +/-6.144V
#  -   1 = +/-4.096V
#  -   2 = +/-2.048V
#  -   4 = +/-1.024V
#  -   8 = +/-0.512V
#  -  16 = +/-0.256V
# See table 3 in the ADS1015/ADS1115 datasheet for more info on gain.
GAIN = 2/3
gainDict = {2/3:6.144, 1:4.096, 2:2.048, 4: 1.024, 8:0.512, 16:0.256}

#timers
timer1dt = 5
timer1 = time.time() + timer1dt

# Micro Controller Loop 
try:
	while True:
		cHardware.manage()
		currTime = time.time()
		if currTime > timer1:
			timer1 = currTime + timer1dt
			values = [0]*4
			for i in range(4):
				values[i] = adc.read_adc(i, gain=GAIN) / 32768.0 * gainDict[GAIN]
			print('| {0:6.4f} | {1:6.4f} | {2:6.4f} | {3:6.4f} |'.format(*values))
except KeyboardInterrupt:
	raise

Multiple I2C Devices

I2C is a 2 wire communication system (4 total wires 2 com, 1 gnd, and 1 vcc) where up to 128 devices can be addressed on a single bus. It’s super convenient and easy to use for sensors. Because it’s so easy to use, you will often want to connect multiple devices which can either be a real pain or very easy depending on how you do it. The royal pain way is to make up your own jumper cables, which I have done. The easy way is to buy I2C hubs and patch cables.

The cheapest way I have found to expand my I2C bus is to buy pixhawk compatible parts off ebay. Here’s an example:

It is nothing more than a pcb with multiple DF13 sockets on it. This one costs about $1.27 at the time of writing shipped from china. You will find these style cable connectors used for drones quite frequently so they are cheap.

Then I buy I2C APM 2.5 cables:

They have a DF13 connector on one side and 0.1 pitch header pins on the other which are perfect for connecting up any I2C device. You’d be surprised at the huge range of sensors you can get that are I2C compatible and almost all of them are just a few dollars each.

Temperature, Pressure, Humidity

It is nice to have an indoor and outdoor weather station on an RV… i mean why not right? Maybe I don’t want to go outside if it is 95 and 100% humidity… maybe I want to track and see if bad weather is coming. Again, you can do this with cheap I2C devices. You can get this sensor in a variety of packages for under $5:

Its the BME280 sensor. Very easy to use and has 2 addressable ports on it. Further it has both arduino and python libraries for it thanks to adafruit.

Controlling a Bi directional motor

I’ve been thinking about how to control the Bal powerpaks for direction and on/off. One way to do this is with a DPDT relay to control direction. Wired up like the following:


Of course, it should be obvious that this will only control direction. Another relay is needed to control on/off.

You can get these relays fairly cheap like this one:


You’ll need another relay to send power through this one any 12V 30+Amp relay will do.

Then you need a way to control it from a micro-controller. I will probably buy one of these cheap relay boards and take the relays off and wire to the coils on the ones I want to control.

Minor Setback

I found out today that I cannot rely on the 5V power from the raspberry pi. I started hooking up and testing the current sensor and all was going well until I started the blynk service on my phone which meant that the java service was running on the pi and it was working harder. This made the voltage on the 5V pin unsteady so it started making the sensor change its output in response. Not good! I need an independent 5V supply for this sensor. It has to have a constant voltage input to function correctly because it basically splits the input voltage and shifts it slightly depending on current it’s measuring, so I need a regulated voltage source for this sensor at 5-8V. Time to shop.

Code for Measuring on the Pi

I started setting up some libraries for various functions. I found a problem with the client.py in that something is causing it to block the thread on about a 5 second interval. I think it is the timer when it is looking for a connection. It confused me because I thought that it would be looking for a connection to the server which it has, but I think it is looking for a connection to the app.

Anyhow, I didn’t want to mess with the timing and all that for the blynk server/client so I coded up something that kicks off a new thread to measure stuff as needed. I put this in a class in pypower.py:]

# -*- coding: utf-8 -*-
"""
contains all functions and classes related to measuring power for the rvp
"""
import Adafruit_ADS1x15
import threading
import time

class PyPower(object):
	def __init__(self, currentPin=0, refVoltagePin=1, voltagePin=2, gain=2/3, aveFact=0.1):
		self.current_pin = currentPin
		self.ref_voltage_pin = refVoltagePin
		self.voltage_pin = voltagePin
		self.ave_fact = aveFact
		self._adc = Adafruit_ADS1x15.ADS1115()
		self.gain = gain
		self.gain_dict = {2/3:6.144, 1:4.096, 2:2.048, 4: 1.024, 8:0.512, 16:0.256}
		self.curr_slope = (0.058 - 0.024)/(12.0 - 5.0)
		self.curr_intercept = 0.058 - 12.0*self.curr_slope
		self.ave_current = None
		self.thread = None
		self.stop_thread = False

	def _measureVoltage(self, pin):
		return self._adc.read_adc(pin, gain=self.gain) / 32768.0 * self.gain_dict[self.gain]

	def measureCurrent(self):
		curr_voltage = self._measureVoltage(self.current_pin)
		ref_voltage  = self._measureVoltage(self.ref_voltage_pin)
		sensitivity = self._calcSensitivity(ref_voltage)
		current = (curr_voltage - ref_voltage/2.0) * sensitivity
		if self.ave_current:
			self.ave_current = self.ave_fact * current + (1.0 - self.ave_fact) * self.ave_current
		else:
			self.ave_current = current
#		print curr_voltage, ref_voltage, sensitivity, self.ave_current

	def _calcSensitivity(self, voltage):
		return 1.0/(self.curr_slope * voltage + self.curr_intercept)

	def threadJob(self):
		while True:
			self.measureCurrent()
			time.sleep(0.05)
			if self.stop_thread:
				break

	def startThread(self):
		if not self.thread:
			self.thread = threading.Thread(name='RVPThread', target=self.threadJob)
			self.thread.setDaemon(True)
			self.thread.start()

	def stopThread(self):
		self.stop_thread = True
		self.thread.join()

This class is imported in the client scrypt

# -*- coding: utf-8 -*-
"""
	example hardware
"""
__author__	= """Dirk Van Essendelft"""
__date__ 		= "2016-07-09"
__version__	= "0.2.0"
__credits__	= """Copyright, Dirk Van Essendelft"""
__license__	= "MIT"

#Imports
import sys
import os
import random
import time
sys.path.append(
	os.path.join(
		os.path.dirname(__file__),
		'..'
	)
)
	
import lib.hw as blynk_hw
import lib.client as blynk_client
from pypower import PyPower


TOKEN			= 'yourToken'


#PyPower setup
pp = PyPower()
print('starting thread')
pp.startThread()
print('running...')

# Blynk Setup
class myHardware(blynk_hw.Hardware):
	"""
	RV project event handlers only need to define On* functions
	"""
	def OnVirtualRead(self, pin):
		print('OnVirdualRead', pin, type(pin))
		if pin == 0:
			# Battery Voltage
			return 14.28
		elif pin == 1:
			# Battery Current
			if pp.ave_current:
				return pp.ave_current
			else:
				return 0.0
		elif pin == 2:
			# Inside Temp
			return 73.4
		elif pin == 3:
			# Inside Humidity
			return 20.1
		elif pin == 4:
			# Ouside Temp
			return 89.1
		elif pin == 5:
			# Outside Humidity
			return 30.0
		else:
			return 0

cConnection=blynk_client.TCP_Client('localhost')
if not cConnection.connect():
	print('Unable to connect')
	sys.exit(-1)

if not cConnection.auth(TOKEN):
	print('Unable to auth')
	
cHardware=myHardware(cConnection)

# Micro Controller Loop 
try:
	while True:
		cHardware.manage()		
except KeyboardInterrupt:
	print('Stopping Thread')
	pp.stopThread()
	print('Stopped')
	raise

startThread in pypower creates a daemon thread that runs in the background in a non-blocking way and all this does is call measureCurrent over and over with a 50ms sleep so this thread doesn’t consume all the processor. What you do is pass threadJob into the thread class and the thread runs that function. I put a separate while loop that calls other measurment functions like measureCurrent. Anything else I want to measure on a frequent basis can go in there. The key is to stuff it in a member variable in the class and then when the app asks for it, just pass the member variable in.

I got a little averaging going to help keep the current measurement steady. It does help but it will make response slower. It works ok in the face of not having a steady 5V source, but it could be faster and easier if the supply voltage was constant. I also am using the ADC to measure the supply voltage and compensate somewhat. I see that there is a small offset and probably a need to calibrate. I measure 0.7 ish amps when there is nothing in the sensor.

It works easily and quickly this way so I am happy with the setup so far.

Measuring Level

Its been a while since I had time to work on this project. Its been a busy summer.

I got the accelerameter installed and code written to calibrate the orientation of the sensor to that of the box it is in. I can measure pitch and roll within 0.05 degrees repeatably. I can use this to calculate how many blocks i nees to stack and at which wheels. Later i can use this to jack my rv with leveling legs.

I used an mpu6050 which is about $1 on ebay and talks over I2C.

Heres the start of the library i wrote to calibrate and measure pitch and roll:

from mpu6050 import mpu6050
import numpy as np
from scipy.optimize import minimize
from scipy.linalg import expm3, norm
import os

sensor = mpu6050(0x68)

class AccellGyro(mpu6050):
	def __init__(self):
		mpu6050.__init__(self, 0x68)
		self.calFile = 'accelCal.txt'
		self.angCorFile = 'angCor.txt'
		if not os.path.exists('./' + self.calFile) or not os.path.exists('./'+self.angCorFile):
			self.angCor = np.asarray([0,0])
			self.setCalibration()
		else:
			self.rotationMatrix = np.loadtxt('./'+self.calFile)
			self.angCor = np.loadtxt('./'+self.angCorFile)

	def measureTilt(self, nSamples=1000, vect=None):
		if vect is None:
			vect = self._measureRaw(nSamples)
		raw = self._measureRaw(nSamples)
		print(raw)
		meas = np.dot(self.rotationMatrix, vect)
		print(meas)
		roll = np.arctan(-meas[0]/meas[2])
		pitch = np.arctan(meas[1]/np.sqrt(np.power(meas[0],2)+np.power(meas[2],2)))
		return np.asarray([pitch, roll])+self.angCor

	def _measureRaw(self, nSamples=1000, norm=True):
		data = np.zeros((nSamples, 3))
		for i in range(nSamples):
			x = self.get_accel_data()
			data[i,:] = np.asarray([x['x'], x['y'], x['z']])
		if norm:
			meas = np.mean(data, 0)
		meas /= np.sqrt(np.sum(np.power(meas, 2)))

		return meas

	def setCalibration(self):
		print('Calibration file not found. Starting Calibration Proceedure.')
		while True:
			r = raw_input('On a flat, level surface, place sensor box upright, y to continue: ')
			if r == 'y':
				break
		a = self._measureRaw(5000)
		while True:
			r = raw_input('On a flat, level surface, place sensor box on the right face, y to continue: ')
			if r == 'y':
				break
		b = self._measureRaw(5000)
		while True:
			r = raw_input('On a flat, level surface, place sensor box on the front face, y to continue: ')
			if r == 'y':
				break
		c = self._measureRaw(5000)

		calInList = [a, b, c]
		
		calInListOrder = [np.where(a == a.max())[0][0], np.where(b == b.max())[0][0], np.where(c == c.max())[0][0]]
		calInList = np.asarray([list(calInList[calInListOrder.index(i)]) for i in range(3)])
		self.mask = 1.0 - np.identity(3)
		print
		print('Measurements complete, starting optimization....')
		ret = minimize(self.calibObjective, [0,0,0], args=(calInList[0,:],calInList[1,:],calInList[2,:]), tol=1e-10)
		print(ret.message)
		print
		print('Optimized Calibration Angles (deg):')
		ang = ret.x
		print(ang*180.0/(np.pi))
		print
		self.rotationMatrix = self.rotate3D(*ang)
		print('Calibration complete! Final Rotation Matrix:')
		print(self.rotationMatrix)
		print
		self.angCor = -1.0*self.measureTilt(vect=a)
		print('Angle Correction (deg):')
		print(self.angCor*180.0/np.pi)
		print
		np.savetxt('./'+self.calFile, self.rotationMatrix)
		np.savetxt('./'+self.angCorFile, self.angCor)


	def rotateAboutAxis(self, axis, theta):
		return expm3(np.cross(np.eye(3), axis/norm(axis)*theta))

	def rotate3D(self, a, b, c):
		return np.dot(np.dot(self.rotateAboutAxis([1,0,0], a), self.rotateAboutAxis([0,1,0], b)), self.rotateAboutAxis([0,0,1], c))

	def calibObjective(self, x, *args):
		args = np.asarray(args)
		#print('args')
		#print(args)
		rm = self.rotate3D(*x)
		rot = np.dot(rm, args)
		#print(rot)
		mult = np.multiply(self.mask, rot)
		err1 = np.sum(np.abs(mult))
		return -1.0*(rot[0,0]+rot[1,1]+rot[2,2]) + err1

if __name__ == '__main__':
	s = AccellGyro()
	print('roll and pitch:')
	print(s.measureTilt()*180.0/np.pi)

1 Like

Are you measuring in “bricks”? That would be funny :slight_smile:

1 Like

well yes that is basically what i am doing. you have stackable lego like bricks and i want to calculate which edge is the highest and then how many bricks to put under the other 3 wheels

1 Like

hello @dirktheeng, did you ever complete this project? I’m renovating an airstream and am interested in controlling lights and monitoring a few things. I think running my own Blynk server and controlling things with bluetooth would be optimal so that you can control things “off the grid” Can you send me details on the set up you have working of you?

Also is it possible when you do have access to wifi to be able to control and view the systems while away from the RV

Thanks
Jamison

3 posts were merged into an existing topic: Need help figuring out what I need to make this work