Monday, February 4, 2013

Mum's portfolio

Book Cover
Tote Bag


Slow cook on the cheap using Arduino 温泉蛋

If you search for "diy sous vide" on google, you get a lot of results stating how cheap they managed to get their sous vide setup vs commercial home use version costing a few hundred US dollars at least. Ever since dad saw this proto-typing electronic board called Arduino 1 year ago, he's been saying he would set one up. Finally got round to it last week and cooked onsen eggs with the setup. For details about how to cook eggs to perfection, just google "egg sous vide" for a myriad of detail discussions on the optimal temperature and timing for cooking egg. For dad's setup, he used 64-65C temperature and about 60 minutes, it all depends on the consistency of the egg yolk you want.

Yummy!!!

Onsen egg just cooked with a few drops of prime soy sauce

Arduino with LCD shield with 2 pins connected, a relay controlling on/off of the heating element and a water-proof temperature sensor based on DS18B20

Excluding the stainless steel pot, the total cost of the electronics and heating element is less than US$20 and this isn't even optimised with dedicated PCBs for micro-controllers.
The code can be modified to include cooking time, total energy consumed etc at no extra costs.

If you by chance come to this blog on the back of this, do let us know by leaving a comment:)

Below are the codes for the controls, which are based on 2 main sources of libraries:
LCD4Bit_mod.h to control LCD Keypad shield
http://www.dfrobot.com/wiki/index.php?title=Arduino_LCD_KeyPad_Shield_(SKU:_DFR0009)
Events.h etc from M Romani
http://arduino.cc/forum/index.php/topic,37650.0.html


#include <Events.h>
#include <EventQueue.h>
#include <EventDispatcher.h>

#include <LCD4Bit_mod.h>
#include <stdio.h>

//create object to control an LCD.  
//number of lines in display=1
LCD4Bit_mod lcd = LCD4Bit_mod(2); 

int getCurrentTemp(char *temp);
void convIntString(char *temp, int tempnum);

//Analog values of key presses
int  adc_key_val[5] ={30, 150, 360, 535, 760 };
char temp_string[10];


#define NUM_KEYS    5
#define KEY_RIGHT   0
#define KEY_UP      1
#define KEY_DOWN    2
#define KEY_LEFT    3
#define KEY_SELECT  4

#define TEMP_PIN 3     //DS18B20 data pin

#define RELAY_PIN 11     //relay pin this is the pin next to pin3



int adc_key_in;
int key=-1;
int oldkey=-1;
int temp_target=60;    //target temperature of the sous vide bath
int counter=1;

// the event queue
EventQueue q;

// the event dispatcher
EventDispatcher disp(&q);

// use this analog channel
#define AN_CHAN 0

// generate an event when the analog
// channel value changes this much
// increase value for noisy sources
//#define AN_DELTA 5

// time event handler
void timeHandler(int event, int param) {
  
    int curr_temp;
    curr_temp=getCurrentTemp(temp_string); 
    //call getCurrentTemp will return current temperature both in integer and string
    lcd.cursorTo(2, 0);  
    lcd.printIn(temp_string); 
    
    convIntString(temp_string, temp_target);
    lcd.cursorTo(1,5);
    lcd.printIn(temp_string);
    
    if (curr_temp < temp_target) {
      digitalWrite(RELAY_PIN, HIGH);
      lcd.cursorTo(1, 7);  
      lcd.printIn("HeaterOn "); 
    }
      
    else {
      digitalWrite(RELAY_PIN, LOW);
      lcd.cursorTo(1, 7);  
      lcd.printIn("HeaterOff"); 
    }  
        
}

// analog event handler
void analogkeyHandler(int event, int param) {
  lcd.clear();
  
  switch (param) {
    
    case KEY_SELECT: //Not Implemented
    lcd.cursorTo(1, 0); 
    lcd.printIn("SELECT NA");
    break;

    case KEY_RIGHT: //Not Implemented
    lcd.cursorTo(1, 0);  
    lcd.printIn("Right NA");
    break;
    
    case KEY_UP:        // Increase target temperature by 1C
    lcd.cursorTo(1, 0);  
    lcd.printIn("Up ");
    if (temp_target < 84)
      temp_target = temp_target + 1;
    break;
    
    case KEY_DOWN:      // Decrease target temperature by 1C
    lcd.cursorTo(1, 0);  
    lcd.printIn("Down ");
    if (temp_target > 40)
      temp_target = temp_target - 1;
    break;
    
    case KEY_LEFT: //Not Implemented
    lcd.cursorTo(1, 0);  
    lcd.printIn("Left NA");
    break;

  }
}

// this function generates an EV_TIME event
// each 1000 ms
// If you don't want the relay to jump back and forth too often
// at around the target temperature
// you can change the timing intervals

void timeManager() {
    static unsigned long prevMillis = 0;
    unsigned long currMillis;

    currMillis = millis();
    if (currMillis - prevMillis >= 1000) {
        prevMillis = currMillis;
        q.enqueueEvent(Events::EV_TIME, 0);    // param is not used here
    }
}

// this function generates an EV_ANALOG event
// whenever the analog channel AN_CHAN changes
void analogkeyManager() {

    adc_key_in = analogRead(AN_CHAN);  // read the value from AN_CHAN
    key = get_key(adc_key_in);    // convert into key press

    if (key != oldkey)         // if keypress is detected
    {
      delay(50);  // wait for debounce time
      adc_key_in = analogRead(AN_CHAN);  // read the value from AN_CHAN **AGAIN**
      key = get_key(adc_key_in);          // convert into key press
      if (key != oldkey)    
      {   
        oldkey = key;
        if (key >=0) {
          q.enqueueEvent(Events::EV_ANALOG0, key);    // use param to pass key value to event handler
        }
      }
    }
}

// Convert ADC value to key number
int get_key(unsigned int input)
{
 int k;
    
 for (k = 0; k < NUM_KEYS; k++)
 {
  if (input < adc_key_val[k])
  {
           
    return k;
        }
 }
    
    if (k >= NUM_KEYS)
        k = -1;     // No valid key pressed
    
    return k;
}

void lcdprint_interval (char *minsec, int interval)
{
  char temp_string[10];
  lcd.cursorTo(2, 9);  
  lcd.printIn(minsec);
  convIntString(temp_string, interval);
  lcd.cursorTo(2, 5);  
  lcd.printIn(temp_string);
  Serial.println(interval);

}

// routine for getting temperature
// standard for the OneWire DS18B20

void OneWireReset(int Pin) // reset.  Should improve to act as a presence pulse
{
     digitalWrite(Pin, LOW);
     pinMode(Pin, OUTPUT); // bring low for 500 us
     delayMicroseconds(500);
     pinMode(Pin, INPUT);
     delayMicroseconds(500);
}

void OneWireOutByte(int Pin, byte d) // output byte d (least sig bit first).
{
   byte n;

   for(n=8; n!=0; n--)
   {
      if ((d & 0x01) == 1)  // test least sig bit
      {
         digitalWrite(Pin, LOW);
         pinMode(Pin, OUTPUT);
         delayMicroseconds(5);
         pinMode(Pin, INPUT);
         delayMicroseconds(60);
      }
      else
      {
         digitalWrite(Pin, LOW);
         pinMode(Pin, OUTPUT);
         delayMicroseconds(60);
         pinMode(Pin, INPUT);
      }

      d=d>>1; // now the next bit is in the least sig bit position.
   }
   
}

byte OneWireInByte(int Pin) // read byte, least sig byte first
{
    byte d, n, b;

    for (n=0; n<8; n++)
    {
        digitalWrite(Pin, LOW);
        pinMode(Pin, OUTPUT);
        delayMicroseconds(5);
        pinMode(Pin, INPUT);
        delayMicroseconds(5);
        b = digitalRead(Pin);
        delayMicroseconds(50);
        d = (d >> 1) | (b<<7); // shift d to right and insert b in most sig bit position
    }
    return(d);
}


int getCurrentTemp(char *temp)
{  
  int HighByte, LowByte, TReading, Tc_100, sign, whole, fract;

  OneWireReset(TEMP_PIN);
  OneWireOutByte(TEMP_PIN, 0xcc);
  OneWireOutByte(TEMP_PIN, 0x44); // perform temperature conversion, strong pullup for one sec

  OneWireReset(TEMP_PIN);
  OneWireOutByte(TEMP_PIN, 0xcc);
  OneWireOutByte(TEMP_PIN, 0xbe);

  LowByte = OneWireInByte(TEMP_PIN);
  HighByte = OneWireInByte(TEMP_PIN);
  TReading = (HighByte << 8) + LowByte;
  sign = TReading & 0x8000;  // test most sig bit
  if (sign) // negative
  {
    TReading = (TReading ^ 0xffff) + 1; // 2's comp
  }
  Tc_100 = (6 * TReading) + TReading / 4;    // multiply by (100 * 0.0625) or 6.25

  whole = Tc_100 / 100;  // separate off the whole and fractional portions
  fract = Tc_100 % 100;

/*
 if(sign) temp[0]='-';
 else    temp[0]='+';
 
 temp[1]= whole%100+'0';
 temp[2]= (whole-100*temp[1])%10 +'0' ;
 temp[3]= whole-100*temp[1]-10*temp[2] +'0';
 
 temp[4]='.';
 temp[5]=fract%10 +'0';
 temp[6]=fract-temp[5]*10 +'0';
 
 temp[7] = '\0';
*/

 sprintf(temp, "%c%3d%c%2d", (sign==0)?'+':'-', whole, '.', fract);
        return whole;
 
} 
// End of Getting temperature routine


void convIntString(char *temp, int tempnum)
{  
 sprintf(temp, "%2d", tempnum); 
} 


// program setup
void setup() {
   // Serial.begin(115200); // Serial Port can be used for future debugging
   // initialize DS18B20 datapin
    digitalWrite(TEMP_PIN, LOW);
    pinMode(TEMP_PIN, INPUT);      // sets the digital pin as input (logic 1)
    
    pinMode(RELAY_PIN, OUTPUT);    // sets Relay pin to output
    lcd.init();
  //optionally, now set up our application-specific display settings, 
  //overriding whatever the lcd did in lcd.init()
  //lcd.commandWrite(0x0F);//cursor on, display on, blink on.  (nasty!)
    lcd.clear();

    disp.addEventListener(Events::EV_TIME, timeHandler);
    disp.addEventListener(Events::EV_ANALOG0, analogkeyHandler);
}

// loop
void loop() {
    // call the event generating functions
    timeManager();
    analogkeyManager();

    // get events from the queue and call the
    // registered function(s)
    disp.run();
}
 

Recent Snaps - Sony RX100

Mui Wo

Star ferry TST

ICC


Homemade hand fried arrowroot chips - yummy!
but a lot of work
Tomato soup from ABC Kitchen
How satsumas' peels are dried!! 陳皮新體現

This young man was shocked!!
乾炒牛河 - 何洪記
水瓜烙 - 曾記粿品 next to ABC kitchen
highly recommended
Sashimi from Senryo - a place that you normally associate with conveyor sushi


Paris come to Hong Kong - Eric Kayser and Laduree

Hong Kong must be an attractive place for foreign brands to do business. You don't need to look far to see people queueing up to go into flagship stores of Chanel, Louis Vuitton etc. You also get famous patisseries and bakeries from Paris setting up shops in Hong Kong. Maison du Chocolat, Jean Paul Hevin have been here for quite some time. Now we have two additions in TST.

Laduree - the name synonymous to macarons, has opened a shop in Harbour City in TST. Modelled in the exact same fashion as their Paris shops, the shop sits directly opposite a Prince Jewellery and Watches store. At 30 dollars a pop, you get to compare macarons made with 150 years of tradition vs all the pretenders to the crown.



Eric Kayser - a famous bakery chain had its grand opening in Jan, also in Harbour City.










Eaten fresh, Eric Kayser's bread are soft and tasty. Try its olive bread if you get a chance. Lunch menu is good value. The savoury dishes are cooked to a high standard and you get a basket of its bread selection. The optional desserts are great value for HK$20 additional but guess they can be better.

We wait for its Happy Valley branch to open in April, then we might get to try the crumbs!!

四姐川菜 Sijie Sichuan Restaurant

Not the most authentic Sichuan restaurant around as we have been reliably told but then it's like saying most of the Indian curries in London are not authentic........

Simple noodle mixed with Sichuan spicy sauce - bad temptation for a no-carb diet

Tasty, fatty, unhealthy pork in a mild sauce

Salt n pepper shrimps - 椒鹽蝦

Sea of red - that you will associate with Sichuan food - 水煮牛肉 (Boiled beef in Szechuan sauce)

Another plateful of red - 辣子雞 (Sichuan spicy diced chicken)


This restaurant seems to be always full in weekends, so do book in advance. The dishes are generally well executed with clean taste and not overly spicy. The new location near Times Square is definitely way better than when it was still in the old shop in Wanchai. Think it's still a BYOB restaurant.

Taken just before the restaurant moved to the new location.

四姐川菜
Sijie Sichuan
10/F, Bartlock Centre,
3 Yiu Wa Street, Causeway Bay,HK
地址:香港銅鑼灣耀華街3號百樂中心10樓
Tel: 28022250