Update: 2013-08-21

DOH!  Now I get it.  Grog warned me that my script didn't look like it would work despite a "valiant effort" and although I believed him, I had to try it.  He was right (of course). Determined to get to the bottom of how this all worked I went back to a BasicSerialRead.py that I had written to just pull data off the wire and print it out.  I tweaked it for using publishByte() rather than read() and added a print statement or two before firing it up. As soon as I saw the output in the Python console I knew what was going on and that I was being unbelievably dense for someone who has been programming for over almost 2 decades.  So here's my Eureka moment in a nutshell in case it helps someone else understand how it works.

You set up your serial listener with a statement like so

serial.addListener("publishByte", python.name, "input")

What this does in plain (bad) English is when a byte of data comes in on the serial line via publishByte, the Listener will fire up an instance of the input() method in the Python code. That input() method will have access to that single byte of data in an array called msg_serial_publishByte.data[0] (that's index 0 of the array). When the next byte of data comes in on the wire, another instance of input() is fired up to handle it and so on. So in my case a whole bunch of bytes come in on the wire every 2 seconds and although that stream of bytes come in back to back in one packet, the input() method only gets one. If I have 200 bytes coming in, then 200 input() methods get fired up to each handle one byte.

So if I wanted to do the Xbee packet parsing with this setup, I would have to set up a global array to act as a buffer and each input() call would stuff their byte of data into the buffer.  Some additional smarts would look at the byte and see if it is a packet starting byte and then call some other code to check if there is a whole packet in the array yet and if so, grab the data, process it, and then remove it from the array. Assuming all that data processing takes less than 2 seconds, it will not fall behind the rate of packets coming in.

Meanwhile, Grog was reading over my example code and understanding what my script was trying to do for grabbing the data. He's now adding more publishing/reading methods to the serial service to cover all the cases that my example would need. Another good example of read world usage driving development.

It is tempting to try to brute force my way through the single byte process I outlined above but I think I would be better off waiting for the new serial input methods. It will certainly lead to more elegant Python code.

So if you have an idea that you think you could use MRL for, throw it at the wall and we'll try to make it stick.

----------------------------------------------------------------------------------

Original post: 2013-08-20

Two things that could be used to describe me are "cheap" and "data hoarder".  While I like to buy cool stuff I prefer to only spend what I need to on boring things.  When it comes to data collection I'm definitely of the school of "more is better".  When I saw the Adafruit Tweet-a-watt project I decided it would be a fun build and it would allow me to track power usage of boring things around the house that I could potentially turn down, upgrade or get rid of. 

The build went simple enough after I found an "original" Kill-a-watt on Amazon. I started using the device to monitor my kitchen fridge but I found it annoying to have to have a Windows laptop running to run the software for collecting and graphing the data. I'd much rather have my Linux server doing the job.  The project got dropped in priority for a while.

Enter MyRobotLab ... looking over all of the things that MRL can do and how Grog was using it to control the sprinklers at his home, I thought I might use MRL to collect my tweet-a-watt data and send it to ThingSpeak.  The tweet-a-watt is basically a "dumb" Xbee embedded in the Kill-a-watt device that samples the voltage and current usage pins via two ADC channels and transmits several samples every 2 seconds. The data is picked up on the computer end by a simple Xbee receiver connected to an FTDI-USB dongle. There are no Arduinos or other microcontrollers in the mix. Just a basic wireless serial data stream wrapped in an Xbee packet.

Once Grog got the Simple Serial Service going, I dug out the Xbee dongle and started trying to read in the data with MRL.  I've gotten as far as having the python script start a serial service and seeing the data being printed in the serial console in the WebGUI.  Being new to Python, Jython, and the MRL framework I'm still trying to wrap my head around how the pieces of the code should be linked together.  I'll attach the code that I've hacked together from the Serial Service examples I've seen around here and the Adafruit python code (http://www.ladyada.net/make/tweetawatt/parser.html and https://github.com/adafruit/Tweet-a-Watt)

 

GroG

11 years 3 months ago

It stumped me a while, figuring out how to provide blocking methods.  These are methods which wait until the right amount of data has been filled before they return.  This equivalent to someone filling a pale of water, and not leaving the faucet until it's filled up "enough"...

Without blocking methods there has to be loops in the "users" code which check the pale of water, and if its not to the right height - you have to say .. "Nope, go get more" ... this clutters the user code with loops and checking return values...

We want a method we can call and guarantee that a full pale of water comes back !

And now WE HAVE IT !!! :)

Serial service now has 6 fresh new methods :

  • readByte
  • readChar
  • readInt
  • readLong
  • readByteArray(length) 
  • readString(delimeter)

All of these will keep you waiting until you get exactly what you want - ALL YOUR DATA !

For your particular case KMC, (if I remember correctly) - your code will want to read a byte until it finds the byte marker for a packet - this would be done by serial.readByte()

Next it wants a integer - you have to be a little careful with this one cause its multi-byte and gets translated into a single value - so Endian counts - most everything does it network order ... which is Big Endian or most significant byte first... that is the "default" way readInt works ... so next comes size = serial.readInt() .. this will be the size of the rest of the packet .. which you can get by doing serial.readByteArray(size)

let me know if this works for you .. good luck :)