Wednesday 9 May 2012

Arduino garden helper

 Over the recent bank holiday weekend we decided to plant a few vegetables in the garden. Nothing too radical, a few Onions, Peppers, Chilli and re-plant our little strawberry plant. We invested in one of those small polythene tunnels too, so help give them a bit of a boost start and see if we can get the plants happy despite the miserable cold weather we've been having. To this end I finally thought up a use for my Arduino I've had kicking around. So I started to think about a soil moisture and poly-tunnel temperature sensor type array that would feed back to the Arduino and tell us when it wanted watering, and also what the highs and lows are for temperature.

So I set about making a soil moisture sensor. My basic background in electronics helped and I knew the simplest method is to use a resistance type technique, so provide +DC to a probe, and them measure the resistance between that and a second probe. This should be easy for the Arudino as it has a few analogue inputs that I can use. The second input was temperature, so I used a temperature sensor I'd previously bought from oomlout.co.uk (TMP36) which has a nice range of -40oC up to 125oC so perfect for this. It's supplied in a standard semiconductor package so first I had to make it a little more weatherproof. Soldering up the 3 wires to a long length of cable (burglar alarm cable, which is my fave cable for general purpose as its 6-core and pretty reasonable in terms of weathering and loss of signal). I then coated each of the exposed wires with a bit of heatgun glue, followed by electricians tape round each leg, so each were electrically insulated and also hopefully sufficiently weather proofed. (Not sure how well that would survive totally exposed to the elements, but as this was going in the poly-tunnel I thought it would be fine).

Next was to build the soil moisture sensor. There are a few different ones on the net, so I went with one that seemed a common way of doing it. Set two probes (in my case 2 brass screws) in a piece of electrically non-conducting packing foam. So this lets the screws push down into the earth whilst keeping the rest of the screws insulated. At the top I then wire wrapped the alarm wire around the screws and added a few blobs of solder for good measure. A bit more electricians tape finished them off.

Here is the sensor that I came up with. You can see the TMP36 semiconductor sticking out the top, and the screw probes out the bottom.

 

After the probe was sorted, I ran it round outside into the poly-tunnel:

 

So the final part was the circuit into the Arduino, and also the code to make it function. The breadboard I had already connected to my Arduino had the LCD attached so I left that to give constant output on the Temperature and soil moisture, so all I had to add was the connections out to the remote probes. I found an interesting issue, at first the soil moisture values varied wildly, even when the probe wasn't in the soil, until I realised that this was because nothing was 'pulling' the input to ground, adding a quick resistor into the setup so that it pulled the input to 0 when it didn't have any other value sorted that out and I got values depending on the resistance being presented. At first I'd connected the soil moisture probe to +5ve and the other probe to my analogue input. One problem I'd found reported quite frequently online was about electrolysis of the metal probes (where electricity passing through them caused corrosion to take place). To counter this I moved the moisture probe to an output pin, and so I'll only switch on current to the probe when I wish to measure it, hopefully reducing the amount of electrolysis taking place.

A few iterations of code were required to get the reading showing anything sensible, and de-bouncing the inputs was essential. I found 4 iterations of the sensor read loop followed by an average calculation stopped a lot of the large fluctuations taking place and gave me what appear to be stable readings, and when compared with the temperature read-out from the car, the outdoor temperature looked around the right values. Soil moisture though is still a bit of an issue as I need to self-calibrate the sensor by checking the soil and deciding when it gets too dry to set that as the lower threshold.

I'll post the final code up here shortly as I'm still fine-tuning it, and also hopefully connecting my Arduino up so I can access the readings remotely and graph them using my rrd graphing system I use for other things around the house (heating, electricity, etc).

Here are a few images of the Arduino in its plastic (indoor) enclosure in operation. The Green LED lights up when the soil moisture drops below the triggered limit, and the Red LED lights when the readings are being taken. Debugging is also output via the serial port.

  

 

Aruino code + schematics to follow!

 

 

Update 10/May/2012 - Here is the current Arduino code I've been using, sorry about the messy code!

<pre>

// AndyB 7-may-2012 Outdoor temperature and soil moisture circuit

// ---------------------------------------------------------------

// include the library code:

#include <LiquidCrystal.h>

#include <EEPROM.h>

#include <Time.h>

#include <TimeAlarms.h>

 

// PINS

//   2 3 4 5 11 12 - LCD

//  9 = watering LED

// 10 = soil power pin

// 13 = Data collection LED

//  8 = LCD display light

//  6 = LCD display button light up

 

// initialize the library with the numbers of the interface pins

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

int sensorPin = A0;       // Temperature sensor

int soilMoisturePin = A1; // soil moisture

int sensorValue1 = 0;

int addr = 0;

int SoilValue1 = 0;

float temperatureC = 0;

const int buttonPin = 7;     // the number of the pushbutton pin

int buttonState = 0;         // variable for reading the pushbutton status

const int ledPin =  13;      // the number of the LED pin

const int waterPin = 9;      // the LED for watering

const int soilPowerPin = 10;  // Pin to power up the soil moisture sensor

const int lcdPowerPin = 8;    // Pin to light up the LCD

const int lcdButtonPin = 6;  // Switch to light up the LCD for a few loops

 

int lcdlightloop=0;

 

int logloop = 0;

 

int debug = 0;  // Debug setting

 

void setup() {

  Serial.begin(9600);

  if (debug == 1) {Serial.println("Program initialised");};

  pinMode(ledPin, OUTPUT);  

  pinMode(buttonPin, INPUT);

  pinMode(waterPin, OUTPUT);

  pinMode(soilPowerPin, OUTPUT);

  pinMode(lcdPowerPin, OUTPUT);

  pinMode(lcdButtonPin, INPUT);

  // set up the LCD's number of columns and rows: 

  lcd.begin(16, 2);

  digitalWrite(lcdPowerPin, HIGH);

  // Print a message to the LCD.

  // lcd.print("       oC");

  // write a 0 to all 512 bytes of the EEPROM

//  for (int i = 0; i < 512; i++) {

//    EEPROM.write(i, 0);

//  };

  // do not reset the EEprom, find the last value and set the pointer, avoids power resets loosing old data

  if (debug == 1) {Serial.println("reading EEPROM");};

  for (int i = 0; i < 512; i++) {

    byte tmp_val = EEPROM.read(i);

    Serial.print("EEPROM ");

    Serial.print(i);

    Serial.print(": ");

    Serial.println(tmp_val, DEC);

    int tmp_val_int=tmp_val;

    if (tmp_val_int == 0) {

        addr=i;

        if (debug == 1) {Serial.print("EEPROM empty at ");};

        if (debug == 1) {Serial.println(i);};

        break;

    };

  };

  digitalWrite(soilPowerPin, LOW);

//  sensorValue1 = analogRead(sensorPin);

//  float voltage = sensorValue1 * 5.0;

//  voltage /= 1024.0;

//  temperatureC = (voltage - 0.5) * 100;

//  SoilValue1 = analogRead(soilMoisturePin);

//  Serial.println("Back from reading sensors");

//  logdatatoeeprom();

  if (debug == 1) {Serial.println("Setup completed");};

  // Alarm.timerRepeat(9000, logdatatoeeprom); // every 15 minutes

  // Alarm.timerRepeat(15, logdatatoeeprom);

  digitalWrite(lcdPowerPin, LOW);

}

 

void loop() {

  if (debug == 1) {Serial.println("Starting main loop");};

  // check the button status

  buttonState = digitalRead(buttonPin);

  if (buttonState == HIGH) {     

    // Button pressed - Reset all of the EEPROM values

    if (debug == 1) {Serial.println("Button pressed - resetting EEPROM");};

    digitalWrite(ledPin, HIGH);  

    // write a 0 to all 512 bytes of the EEPROM

    for (int i = 0; i < 512; i++) {

      EEPROM.write(i, 0);

    };

    Serial.println("EEPROM ERASED");

    addr=0;

    logdatatoeeprom();

    digitalWrite(ledPin, LOW);

  };

  // check for LCD display button

  if (digitalRead(lcdButtonPin) == HIGH) {

      // LCD button was pressed

      lcdlightloop=1;

  };

  if (lcdlightloop > 0) {

      lcdlightloop += 1;

      digitalWrite(lcdPowerPin, HIGH);

  };

  if (lcdlightloop > 20) {

    lcdlightloop=0;

    digitalWrite(lcdPowerPin, LOW);

  };

  if (debug == 1) {Serial.println("Button checks complete");};

  // set the cursor to column 0, line 1

  // (note: line 1 is the second row, since counting begins with 0):

  digitalWrite(ledPin, HIGH);

  if (debug == 1) {Serial.println("Read temp sensor");};

  

  // READ SENSORS HERE

  temperatureC = readinput_temp();

  SoilValue1 = readinput_soil();

  

  if (SoilValue1 < 40) {

    digitalWrite(waterPin, HIGH);

  } else {

    digitalWrite(waterPin, LOW);

  };

 if (debug == 1) {Serial.println("Display to LCD");};

 Serial.println("Temp: ");

 Serial.println(temperatureC);

 Serial.println("Moisture: ");

 Serial.println(SoilValue1);

  lcd.clear();

  lcd.setCursor(0,0);

  lcd.print("temp: ");

  lcd.print(temperatureC,1);

  lcd.print(" oC");

  lcd.setCursor(0,1);

  lcd.print("soil: ");

  lcd.print(SoilValue1);

if (debug == 1) {Serial.println("Done writing LCD");};

    // we write to the eeprom every 10 iterations

    logloop=logloop+1;

    if (debug == 1) {Serial.print("Testing for logloop ");};

    if (debug == 1) {Serial.println(logloop);};

      if (logloop > 9) {

        logdatatoeeprom();

        logloop = 0;

      };

  digitalWrite(ledPin, LOW);

  if (debug == 1) {Serial.println("LED off");};

  Alarm.delay(10000); // wait ten second between clock display

  if (debug == 1) {Serial.println("Delay set");};

}

 

void logdatatoeeprom(){

  digitalWrite(ledPin, HIGH);

  lcd.setCursor(15,1);

  lcd.print("E");

  if (debug == 1) {Serial.println("logdatatoeeprom started");};

  if (debug == 1) {Serial.print("writing to eeprom ");};

  if (debug == 1) {Serial.print(addr);};

  if (debug == 1) {Serial.print(" : ");};

  if (debug == 1) {Serial.println(temperatureC);};

  EEPROM.write(addr, temperatureC);

  if (debug == 1) {Serial.println("back from writing to eeprom");};

  digitalWrite(ledPin, LOW);

  digitalWrite(ledPin, HIGH);

  digitalWrite(ledPin, LOW);

  // advance to the next address.  there are 512 bytes in 

  // the EEPROM, so go back to 0 when we hit 512.

  addr = addr + 1;

  if (addr == 512) {

    addr = 0;

  };

  if (debug == 1) {Serial.println("end logdatatoeeprom");};

}

 

float readinput_temp() {

  // Read inputs and de-bounce, so read 3 times and take an average

  if (debug == 1) {Serial.println("Readinputs (Temperature) function started");};

  float temp1 = 0;

  float temp2 = 0;

  int soil1 = 0;

  int soil2 = 0;

  int lpcount=0;

  

  while (lpcount<6) {

    sensorValue1 = analogRead(sensorPin);

    float voltage = sensorValue1 * 5.0;

    voltage /= 1024.0;

    temp2 = (voltage - 0.5) * 100;

    temp1 += temp2;

    if (debug == 1) {Serial.print("Temp loop :");};

    if (debug == 1) {Serial.print(lpcount);};

    if (debug == 1) {Serial.print(" : ");};

    if (debug == 1) {Serial.println(temp2);};

    lpcount=lpcount+1;

  };

  temperatureC = temp1/6;

  if (debug == 1) {Serial.print("Temperature average: ");};

  if (debug == 1) {Serial.println(temperatureC);};

  if (debug == 1) {Serial.println("Exit Readinputs (Temperature)");};

return temperatureC;

};

 

int readinput_soil() {

  if (debug == 1) {Serial.println("Readinputs (Soil) function started");};

  // Switch power pin on for the soil sensor

  digitalWrite(soilPowerPin, HIGH);

  float temp1 = 0;

  float temp2 = 0;

  int soil1 = 0;

  int soil2 = 0;

  int lpcount=0;

 

  if (debug == 1) {Serial.println("Read soil moisture sensor");};

 

  lpcount=0;

  while (lpcount<4) {

    soil2 = analogRead(soilMoisturePin);

    soil2 = (soil2 / 2);

    soil1 += soil2;

    if (debug == 1) {Serial.print("Soil loop :");};

    if (debug == 1) {Serial.print(lpcount);};

    if (debug == 1) {Serial.print(" : ");};

    if (debug == 1) {Serial.println(soil2);};

    lpcount=lpcount+1;

  };

  SoilValue1 = soil1/4;

  if (debug == 1) {Serial.print("Soil average: ");};

  if (debug == 1) {Serial.println(SoilValue1);};

  digitalWrite(soilPowerPin, LOW);

  if (debug == 1) {Serial.println("Exit Readinputs (Soil)");};

return SoilValue1;

}

 

</pre>

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

Update: Tuesday 15th May 2012

I'm now graphing the output as I've hooked the Arduino up to a laptop via USB, below are the graphs (updated every hour):

    

 

Saturday 5 May 2012

XBMC, TVHeadend, MythTV, streaming to your TV

 I thought it was about time I wrote a bit more about what I've been up to with our TV setup at home. Firstly a bit of background, take a look at the previous posts on my XBMC setup, but that is now a bit out of date as I've found this isn't a set and forget sort of setup, so I can see why a lot of people won't bother with this day-to-day. But I'm persistant and wanted to get my setup as good as possible so we've got the best of several options.

Firstly FreeSat with the superior HD channels, then Freeview (via Aerial) as this still has some channels not on FreeSat, then PVR functionality for pause/rewind of live tv, finally playback from the network media drive. In addition I've also got a want to stream iPlayer, ITV, 4od and 5 via their online stream facilities.

So originally I had TVHeadend running (via their git repository) with a custom patch for adapter priorities. That patch allows me to 'weight' which adapter I want to be used, so the rule was, use the Freesat adapters, unless the channel wasn't available (or signal was too low). This worked great, but there are problems with the integration to XBMC (The frontend). The biggest problem was that the PVR functionality of pause/rewind didn't work, and the author of TVHeadend/XBMC PVR plug-in really doesn't have any interest in developing this as he has no need for it. That causes me a bit of a headache, as that component lets the system down quite a bit. There are also problems with TVHeadend in that it relies on XMLTV to grab TV listings, as the EIT grabber (Over the air/satellite) barely works at all, and again nobody can see to improve that at the moment. So these two items let it down quite a bit as it means we get dodgy entries in our TV Guide, so its not reliable, and we can't pause. Another huge bug, and I believe this is an XBMC problem, is that if a channel doesn't have an EPG entry, or the entry is deemed as 'invalid' by XBMC it actually hides the channel. Now this causes channels to disappear and re-appear due to bad EPG data, this is something I've asked numerous times on the forums on how to correct myself in the code and re-compile but nobody seemed to know the definitive answer, or didn't want to help. This also frustrated me!

So I thought, time to move to MythTV using XBMC as the front-end. Myth does have pause/rewind functions, has EIT working fully and a few other nice things so this seemed a good option to switch to. Installing Myth-backend is a pain as although they have debian/ubuntu packages, you have to guess which ones you need to run the backend (mythtv-backend, mythtv-web and a few others). Once installed onto my headless server that has my 2 FreeSat and 1 Freeview cards in it I needed to set it up. Here was the first problem, its a GUI that you run within an Xserver (a desktop). This is a headless server so I can't do that, so have to run it over an X forwarder to my laptop, so its a bit laggy but works. The mythtv-setup is HORRIBLE! It's clunky, and looks like its been designed to be used from a TV remote, but even that it would be horrible to use, so I didn't have a good experience there. The logic of channels, channel inputs, hardware adapters and channels is also a little confusing. So you first setup your capture card, thats pretty straight forward, the system finds my 3, you have to setup Diseqc and tell it just to use an LNB (even for the Freeview) which is weird as it seems to be a requirement rather than an option. Once they're all setup you have to create a video source. This is straight forward as I want to use EIT, so select that, and thats done. Now the bit that I've still not fully got my head around, Input Connections. You set these to match your channels, so on mine I've created 2, one for FreeSat and one for Freeview (I have no idea if this is right, as it doesn't seem 100% correct!), so I went in, scanned for FreeSat DVB-S first (this takes about 10 minutes), then for DVB-S2 (another 10 minutes). Then create another Input Connection for Freeview, and scan for channels on there (About 10 minutes again). Wow that takes a long time! Now you can go into channel listings. This is mostly pointless, as the interface is so unusable you have about 200 entries in a list and it expects you to click into them one-by-one and disable the ones you don't want, correct incorrect text, etc. I would be on forever with that, so opted to leave that as it is for now and just have a stupid number of channels visible. At this point you can save and exit.

Now you need to compile cmyth with XBMC. cmyth is a fork of XBMC as the developer has re-written some parts of XBMC to handle the Myth setup correctly. So I grabbed his sources and started compiling, hitting many incompatability problems along the way as debian packages were missing, I was running an old version of Ubuntu, so had to upgrade the distribution first. That took longer than I hoped, and if you do a custom compile, don't forget to put your ./configure command options in to make all your add-ons work. My line ended up as './configure --prefix=/usr --enable-vdpau --enable-static --disable-shared --enable-sdl --enable-rtmp' Doing this, a make and install got us up and running and I could see the EPG loading. Going in however I had about 10 BBC1's, 10 BBC2, etc. Basically what it had done is found each copy of the channel on the FreeSat devices, Freeview channels and just put each one into the listing, it didn't merge any! So that meant back to the MythTV listing setup (Also each time you go into myth-setup you have to shutdown the daemon, which disconnects all clients stopping TV from working again!). I tried again but found the channel editor impossible. Luckily I spotted the MythWeb add-on, went into that and found you can edit the channels through the web interface, so I went through removing channels I didnt want. But what to do about the duplicate BBC1? A bit more reading revealed that you should give these the same channel number. So find all the BBC1's in your listing and set them to a channel number (e.g. I used 101 to mirror the Sky numbering system). I've done that and sure enough it looks much better.

The main problems I have now (and not solved):

  • EPG loading time in XBMC is very slow. It can be up to 5 minutes and its not a good idea so start watching TV till this is finished as if you do that it seems to corrupt a lot of EPG entries. This is a pain as we all have to sit watching a loading bar before we watch TV now!
  • After selecting a channel, it takes almost a minute before the channel engages and starts streaming
  • Pressing anything like EPG, pause, etc, also freezes the playback for a few seconds as the box 'thinks'
  • Channels without proper EPG still don't appear in the EPG line-up, so again this is a big problem!

So after all of these, I'm unsure if I should revert back to TVHeadend, as there are a few too many bugs and problems that are frustrating, and I'm not sure if the gains (Pause TV) is enough to warrant it. I may re-do all of it over the weekend back to TVHeadend and see if I can correct the few bugs remaining there myself!