A tablet running blynk connected via wifi to the rasp-pi that runs the blynk server on the RV?
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
-
uncomment
#device_tree_param=i2c_arm=on
in the /boot/config.txt -
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) -
in the /etc/modules-load.d/ directory, there will be a *.conf that should be responsible for loading various modules. Add
i2c-bcm2708
andi2c-dev
as separate lines in the file -
reboot the pi
-
login and install i2c-tools:
sudo apt-get install i2c-tools
-
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: -- -- -- -- -- -- -- --
-
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
-
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)
Are you measuring in âbricksâ? That would be funny
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
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