Permalink
Browse files

Adding XBee live plot

  • Loading branch information...
1 parent 0083ee6 commit c77c88e3e28cb0b810e0a85eaadf8fdef783676c @abhinavgupta committed Oct 31, 2012
@@ -0,0 +1,19 @@
+
+TempSensor
+----------
+This is a simple live plotting code for XBee. The hardware setup includes two XBees and a temp sensor connected to the end device's ADC. NO ARDUINOS OR OTHER CONTROLLERS
+
+The transmission although AT in nature, the data packets are in the API I/O frame format. We use the xbee-python library for ease in the data parsing.
+
+Source for xbee-python : http://code.google.com/p/python-xbee/
+
+Using monitor.py, a client computer with the other XBee connected can plot the temperature data transmitted by the Router AT wirelessly.
+
+You need:
+ - PyQt
+ - PyQwt
+ - PySerial
+
+Python modules for this to work correctly.
+
+Note that the serial port is hardcoded in SerialMonitor.py, change it to fit your needs. (USE: 'dmesg|tail' on Linux to know what the serial port is, just after connecting your XBee device). Other things hardcoded in this file is the conversion of ADC data to temperature value using a Look Up Table. The temperature measurement in query is a thermistor hence the setup. If you have a LM35 you just need to map the voltage to the temperature value instead of going through the entire process. I have marked the part which is special to my need in the code.
@@ -0,0 +1,23 @@
+
+TempSensor
+----------
+This is a simple live plotting code for XBee. The hardware setup includes two XBees and a temp sensor connected to the end device's ADC. NO ARDUINOS OR OTHER CONTROLLERS
+
+The transmission although AT in nature, the data packets are in the API I/O frame format. We use the xbee-python library for ease in the data parsing.
+
+Source for xbee-python : http://code.google.com/p/python-xbee/
+
+Using monitor.py, a client computer with the other XBee connected can plot the temperature data transmitted by the Router AT wirelessly.
+
+You need:
+ - PyQt
+ - PyQwt
+ - PySerial
+
+Python modules for this to work correctly.
+
+Note that the serial port is hardcoded in SerialMonitor.py, change it to fit your needs. (USE: 'dmesg|tail' on Linux to know what the serial port is, just after connecting your XBee device). Other things hardcoded in this file is the conversion of ADC data to temperature value using a Look Up Table. The temperature measurement in query is a thermistor hence the setup. If you have a LM35 you just need to map the voltage to the temperature value instead of going through the entire process. I have marked the part which is special to my need in the code.
+
+The Plotter application is a simplified/modified version of Eli Bendersky's
+plotter:
+ http://eli.thegreenplace.net/2009/08/07/a-live-data-monitor-with-python-pyqt-and-pyserial/
@@ -0,0 +1,83 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import time
+import Queue
+import threading
+from xbee import XBee
+import serial
+import dict_lut
+
+ADC_RESOLUTION = 1024.00
+RESISTANCE = 47.00
+
+class SerialMonitorThread(threading.Thread):
+ """ A thread for monitoring a serial port. The serial port is
+ opened when the thread is started.
+
+ data_q:
+ Queue for received data. Items in the queue are
+ (data, timestamp) pairs, where data is a binary
+ string representing the received data, and timestamp
+ is the time elapsed from the thread's start (in
+ seconds).
+
+ error_q:
+ Queue for error messages. In particular, if the
+ serial port fails to open for some reason, an error
+ is placed into this queue.
+ """
+ def __init__(self, data_q, error_q):
+ threading.Thread.__init__(self)
+
+ self.serial_port = None
+ self.data_q = data_q
+ self.error_q = error_q
+
+ self.alive = threading.Event()
+ self.alive.set()
+
+ def run(self):
+ try:
+ if self.serial_port:
+ self.serial_port.close()
+ # Port hardcoded :)
+ self.serial_port = serial.Serial("/dev/ttyACM0", 9600)
+ except serial.SerialException, e:
+ self.error_q.put(e.message)
+ return
+
+ # Restart the clock
+ time.clock()
+
+ while self.alive.isSet():
+ # Reading 1 byte, followed by whatever is left in the
+ # read buffer, as suggested by the developer of
+ # PySerial.
+ #
+
+ # XBee library used from this point:
+ xbee = XBee(self.serial_port)
+ #Read Response frame now:
+ response = xbee.wait_read_frame()
+ #Read the sample part from the respnase frame taking the first value (In case you use multiple sensors):
+ sample = response['samples'][0]
+ #ADC-0 is the one in my setup
+ adc_value = sample['adc-0']
+ #Changing ADC value to Resistance and subsequent mapping of Resistance to Temperature in the next three steps
+ # FIXME: Change for your specific use, this works fine for my use just used the highlighter to point this fact
+ temp_value = ADC_RESOLUTION/adc_value -1
+ thermistor_value = RESISTANCE/temp_value
+ temperature_value = dict_lut.closest_match(thermistor_value)
+ #Add Time stamp here:
+ timestamp = time.clock()
+ self.data_q.put((temperature_value, timestamp))
+
+ # clean up
+ if self.serial_port:
+ self.serial_port.close()
+
+ def join(self, timeout=None):
+ self.alive.clear()
+ threading.Thread.join(self, timeout)
+
@@ -0,0 +1,76 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import time
+import Queue
+import threading
+from xbee import XBee
+import serial
+import dict_lut
+
+ADC_RESOLUTION = 1024.00
+RESISTANCE = 47.00
+
+class SerialMonitorThread(threading.Thread):
+ """ A thread for monitoring a serial port. The serial port is
+ opened when the thread is started.
+
+ data_q:
+ Queue for received data. Items in the queue are
+ (data, timestamp) pairs, where data is a binary
+ string representing the received data, and timestamp
+ is the time elapsed from the thread's start (in
+ seconds).
+
+ error_q:
+ Queue for error messages. In particular, if the
+ serial port fails to open for some reason, an error
+ is placed into this queue.
+ """
+ def __init__(self, data_q, error_q):
+ threading.Thread.__init__(self)
+
+ self.serial_port = None
+ self.data_q = data_q
+ self.error_q = error_q
+
+ self.alive = threading.Event()
+ self.alive.set()
+
+ def run(self):
+ try:
+ if self.serial_port:
+ self.serial_port.close()
+ # Port hardcoded :)
+ self.serial_port = serial.Serial("/dev/ttyACM0", 9600)
+ except serial.SerialException, e:
+ self.error_q.put(e.message)
+ return
+
+ # Restart the clock
+ time.clock()
+
+ while self.alive.isSet():
+ # Reading 1 byte, followed by whatever is left in the
+ # read buffer, as suggested by the developer of
+ # PySerial.
+ #
+ xbee = XBee(self.serial_port)
+ # data = self.serial_port.next()
+ response = xbee.wait_read_frame()
+ sample = response['samples'][0]
+ adc_value = sample['adc-0']
+ temp_value = ADC_RESOLUTION/adc_value -1
+ thermistor_value = RESISTANCE/temp_value
+ temperature_value = dict_lut.closest_match(thermistor_value)
+ timestamp = time.clock()
+ self.data_q.put((temperature_value, timestamp))
+
+ # clean up
+ if self.serial_port:
+ self.serial_port.close()
+
+ def join(self, timeout=None):
+ self.alive.clear()
+ threading.Thread.join(self, timeout)
+
@@ -0,0 +1,14 @@
+from bisect import bisect_right
+
+Temperature = [15.0,15.1,15.2,15.3,15.4,15.5,15.6,15.7,15.8,15.9,16.0,16.1,16.2,16.3,16.4,16.5,16.6,16.7,16.8,16.9,17.0,17.1,17.2,17.3,17.4,17.5,17.6,17.7,17.8,17.9,18.0,18.1,18.2,18.3,18.4,18.5,18.6,18.7,18.8,18.9,19.0,19.1,19.2,19.3,19.4,19.5,19.6,19.7,19.8,19.9,20.0,20.1,20.2,20.3,20.4,20.5,20.6,20.7,20.8,20.9,21.0,21.1,21.2,21.3,21.4,21.5,21.6,21.7,21.8,21.9,22.0,22.1,22.2,22.3,22.4,22.5,22.6,22.7,22.8,22.9,23.0,23.1,23.2,23.3,23.4,23.5,23.6,23.7,23.8,23.9,24.0,24.1,24.2,24.3,24.4,24.5,24.6,24.7,24.8,24.9,25,25.1,25.2,25.3,25.4,25.5,25.6,25.7,25.8,25.9,26,26.1,26.2,26.3,26.4,26.5,26.6,26.7,26.8,26.9,27,27.1,27.2,27.3,27.4,27.5,27.6,27.7,27.8,27.9,28,28.1,28.2,28.3,28.4,28.5,28.6,28.7,28.8,28.9,29,29.1,29.2,29.3,29.4,29.5,29.6,29.7,29.8,29.9,30,30.1,30.2,30.3,30.4,30.5,30.6,30.7,30.8,30.9,31,31.1,31.2,31.3,31.4,31.5,31.6,31.7,31.8,31.9,32,32.1,32.2,32.3,32.4,32.5,32.6,32.7,32.8,32.9,33,33.1,33.2,33.3,33.4,33.5,33.6,33.7,33.8,33.9,34,34.1,34.2,34.3,34.4,34.5,34.6,34.7,34.8,34.9,35,35.1,35.2,35.3,35.4,35.5,35.6,35.7,35.8,35.9,36,36.1,36.2,36.3,36.4,36.5,36.6,36.7,36.8,36.9,37,37.1,37.2,37.3,37.4,37.5,37.6,37.7,37.8,37.9,38,38.1,38.2,38.3,38.4,38.5,38.6,38.7,38.8,38.9,39,39.1,39.2,39.3,39.4,39.5,39.6,39.7,39.8,39.9,40]
+Resistance = [3.539,3.523,3.506,3.490,3.474,3.458,3.442,3.426,3.410,3.394,3.379,3.363,3.348,3.332,3.317,3.301,3.286,3.271,3.256,3.241,3.226,3.212,3.197,3.182,1.168,3.153,3.139,3.124,3.110,3.096,3.082,3.068,3.054,3.040,3.026,3.012,2.998,2.985,2.971,2.958,2.944,2.931,2.918,2.905,2.891,2.878,2.865,2.852,2.840,2.827,2.814,2.801,2.789,2.776,2.764,2.751,2.739,2.727,2.714,2.702,2.690,2.678,2.666,2.654,2.642,2.630,2.619,2.607,2.595,2.584,2.572,2.561,2.549,2.538,2.527,2.516,2.504,2.493,2.482,2.471,2.460,2.449,2.439,2.428,2.417,2.406,2.396,2.385,2.375,2.364,2.354,2.344,2.333,2.323,2.313,2.303,2.293,2.283,2.273,2.263,2.253,2.243,2.233,2.223,2.214,2.204,2.194,2.185,2.175,2.166,2.156,2.147,2.138,2.128,2.119,2.110,2.101,2.092,2.083,2.074,2.065,2.056,2.047,2.038,2.029,2.020,2.012,2.003,1.994,1.986,1.977,1.969,1.960,1.952,1.944,1.935,1.927,1.919,1.910,1.902,1.894,1.886,1.878,1.870,1.862,1.854,1.846,1.838,1.831,1.823,1.815,1.807,1.800,1.792,1.784,1.777,1.769,1.762,1.754,1.747,1.740,1.732,1.725,1.718,1.710,1.703,1.696,1.689,1.682,1.675,1.668,1.661,1.654,1.647,1.640,1.633,1.626,1.619,1.613,1.606,1.599,1.592,1.586,1.579,1.573,1.566,1.560,1.553,1.547,1.540,1.534,1.527,1.521,1.515,1.508,1.502,1.496,1.490,1.484,1.478,1.471,1.465,1.459,1.453,1.447,1.441,1.435,1.430,1.424,1.418,1.412,1.406,1.400,1.395,1.389,1.383,1.378,1.372,1.366,1.361,1.355,1.350,1.344,1.339,1.333,1.328,1.322,1.317,1.312,1.306,1.301,1.296,1.291,1.285,1.280,1.275,1.270,1.265,1.260,1.254,1.249,1.244,1.239,1.234,1.229,1.224,1.219,1.215,1.210,1.205,1.200]
+
+
+data = dict(zip(Resistance, Temperature))
+
+def closest_match(query):
+ keys = sorted(data)
+ binary_search = bisect_right(keys,query)
+ value = data[min(map(abs, (keys[binary_search-1],keys[binary_search])))]
+
+ return value
@@ -0,0 +1,160 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Modified version of Eli Bendersky's live plotter
+# http://eli.thegreenplace.net/2009/08/07/a-live-data-monitor-with-python-pyqt-and-pyserial/
+# Ozan Caglayan - 2012
+
+import sys
+import Queue
+
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+
+import PyQt4.Qwt5 as Qwt
+
+from SerialMonitor import SerialMonitorThread
+
+def get_all_from_queue(Q):
+ """ Generator to yield one after the others all items
+ currently in the queue Q, without any waiting.
+ """
+ try:
+ while True:
+ yield Q.get_nowait()
+ except Queue.Empty:
+ raise StopIteration
+
+class LiveDataFeed(object):
+ """
+ to read the most recent data and find out whether it was
+ updated since the last read.
+
+ Interface to data writer:
+
+ add_data(data):
+ Add new data to the feed.
+
+ Interface to reader:
+
+ read_data():
+ Returns the most recent data.
+
+ has_new_data:
+ A boolean attribute telling the reader whether the
+ data was updated since the last read.
+ """
+ def __init__(self):
+ self.cur_data = None
+ self.has_new_data = False
+
+ def add_data(self, data):
+ self.cur_data = data
+ self.has_new_data = True
+
+ def read_data(self):
+ self.has_new_data = False
+ return self.cur_data
+class PlottingDataMonitor(QMainWindow):
+ def __init__(self, parent=None):
+ super(PlottingDataMonitor, self).__init__(parent)
+
+ self.livefeed = LiveDataFeed()
+ self.temperature_samples = []
+
+ self.create_main_frame()
+
+ self.data_q = Queue.Queue()
+ self.error_q = Queue.Queue()
+ self.serial_monitor = SerialMonitorThread(self.data_q, self.error_q)
+ self.serial_monitor.start()
+ self.monitor_active = True
+
+ self.timer = QTimer()
+ self.timer.timeout.connect(self.on_timer)
+ self.timer.start(1000)
+
+ def create_plot(self):
+ plot = Qwt.QwtPlot(self)
+ plot.setAxisTitle(Qwt.QwtPlot.xBottom, 'Time')
+ plot.setAxisScale(Qwt.QwtPlot.xBottom, 0, 3, 0.1)
+ plot.setAxisTitle(Qwt.QwtPlot.yLeft, 'Temperature')
+ plot.setAxisScale(Qwt.QwtPlot.yLeft, 0, 50, 5)
+ plot.replot()
+
+ curve = Qwt.QwtPlotCurve('')
+ curve.setRenderHint(Qwt.QwtPlotItem.RenderAntialiased)
+ pen = QPen(QColor('red'))
+ pen.setWidth(2)
+ curve.setPen(pen)
+ curve.attach(plot)
+
+ return plot, curve
+
+ def create_main_frame(self):
+ self.plot, self.curve = self.create_plot()
+
+ plot_layout = QVBoxLayout()
+ plot_layout.addWidget(self.plot)
+
+ plot_groupbox = QGroupBox('Temperature')
+ plot_groupbox.setLayout(plot_layout)
+
+ # Main frame and layout
+ #
+ self.main_frame = QWidget()
+ main_layout = QVBoxLayout()
+ main_layout.addWidget(plot_groupbox)
+ main_layout.addStretch(1)
+ self.main_frame.setLayout(main_layout)
+
+ self.setCentralWidget(self.main_frame)
+
+ def on_timer(self):
+ """ Executed periodically when the monitor update timer
+ is fired.
+ """
+ self.read_serial_data()
+ self.update_monitor()
+
+ def update_monitor(self):
+ """ Updates the state of the monitor window with new
+ data. The livefeed is used to find out whether new
+ data was received since the last update. If not,
+ nothing is updated.
+ """
+ if self.livefeed.has_new_data:
+ data = self.livefeed.read_data()
+
+ self.temperature_samples.append(
+ (data['timestamp'], data['temperature']))
+ if len(self.temperature_samples) > 100:
+ self.temperature_samples.pop(0)
+
+ xdata = [s[0] for s in self.temperature_samples]
+ ydata = [s[1] for s in self.temperature_samples]
+
+ self.plot.setAxisScale(Qwt.QwtPlot.xBottom, xdata[0], max(20, xdata[-1]))
+ self.curve.setData(xdata, ydata)
+ self.plot.replot()
+
+
+ def read_serial_data(self):
+ """ Called periodically by the update timer to read data
+ from the serial port.
+ """
+ qdata = list(get_all_from_queue(self.data_q))
+ if len(qdata) > 0:
+ data = dict(timestamp=qdata[-1][1],
+ temperature=float(qdata[-1][0]))
+ #print qdata[-1][0]
+ self.livefeed.add_data(data)
+
+def main():
+ app = QApplication(sys.argv)
+ form = PlottingDataMonitor()
+ form.show()
+ app.exec_()
+
+if __name__ == "__main__":
+ main()
@@ -0,0 +1,12 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import serial
+
+TTY = "/dev/ttyUSB0"
+
+port = serial.Serial(TTY)
+
+for temp in port:
+ temp = temp.strip()
+ print "Temperature is: %s" % temp

0 comments on commit c77c88e

Please sign in to comment.