The Beer Fridge saga continues

Since the last update on the beer fridge, we’ve had to do some murdering of the original PCB to get it to fit in a case. In addition, we’d failed to take into account the startup power draw for the compressor (5 amps), and had to upgrade the relay we’re using from a 3 amp solid state relay. Luckily Doug had the beefier relay just sitting around in his shed. That means we’ve lost our opto-isolation because the new relay is a simple mechanical one, but we have a relay coil doing the same thing now. A new PCB will make the world a lot neater, which will be nice. Oh, we’ve also been mentioned on Hack a Day, which has generated some interesting comments on their site.

I’ve observed that sometimes the relay doesn’t turn on, even though the arduino thinks it has done so. An example can be seen here:

(Note that graph is a Google chart server image, generated by a simple visualization program I wrote in python. If you’re that way inclined, the visualization software is in my public SVN repository).

At first we thought this was a software problem with comparing ints to floats, but it continued to happen even after we fixed that bug. You can see in the graph above that during testing the compressor was “turned on” (i.e. the arduino thinks it has activated the relay), but the temperature continued to rise. A visual inspection showed that in fact the relay hadn’t turned the compressor on, and power cycling the controller (and therefore the relay) didn’t help. However, the simple expedient of hitting the relay with a screw driver handle fixed the problem, which made me think that Doug had given me a bodgy relay. This got me thinking about if it would be possible to unstick a relay in software though — its pretty easy to detect this case (temperature continues to rise despite the compressor being). I’m thinking that perhaps if you cycled the inputs over and over you might be able to get the relay to unstick.

Detecting this case in software might be generally useful anyway though… Ignoring stuck relays, it would also tell you when someone had left the door open for example. It would be nice to detect this case and then give up on the compressor for a few minutes before trying again. I wrote a quick implementation of this “door open detection” and gave it a try, but I’ve now given up on it as being too fiddly. I found problems such as when the outside world is heating up rapidly (summer mornings for example), the compressor can’t make a big enough difference to the internal temperature of the fridge, but you still really want the compressor on because something is better than nothing.

In the end it turned out the problem with the relay was that the arduino wasn’t providing enough current to pull the relay contact in reliably. This was fixed with the addition of a transistor to the board, which is yet another thing to add to the next PCB.

One of the big advantages of a microprocessor based custom thermostat is that I can implement new features that wouldn’t normally be present in a fridge. Some of these features might turn out to be bad ideas, but I view the beer fridge as a bit of an experimentation platform.

Since my extra Dallas 1820s have arrived from ebay, I also added a sensor inside the freezer. This is interesting because the freezer isn’t getting anywhere near the FDA recommended temperature (-17.7 Celsius). I tweaked the code yet again to turn on the compressor if either compartment is over temperature, but this resulted in the fridge running much too cold — it was close to freezing point in fact. I think this is probably a hardware problem with the freezer, and given its a beer fridge I’ve just written “this is not the freezer you are looking for” on the door and gone back to only monitoring the temperature of the fridge compartment. Here’s a graph of my experiments with the freezer yesterday:

Another recent software tweak is a start up delay for the compressor in order to reduce the risk of back pressure damage to the compressor. Thanks Murray! This comes with yet another LED which indicates that the compressor is currently disabled.

I think this project is nearly ready to start showing people the code. The code is also getting big enough that cutting and pasting it into blog posts is starting to get annoying. So, here are a few links:

  • The code for the arduino. This should be self explanatory, except for saying that we’re using a nuelectronics.com ethernet shield, not the standard one.
  • The visualization software. You want temperature.py to scrape the web server on the arduino and push stuff into a MySQL database, and then server.py is a simple python web server that provides the UI for visualization. The UI isn’t very good at the moment, but I shall improve it soon.

Beer fridge controller 0.3

Last night Doug made up the first cut of the PCB for the beer fridge controller mentioned in previous posts, and we fitted the arduino to it. There wasn’t much in the way of software changes, apart from changing the pin that the compressor runs on.

You can see here that we’ve mounted both the arduino and the Ethernet shield onto the PCB — this is just temporary until we get the PCB right. The black rectangle at the front right is a 240 volt capable relay, and the thing behind it is a 240 volt transformer which is capable of powering all the electronics on the boards. In the final PCB we wont need the arduino at all — just the Ethernet shield and the atmega 328 from the arduino. However, that didn’t work out this time around because of problems getting the Ethernet socket to fit nicely. Its clearer on this picture of the other side of the board:

See how we had to cut a hole in the PCB for the socket? That took out some of the pin holes for the atmega, and a few tracks. Its not a big problem because we’re going to iterate a little on the PCB design (and by “we”, I mean Doug). You can also see the perspex shield, which covers all the 240 volt rails, which is a nice touch. This version of the hardware is now sitting out on top of the beer fridge, and I wrote some simple scraping and visualization software for the temperature values I am seeing from the embedded hardware. You can see here the temperatures out the back of my house for this afternoon:

As I’ve mentioned before, the hardware and software can handle more than one temperature probe, so the ultimate plan is to take the opportunity to place a bunch of these probes around the house and see what interesting data we end up with.

Beer fridge controller 0.2

Further to yesterday’s post about the beer fridge thermostat replacement, I’ve been hacking on ethernet support for the controller. This is handy because I’d like to log the temperature and compressor state over the network, because I’m hoping that can be used to make calculations about the thermal mass of the contents of the fridge, and therefore derive how much beer is actually in the fridge at any given time.

Because the controller also supports more than one temperature probe, I’ll also add more 1-Wire temperature sensors around the house so I can determine important things like if its hot in the outside world.

The code is currently experiencing some bloat in the binary size, mainly because the ethernet library and the sprintf implementation are quite large. I’ll have to think more about that. Here’s the current code:

    #include <enc28j60.h>
    #include <etherShield.h>
    #include <ip_arp_udp_tcp.h>
    #include <ip_config.h>
    #include <net.h>
    #include <websrv_help_functions.h>
    
    #include <OneWire.h>
    #include <DallasTemperature.h>
    
    // Temperature sensor and compressor setup
    #define COMPRESSOR 9
    #define ONEWIRE 3
    
    #define HIGHTEMP 4
    #define LOWTEMP 3.6
    
    // 220L Kelvinator is 85 watts
    #define COMPRESSOR_WATTAGE 85.0
    
    #define SLEEP_SEC 10
    
    OneWire oneWire(ONEWIRE);
    DallasTemperature sensors(&oneWire);
    
    unsigned long runtime = 0, chilltime = 0, last_checked = 0, this_check = 0;
    uint8_t compressor = LOW;
    
    // Web server setup
    #define MYWWWPORT 80
    #define BUFFER_SIZE 550
    #define ERROR_500 "HTTP/1.0 500 Error\r\nContent-Type: text/html\r\n\r\n<h1>500 Error</h1>"
    
    static uint8_t mymac[6] = {0x54, 0x55, 0x58, 0x10, 0x00, 0x24};
    static uint8_t myip[4] = {192, 168, 1, 253};
    static uint8_t buf[BUFFER_SIZE + 1];
    char data[BUFFER_SIZE + 1];
    
    // The ethernet shield
    EtherShield es = EtherShield();
    
    uint16_t http200ok(void)
    {
      return(es.ES_fill_tcp_data_p(buf, 0, PSTR("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n"
                                                "Pragma: no-cache\r\n\r\n")));
    }
    
    // prepare the webpage by writing the data to the tcp send buffer
    uint16_t print_webpage(uint8_t *buf)
    {
      uint16_t plen;
      plen = http200ok();
      plen = es.ES_fill_tcp_data_p(buf, plen, PSTR("<html><head><title>Temperature sensor</title>"
                                                   "</head><body><pre>"));
      plen = es.ES_fill_tcp_data(buf, plen, data);
      plen = es.ES_fill_tcp_data_p(buf, plen, PSTR("</pre></body></html>"));
    
      return(plen);
    }
    
    // Float support is hard on arduinos
    // (http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1164927646)
    char *ftoa(char *a, double f, int precision)
    {
      long p[] = {0,10,100,1000,10000,100000,1000000,10000000,100000000};
    
      char *ret = a;
      long heiltal = (long)f;
      itoa(heiltal, a, 10);
      while (*a != '\0') a++;
      *a++ = '.';
      long desimal = abs((long)((f - heiltal) * p[precision]));
      itoa(desimal, a, 10);
      return ret;
    }
    
    void setup()   {
      // initialize the digital pin as an output:
      pinMode(COMPRESSOR, OUTPUT);
      Serial.begin(9600);
      sensors.begin();
    
      es.ES_enc28j60Init(mymac);
      es.ES_init_ip_arp_udp_tcp(mymac, myip, MYWWWPORT);
    }
    
    void loop()
    {
      int i, j, data_inset, delta;
      char float_conv[10];
      float t;
      DeviceAddress addr;
      uint16_t plen, dat_p;
    
      // Read temperatures, we dump the state to a buffer so we can serve it
      this_check = millis();
      if(this_check > last_checked + SLEEP_SEC * 1000)
      {
        delta = int((this_check - last_checked) / 1000);
        runtime += delta;
        if(compressor == HIGH) chilltime += delta;
    
        data_inset = 0;
        sensors.requestTemperatures();
        for(i = 0; i < sensors.getDeviceCount(); i++)
        {
          t = sensors.getTempCByIndex(i);
          sensors.getAddress(addr, i);
    
          for (j = 0; j < 8; j++)
          {
            sprintf(data + data_inset, "%02x", addr[j]);
            data_inset += 2;
          }
          sprintf(data + data_inset, ": %s\n", ftoa(float_conv, t, 2));
          data_inset = strlen(data);
        }
    
        // Control compressor
        if(t > HIGHTEMP) compressor = HIGH;
        else if(t < LOWTEMP) compressor = LOW;
        digitalWrite(COMPRESSOR, compressor);
    
        // Status dump
        sprintf(data + data_inset,
                "Compressor: %s\nRuntime: %lu\nChilltime: %lu\n%% chill: %d\nWatt hours: %d\n",
                compressor == HIGH ? "on" : "off", runtime, chilltime,
    	    int(chilltime * 100.0 / runtime),
                int(chilltime * COMPRESSOR_WATTAGE / 3600));
        Serial.println(data);
        last_checked = this_check;
      }
    
      // Handle network packets
      dat_p = es.ES_packetloop_icmp_tcp(buf, es.ES_enc28j60PacketReceive(BUFFER_SIZE, buf));
      if(dat_p != 0)
      {
        if (strncmp("GET ", (char *)&(buf[dat_p]), 4) != 0){
          // head, post and other methods:
          dat_p = http200ok();
          dat_p = es.ES_fill_tcp_data_p(buf, dat_p, PSTR("<h1>200 OK</h1>"));
        }
    
        // just one web page in the "root directory" of the web server
        else if (strncmp("/ ", (char *)&(buf[dat_p+4]), 2) == 0){
          dat_p = print_webpage(buf);
          Serial.println("Served temperature web page");
        }
    
        else{
          dat_p = es.ES_fill_tcp_data_p(buf, 0, PSTR(ERROR_500));
        }
    
        es.ES_www_server_reply(buf, dat_p);
      }
    }