cheap avr intervalometer

now that i have a digital camera (but my heart will always belong to film-based photography) i can do time lapse photography. sadly the firmware of my camera isn’t supporting this feature, so i had to build an intervalometer. i didn’t want to use an arduino (overkill & pricy), so i went with an attiny45, a generic optocoupler, a voltage regulator and a potentiometer for adjusting the timer from 1 second to 1 minute.

i just found out that it’s possible to power the avr from the camera (focus ring). have a look at this complete and small solution: http://www.doc-diy.net/photo/hdr-jack/

//Intervalometer from 1 second to 1 minute
 
//Author: Patrick Sebastien Coulombe
//Website: www.workinprogress.ca
//Date: 2010-07-24
 
#define F_CPU 8000000
#include <avr/io.h>
#include <util/delay.h>
 
// use PB2 for led, pin 7
#define LED_BIT 2
// select ADC2, PB4, pin 3
#define CHANNEL 2
// shutter on (in ms)
#define HOLD 300 
 
// Return the 10bit value of the selected adc channel.
uint16_t get_adc(uint8_t channel) {
 
	// ADC setup
	ADCSRA = (1 << ADEN) | (1 << ADPS1) | (1 << ADPS0);
 
	// select channel
	ADMUX = channel;
 
	// warm up the ADC, discard the first conversion
	ADCSRA |= (1 << ADSC);
	while (ADCSRA & (1 << ADSC)); 
 
	ADCSRA |= (1 << ADSC); // start single conversion
	while (ADCSRA & (1 << ADSC)); // wait until conversion is done
 
	return ADCW;
}
 
// Scale
long map(long x, long in_min, long in_max, long out_min, long out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
 
// Main program
int main(void) {
 
	// vars
	uint16_t adcvalue = 0;
	uint16_t i;
 
	// define LED as outputs
	DDRB |= (1 << LED_BIT);	
 
	while (1) {	
 
		//release the shutter
		PORTB |= (1 << LED_BIT);
 
		//exposure length
		for (i=0; i<HOLD; i++) { 
			_delay_ms(1); 
		}
		PORTB &= ~(1 << LED_BIT);
 
		//interval time (using a potentiometer to adjust)
		adcvalue = map(get_adc(CHANNEL), 0, 1023, 1, 60);
		adcvalue = adcvalue * 1000;
 
		//one way to achieve long delay
		for (i=0; i<adcvalue; i++) { 
			_delay_ms(1); 
		}
 
	}
	return 0;
 
}

Time lapse test (3 hours) from my balcony.

I used Blender for making the tilt / shift effect. Here’s the source of the Blender file.

24

07 2010

singing birds

10 watts amplifier with built-in 16-bit / 48kHz RIFF-WAVE player (music on a SD-CARD). 2 servos for controlling the beaks. I did this project mostly to learn stuff. The result is a bit stupid.

VIDEO:

PHOTOS:

Schematic & Board (Kicad)
How to filter PWM for music

sd player source code

/*---------------------------------------------------------------*/
/* 8-pin SD audio player R0.02                     (C)ChaN, 2009 */
/*---------------------------------------------------------------*/

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "diskio.h"
#include "pff.h"

char strbuff[10];
unsigned int position = 0;
unsigned long count = 0;
unsigned int nbwave = 0;

#define FCC(c1,c2,c3,c4)  (((DWORD)c4<<24)+((DWORD)c3<<16)+((WORD)c2<<8)+(BYTE)c1)  /* FourCC */

/*---------------------------------------------------------*/
/* Work Area                                               */
/*---------------------------------------------------------*/

volatile BYTE FifoRi, FifoWi, FifoCt;  /* FIFO controls */

BYTE Buff[256];    /* Wave output FIFO */

FATFS fs;      /* File system object */
DIR dir;      /* Directory object */
FILINFO fno;    /* File information */

WORD rb;      /* Return value. Put this here to avoid bugs of avr-gcc */

EMPTY_INTERRUPT(WDT_vect);

/*---------------------------------------------------------*/

static
DWORD load_header (void)  /* 0:Invalid format, 1:I/O error, >1:Number of samples */
{
  DWORD fcc, sz;
  UINT i;
  FRESULT res;

  res = pf_read(Buff, 256, &rb);    /* Load file header (256 bytes) */
  if (res) return 1;

  if (rb != 256 || LD_DWORD(Buff+8) != FCC('W','A','V','E')) return 0;

  i = 12;
  while (i < 200) {
    fcc = LD_DWORD(&Buff[i]);  /* FCC */
    sz = LD_DWORD(&Buff[i+4]);  /* Chunk size */
    i += 8;
    switch (fcc) {

    case FCC('f','m','t',' ') :    /* 'fmt ' chunk */
      if (sz > 100 || sz < 16)        /* Check chunk size */
        return 0;
      if (Buff[i+0] != 1)            /* Check coding type (1) */
        return 0;
      if (Buff[i+2] != 1 && Buff[i+2] != 2)  /* Check channels (1/2) */
        return 0;
      GPIOR0 = Buff[i+2];    /* Channel flag */
      if (Buff[i+14] != 8 && Buff[i+14] != 16)  /* Check resolution (8/16) */
        return 0;
      GPIOR0 |= Buff[i+14];  /* Resolution flag */
      OCR0A = (BYTE)(F_CPU / 8 / LD_WORD(&Buff[i+4])) - 1;  /* Sampling freq */
      break;

    case FCC('f','a','c','t') :    /* 'fact' chunk (skip) */
      break;

    case FCC('d','a','t','a') :    /* 'data' chunk (start to play) */
      fs.fptr = i;
      return sz;

    default :            /* Unknown chunk (error) */
      return 0;
    }
    i += sz;
  }

  return 0;
}

static
UINT play (
  const char *fn
)
{
  DWORD sz;
  FRESULT res;
  BYTE sw;
  WORD btr;

  if ((res = pf_open(fn)) == FR_OK) {
    sz = load_header();      /* Load file header */
    if (sz < 256) return (UINT)sz;

    if (!TCCR1) {        /* Enable audio out if not enabled */
      PLLCSR = 0b00000110;  /* Select PLL clock for TC1.ck */
      GTCCR =  0b01100000;  /* Enable TC1.OCB as PWM out (L-ch) */
      OCR1B = 128; OCR1A = 128;
      TCCR1 = STEREO ? 0b01100001 : 0b00000001;  /* Start TC1 with TC1.OCA is enabled as PWM out (R-ch) */
      TCCR0A = 0b00000010;  /* Enable TC0.ck = 2MHz as interval timer */
      TCCR0B = 0b00000010;
      TIMSK = _BV(OCIE0A);
    }

    FifoCt = 0; FifoRi = 0; FifoWi = 0;    /* Reset FIFO */
    pf_read(0, 512 - fs.fptr, &rb);      /* Snip sector unaligned part */
    sz -= rb;

    sw = 1;    /* Button status flag */
    do {
      /* Forward audio data */
      btr = (sz > 1024) ? 1024 : (WORD)sz;
      res = pf_read(0, btr, &rb);
      if (res != FR_OK || btr != rb) break;
      sz -= rb;
      /* Check button down and break on button down */
      sw <<= 1;
      //if (bit_is_clear(PINB, 0) && ++sw == 1) break;
      if (bit_is_clear(PINB, 0) && ++sw == 1) {
        
        count = 0;
        
        do { 
          count++;
          wdt_reset();
        } while (bit_is_clear(PINB, 0));
        
        if(count > 500000) {
          position--;
          position--;
        }
        
        break;
        
      }
      wdt_reset();
    } while (rb == 1024);  /* Repeat until all data read */
  }

  while (FifoCt) ;      /* Wait for FIFO empty */
  OCR1A = 128; OCR1B = 128;

  return res;
}

long smallrandom(long howbig)
{
  if (howbig == 0) {
    return 0;
  }
  return random() % howbig;
}

long rangerandom(long howsmall, long howbig)
{
  if (howsmall >= howbig) {
    return howsmall;
  }
  long diff = howbig - howsmall;
  return smallrandom(diff) + howsmall;
}

int main (void)
{
  PORTB = 0b111011;    /* Initialize port: - - H H H L H P */
  DDRB  = 0b111110;
  
  sei();
  
  //RANDOM (SEED)
  unsigned long seednumber = 1;
  unsigned char i;
  ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (0 << ADPS0);  //ADC Prescalar set to 64 - 125kHz@8MHz
  ADMUX |= (1 << ADLAR);
  ADCSRA |= (1 << ADEN);  // Enable ADC
    for(i = 0; i < 100; i++) {
    wdt_reset();
    ADCSRA |= (1 << ADSC);  // Start A2D Conversions
    while (ADCSRA & (1 << ADSC)); 
    seednumber = seednumber + ADCH;
  }
  ADCSRA |= (0 << ADATE);
  ADCSRA |= (0 << ADEN);  // Disable ADC
    ADCSRA |= (0 << ADSC);  // Stop A2D Conversions
  
  MCUSR = 0;
  WDTCR = _BV(WDE) | 0b110;  /* Enable WDT reset in timeout of 1s */

  BYTE res;
  //mount
  do {
    res = pf_mount(&fs);
    wdt_reset();
    seednumber++;
  } while (res != FR_OK);
  
  Buff[0] = 0;
  do {
    res = pf_opendir(&dir, (char*)Buff);
    wdt_reset();
    seednumber++;
  } while (res != FR_OK);
  
  //nb files
  for (;;) {
    res = pf_readdir(&dir, &fno);
    if (res != FR_OK || fno.fname[0] == 0) break;
    wdt_reset();
    nbwave++;
    seednumber++;
  }
  
  srandom(seednumber);
  //start with a random position
  position = rangerandom(1, nbwave + 1);

  for (;;) {
    
    //limit
    if(position > (nbwave + 1)) {
      position = 1;
    } else if(position < 1) {
      position = 1;
    }
    
    //play
    sprintf(strbuff,"%d.wav", position);
    play(strbuff);
    
    //increment song
    position++;

  }
}

servo source code

#include <avr/io.h>
#include <avr/interrupt.h>
#include <inttypes.h>
#include <avr/sleep.h>
#include <util/delay.h> 

#define F_CPU 8000000
#define nop()  __asm__ __volatile__("nop")

void wait100us(void) {
    unsigned char i;
    for(i = 0; i < 100; i++) {
      nop();
    }
}

void Delay_ms(unsigned int t)

  {
   unsigned int i, aika;
   aika = 140;

   while(t--)
    for(i = 0; i < aika; i++)

       if(t==0) return;

  } 

void adcinit (void) {

      // PRESCALER 128
      ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (0 << ADPS0);

      // AUTO TRIGGER ENABLE
      ADCSRA |= (1 << ADATE);

      // VCC ref
      ADMUX |= (0 << REFS1) | (0 << REFS0);

      // RIGHT ADJUST
      ADCSRB |= (0 << ADLAR);

      // CHANNEL 0
      ADMUX |= (0 << MUX5) | (0 << MUX4) | (0 << MUX3) | (0 << MUX2) | (0 << MUX1) | (1 << MUX0);

      // FREE RUNNING MODE
      ADCSRB |= (0 << ADTS2) | (0 << ADTS1) | (0 << ADTS0);

      // ENABLE ADC
      ADCSRA |= (1 << ADEN);

    // PORT A: INPUT
    DDRA = 0x00;
    PORTA = 0x00;

}

void pwminit (void) {
    // disable PWM while configuring it
    TCCR1A = 0;

    // used for TOP, makes for 50 hz PWM = (8,000,000 / (8 * 50)) - 1
    ICR1 = 19999;

    // servo pins outputs
    DDRA = _BV(PA5) | _BV(PA6);

    // set up counter 1 (16 bit) to act as a dual channel PWM generator
    // comparator mode = non-inverting, use ICR1 as TOP and have a prescale of 8.
    TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM11);
    TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS11);
}

long map(long x, long in_min, long in_max, long out_min, long out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

int main() {

  adcinit();
  pwminit();
    sei();

    // START A2D
    ADCSRA |= (1 << ADSC);

  while(1) {
    
    //700 to 2400
    //but here we need only 50 degress
    OCR1A = map(ADC, 0, 1023, 700, 1100);
    ADMUX |= (1 << MUX1);
    ADMUX &= ~(1 << MUX0);
    Delay_ms(2);
    OCR1B = map(ADC, 0, 1023, 700, 1100);
    ADMUX |= (1 << MUX0);
    ADMUX &= ~(1 << MUX1);
    Delay_ms(2);
  }
    return(0);
}

13

04 2010

kohana 3.2 tutorial

Below you will find an article / tutorial on Kohana 3.2 – An elegant HMVC PHP5 framework that provides a rich set of components for building web applications.

I am sharing a site template that use authentication & internationalization. You can view the website (not much to see) and download it from github. Any pull request will be more than welcome!

Download via Github:
https://github.com/patricksebastien/kohana-3.2-example


Topics



Install

Using GIT:
http://kohanaframework.org/3.2/guide/kohana/tutorials/git
or
Download:
http://kohanaframework.org/download


Structure of folders:
www/yoursite/site/index.php & .htaccess -> and your assets (css, images, js)
www/yoursite/application -> the very core of your site
www/yoursite/module & system -> core of kohana
or:
www/kohana/version/system & module -> multiple site using kohana
or:
everything in www/yoursite/site (application, module, system, index.php, .htaccess, …)


Test:
127.0.0.1/
If it’s greenish, then remove install.php


index.php
Reflect this structure in www/yoursite/site/index.php:

1
2
3
$application = '../application';
$modules = '../modules';
$system = '../system';

bootstrap.php
Modify application/bootstrap.php

1
date_default_timezone_set('America/Montreal');

Set PRODUCTION vs DEVELOPMENT

1
2
3
4
5
if (isset($_SERVER['KOHANA_ENV'])) {
    Kohana::$environment = constant('Kohana::'.strtoupper($_SERVER['KOHANA_ENV']));
} else {
    Kohana::$environment = ($_SERVER['REMOTE_ADDR'] == '127.0.0.1' ? Kohana::DEVELOPMENT : Kohana::PRODUCTION);
}

Initialize Kohana

1
2
3
4
5
'base_url'   => '/', // or for example: /yoursite/site/mykoapp
'index_file' => FALSE, // SEO (avoid index.php/mycontroller/action)
'profile' => (Kohana::$environment !== Kohana::PRODUCTION), //see how good you are
'caching' => (Kohana::$environment === Kohana::PRODUCTION),
'errors' => TRUE, //for custom 404, 500 FALSE for internal error handling

Enable modules (for example):

1
2
3
'auth'       => MODPATH.'auth',       // Basic authentication
'database'   => MODPATH.'database',   // Database access
'orm'        => MODPATH.'orm',        // Object Relationship Mapping

Many modules are available:
https://github.com/kolanos/kohana-universe
http://kohana-modules.com/

Set the routes (default controller will be login.php in this example)
http://kohanaframework.org/3.2/guide/kohana/routing

The order of your routes are important!

1
2
3
4
5
Route::set('default', '(<controller>(/<action>(/<id>)))')
    ->defaults(array(
        'controller' => 'login', // application/classes/controller/login.php
        'action'     => 'index',
    ));

.htaccess
Add this line at the very top of .htaccess (protect from sniffing directory)

1
Options All -Indexes -Multiviews

and modify (/ or for example: /yoursite/site/mykoapp)

1
2
# Installation directory
RewriteBase /

 
Template
http://kerkness.ca/kowiki/doku.php?id=template-site:create_the_template

Create the template controller (classes/controller/template/website.php) extending Controller_Template

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php defined('SYSPATH') or die('No direct script access.');
class Controller_Template_Website extends Controller_Template
{
    public $template = 'template/website';

    /**
     * The before() method is called before your controller action.
     * In our template controller we override this method so that we can
     * set up default values. These variables are then available to our
     * controllers if they need to be modified.
     */

    public function before() {
        parent::before();
        if ($this->auto_render) {
            // Initialize empty values
            $this->template->title   = '';
            $this->template->content = '';
            $this->template->styles = array();
            $this->template->scripts = array();
        }
    }
     
    /**
     * The after() method is called after your controller action.
     * In our template controller we override this method so that we can
     * make any last minute modifications to the template before anything
     * is rendered.
     */

    public function after() {
        if ($this->auto_render) {
            $styles = array(
            'assets/css/website.css' => 'screen, projection',
            );
            $scripts = array(
            'http://code.jquery.com/jquery.min.js',
            );
            $this->template->styles = array_merge( $this->template->styles, $styles );
            $this->template->scripts = array_merge( $this->template->scripts, $scripts );
        }
        parent::after();
    }
}

Create the controller (classes/controller/login.php) extending Controller_Template_Website (look at the second action for an example on how to use another template per action):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Controller_Login extends Controller_Template_Website {

    public function action_index()
    {
        $this->template->title = 'Log in';
        $this->template->content = View::factory('login'); // application/views/login.php
    }

        // this action is using another template but using the same Controller_Template_Website
    public function action_showinfooverlay()
    {
        $this->template = 'template/overlay';
        parent::before();
        $this->template->title = 'Log in';
        $this->template->content = View::factory('login'); // application/views/login.php
    }
}

Create the html template (view/template/website.php)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php echo substr(I18n::$lang, 0, 2); ?>" lang="<?php echo substr(I18n::$lang, 0, 2); ?>">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name="language" content="<?php echo I18n::$lang ?>" />
<title><?php echo $title ?></title>
<?php foreach ($styles as $file => $type) echo HTML::style($file, array('media' => $type)), PHP_EOL ?>
<?php foreach ($scripts as $file) echo HTML::script($file), PHP_EOL ?>
</head>
<body>
<div id="wrapper">
<?php echo $content ?>
</div>
</body>
</html>

Finally create your view content application/views/login.php

1
2
3
4
5
6
7
8
9
<?php echo Form::open(); ?>
<dl>
    <dt><?php echo Form::label('username', 'User') ?></dt>
    <dd><?php echo Form::input('username') ?></dd>
    <dt><?php echo Form::label('password', 'Pwd') ?></dt>
    <dd><?php echo Form::password('password') ?></dd>
</dl>
<p><?php echo Form::submit(NULL, 'Log in'); ?></p>
<?php echo Form::close(); ?>

At this point you can point your browser to see the login page:
http://localhost/ -> depending on base_url and .htaccess (could be in a sub-folder)
http://localhost/login -> not defined login as the default controller in bootstrap.php

Someone on #kohana (irc / freenode) made a suggestion of using view classes instead of template controller. Here’s two solutions: https://github.com/zombor/kostache & https://github.com/beautiful/view

 
Configure
Database
Copy modules/database/config/database.php to application/config/database.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'default' => array
(
    'type'       => 'mysql',
    'connection' => array(
        'hostname'   => 'localhost',
        'database'   => 'yourdb',
        'username'   => 'user',
        'password'   => 'pwd',
        'persistent' => FALSE,
    ),
    'table_prefix' => '',
    'charset'      => 'utf8',
    'caching'      => FALSE,
    'profiling'    => FALSE, // if you use profiling turn this on (to see querys)
),

Cookie
in application/bootstrap.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * Cookie
 */

// Set the magic salt to add to a cookie
Cookie::$salt = 'fjsdijeihrewhbfsugfuyegwufewgwb';
// Set the number of seconds before a cookie expires
Cookie::$expiration = DATE::WEEK; // by default until the browser close
// Restrict the path that the cookie is available to
//Cookie::$path = '/';
// Restrict the domain that the cookie is available to
//Cookie::$domain = 'www.mydomain.com';
// Only transmit cookies over secure connections
//Cookie::$secure = TRUE;
// Only transmit cookies over HTTP, disabling Javascript access
//Cookie::$httponly = TRUE;

Session (stored in database)
http://kohanaframework.org/3.2/guide/kohana/sessions
in application/bootstrap.php add the default session handler:

1
Session::$default = 'database';

Copy system/config/encrypt.php to application/config/encrypt.php

1
2
3
4
5
6
7
8
9
return array(

    'default' => array(
        'key'   => 'fjdsjkfdskjfurew',
        'cipher' => MCRYPT_RIJNDAEL_128,
        'mode'   => MCRYPT_MODE_NOFB,
    ),

);

Create a table if you want to use database session

1
2
3
4
5
6
7
CREATE TABLE  `sessions` (
            `session_id` VARCHAR(24) NOT NULL,
            `last_active` INT UNSIGNED NOT NULL,
            `contents` TEXT NOT NULL,
        PRIMARY KEY (`session_id`),
        INDEX (`last_active`)
        ) ENGINE = MYISAM;

Copy system/config/session.php to application/config/session.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
return array(
    'database' => array(
        'name' => 'session',
        'encrypted' => TRUE, // need a key in config/encrypt.php
        'lifetime' => DATE::HOUR, // 0 = expire when the browser close
        'group' => 'default',
        'table' => 'sessions',
        'columns' => array(
            'session_id'  => 'session_id',
            'last_active' => 'last_active',
            'contents'    => 'contents'
        ),
        'gc' => 500,
    ),
);

Use it in your controller:

1
2
Session::instance()->set('key', 'value');
Session::instance()->get('key');

yoursite
Create a file in application/config/yoursite.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php defined('SYSPATH') or die('No direct access allowed.');
return array(
    'myconfig1' => 'fjdsjkfdskjfurew',
       'playlists' => array
    (
        1478363 => 10171,
        22857234 => 10171,
        386 => 10171,
        722 => 10171,
        893 => 10171,
        237 => 10171,
    ),
   
);

Then you can call a config like this:

1
2
$playlists = Kohana::$config->load('yoursite.playlists');
Kohana::$config->load('yoursite.myconfig1 ');

Message
(might be better to use I18N directly)

Create a file in application/message/yoursite.php for you project

1
2
3
4
5
<?php defined('SYSPATH') or die('No direct script access.');
return array(
    'permission'         => 'You don\'t have the permission',
    'wrong'         => 'Wrong username or password',
);

Then you can use it like this:

1
Kohana::message('yoursite', 'permission');

Translation
http://blog.mixu.net/2010/11/11/kohana-3-i18n-tutorial/

1
<?php echo __('Dear :firstname, your username is: :user', array(':firstname' => 'gfdgfdg', ':user' => 'gfdgfd')); ?>

 
Validation
http://kohanaframework.org/3.2/guide/kohana/security/validation
Copy system/messages/validation to application/message/validation.php if you want to change the error message

1
2
3
4
5
6
7
8
9
10
11
12
13
// Validate a form ($_POST)
if (isset($_POST) && Valid::not_empty($_POST)) {       
    // Validate the login form
    $post = Validation::factory($_POST)
    ->rule('username', 'not_empty')
    ->rule('username', 'regex', array(':value', '/^[a-z_.]++$/iD'))
    ->rule('password', 'not_empty')
    ->rule('password', 'min_length', array(':value', 3));
           
    // If the form is valid and the username and password matches
    if ($post->check()) {
        echo 'Validated';
    }

Using a callback for custom validation & error message

1
2
3
4
5
6
7
8
9
// form post handling
if (isset($_POST) && Valid::not_empty($_POST)) {           
    // validate
    $post = Validation::factory($_POST)
    ->rule('username', 'alpha_numeric')
    ->rule('password', array($this, 'pwdneusr'), array(':validation', ':field', 'username'));              
    if ($post->check()) {
        }
}
1
2
3
4
5
6
7
8
9
// CALLBACK
// validation rule: password != username
public function pwdneusr($validation, $password, $username)
{
    if ($validation[$password] === $validation[$username])
    {
        $validation->error($password, 'pwdneusr');
    }
}

 
Error page

http://kohanaframework.org/3.2/guide/kohana/tutorials/error-pages

Create the views
views/error/404.php / 500.php etc…

Extend the exception handler of Kohana
classes/kohana/exception.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php defined('SYSPATH') or die('No direct script access.');
class Kohana_Exception extends Kohana_Kohana_Exception {
    public static function handler(Exception $e)
    {
        if (Kohana::DEVELOPMENT === Kohana::$environment)
        {
            parent::handler($e);
        }
        else
        {
            try
            {
                Kohana::$log->add(Log::ERROR, parent::text($e));
 
                $attributes = array
                (
                    'controller' => 'error',
                    'action'  => 500,
                    'message' => rawurlencode($e->getMessage())
                );
 
                if ($e instanceof HTTP_Exception)
                {
                    $attributes['action'] = $e->getCode();
                }
 
                // Error sub-request.
                echo Request::factory(Route::get('error')->uri($attributes))
                ->execute()
                ->send_headers()
                ->body();
            }
            catch (Exception $e)
            {
                // Clean the output buffer if one exists
                ob_get_level() and ob_clean();
 
                // Display the exception text
                echo parent::text($e);
 
                // Exit with an error status
                exit(1);
            }
        }
    }
}

Create the controller:
classes/controller/error.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php defined('SYSPATH') or die('No direct script access.');
class Controller_Error extends Controller_Template_Nobrand {
    public function before()
    {
        parent::before();
        // Internal request only!
        if (Request::$initial !== Request::$current) {
            if ($message = rawurldecode($this->request->param('message'))) {
                $this->template->message = $message;
            }
        } else {
            $this->request->action(404);
        }
        $this->response->status((int) $this->request->action());
    }
    public function action_404()
    {
        $this->template->title = '404 Not Found';
        $this->template->content = View::factory('error/404' );
    }
    public function action_500()
    {
        $this->template->title = 'Internal Server Error';
        $this->template->content = View::factory('error/500' );
    }
    public function action_503()
    {
        $this->template->title = 'Maintenance Mode';
        $this->template->content = View::factory('error/503' );
    }
}

Edit application/bootstrap.php to add the route:

1
2
3
4
Route::set('error', 'error/<action>(/<message>)', array('action' => '[0-9]++', 'message' => '.+'))
    ->defaults(array(
        'controller' => 'error'
    ));

 
Authentication
Copy modules/auth/config/auth.php to application/config/auth.php

1
2
3
4
5
6
7
return array(
    'driver'       => 'orm',
    'hash_method'  => 'sha256',
    'hash_key'     => 'wigbble',
    'lifetime'     => Date::HOUR * 2,
    'session_key'  => 'auth_user',
);

Schema for mysql / postgresql located:
modules/orm/auth-schema-mysql.sql

Change the rules if you don’t want to required an email (you will also need to remove the index in mysql: uniq_email – BTREE)
Copy modules/orm/classes/model/auth/user.php to application/classes/model/auth/user.php and change the public function rules() to your needs

It’s a good idea to add a new role for your normal user, that way you can list them easily:

1
$p = ORM::factory('role', array('name' => 'participant'))->users->find_all();

Create application/messages/models/user.php

1
2
3
4
5
<?php defined('SYSPATH') or die('No direct script access.');
return array(
    'username.unique'         => 'Username must be unique',
    'email.unique'         => 'Email must be unique',
);

If you want to use the remember feature:

1
2
$remember = isset($post['remember']);
Auth::instance()->login($post['username'], $post['password'], $remember)

then you need to be sure to have a cookie salt in application/bootstrap.php

1
2
3
4
/**
* Cookie salt for remember user info
*/

Cookie::$salt = 'fdsh-tretgd-re-gfds-gt-erg-fdg-';

Some useful stuff:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Add an administrator (in a temporary controller)
$model = ORM::factory('user');
$model->values(array(
    'username' => 'admin',
    'password' => 'admin',
    'password_confirm' => 'admin',
        'email' => 'your@email.com',
));
$model->save();
// remember to add the login role AND the admin role
// add a role; add() executes the query immediately
$model->add('roles', ORM::factory('role')->where('name', '=', 'login')->find());
$model->add('roles', ORM::factory('role')->where('name', '=', 'admin')->find());
1
2
3
4
5
6
7
8
9
// If this user doesn't have the admin role, and is not trying to login, redirect to login
public function before()
{
  parent::before();
  if ( ! Auth::instance()->logged_in('admin') AND Request::current()->uri() !== 'manage')
  {
    $this->request->redirect('/manage');
  }
}
1
2
3
4
// Administrator already logged in, redirect to dashboard
if (Auth::instance()->logged_in('admin')) {
    $this->request->redirect('manage/dashboard');
}
1
2
// Log the user
Auth::instance()->login($post['username'], $post['password'], FALSE)
1
2
// Check if the user have the admin permission
if(!Auth::instance()->logged_in('admin')) {
1
2
// Log user out
Auth::instance()->logout();
1
2
3
4
// check if email or username (automagic) is already taken
if(ORM::factory('user')->unique_key_exists($_POST['username'])) {
       echo "FOUND";
}

 
ORM
http://kohanaframework.org/3.2/guide/orm/
http://karlsheen.com/kohana/kohana-3-orm-tutorial-and-samples/
http://kohanaframework.blogspot.com/2010/12/kohana-3-orm-simple-example.html
http://www.geekgumbo.com/2011/05/24/kohana-3-orm-a-working-example/
http://kohanaframework.org/3.2/guide/api/ORM

ORM is included with the Kohana 3.x install but needs to be enabled before you can use it. In your application/bootstrap.php file modify the call to Kohana::modules and include the ORM modules:

1
'orm' => MODPATH.'orm',

The table name must be in plural;
The table must have an id with auto increment (required);
You must create a Model that extends ORM class (this one not in plural)

1)
Create a table with a “s” as the end:

tracking -> trackings
category -> categories

2)
Create a model (application/classes/model) without the “s”

tracking.php
category.php

1
2
3
4
5
<?php defined('SYSPATH') or die('No direct access allowed.');
class Model_Tracking extends ORM
{
    ...
}
1
2
3
4
5
<?php defined('SYSPATH') or die('No direct access allowed.');
class Model_Category extends ORM
{
    ...
}

3)
Establish your relation (one-to-one, one-to-many etc…)

4)
Use your model / ORM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
try
{
    $tracking = ORM::factory('tracking');
    $tracking->user_id = Auth::instance()->get_user()->id;
    $tracking->session_id = Session::instance()->id();
    $tracking->title = ' fdsfdsf ';
    if($tracking->save()) {
        echo "save";
    } else {
        echo "for some reason, there's an error";
    }
  }
catch (ORM_Validation_Exception $e) {
    echo "error";
    var_dump($e->errors());
}

Some useful stuff:

1
2
3
$user = ORM::factory('user');
echo $user->count_all();
echo $user->last_query();
1
2
3
4
$playlists = ORM::factory('playlist')->where('week_id', '=', 1)->find_all();
foreach ($playlists as $playlist) {
    echo $playlist->url;
}

Last ID from ->save();

1
2
$myormmodel->save();
echo $myormmodel->id();

ORM Validation
http://kohanaframework.org/3.2/guide/orm/examples/validation
http://kohanaframework.org/3.2/guide/kohana/security/validation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php defined('SYSPATH') or die('No direct access allowed.');
class Model_Member extends ORM {
    public function rules()
    {
        return array(
            'username' => array(
                array('not_empty'),
                array('min_length', array(':value', 4)),
                array('max_length', array(':value', 32)),
                array(array($this, 'username_available')),
            ),
            'password' => array(
                array('not_empty'),
            ),
        );
    }
    public function filters()
    {
        return array(
            'password' => array(
                array(array($this, 'hash_password')),
            ),
        );
    }
    public function username_available($username)
    {
        // There are simpler ways to do this, but I will use ORM for the sake of the example
        return ORM::factory('member', array('username' => $username))->loaded();
    }
 
    public function hash_password($password)
    {
        // Do something to hash the password
    }
}

 
Database:
http://kohanaframework.org/3.2/guide/database/
http://kohanaframework.org/3.2/guide/api/Database

There’s 2 ways to query a database: prepared (normal SQL) and query builder (dynamic)

Prepared:

1
2
3
4
5
6
$query = DB::query(Database::SELECT, 'SELECT * FROM users WHERE username = :user AND status = :status');
 
$query->parameters(array(
    ':user' => 'john',
    ':status' => 'active',
));

Query builder:

1
2
3
4
$query = DB::select()->from('users')->where('username', '=', 'john');
$query = DB::select('username')->distinct(TRUE)->from('posts');
$query = DB::select()->from(`posts`)->limit(10)->offset(30);

Results:

1
2
3
4
5
6
$results = DB::select()->from('users')->where('verified', '=', 0)->execute();
foreach($results as $user)
{
    // Send reminder email to $user['email']
    echo $user['email']." needs to verify his/her account\n";
}
1
2
3
4
5
6
$results = DB::select()->from('users')->where('verified', '=', 0)->as_object()->execute();
foreach($results as $user)
{
    // Send reminder email to $user->email
    echo $user->email." needs to verify his/her account\n";
}

Only get 1 result:

1
$total_users = DB::select(array('COUNT("username")', 'total_users'))->from('users')->execute()->get('total_users', 0);
1
2
3
// Get the total number of records in the "users" table
$db = Database::instance();
$count = $db->count_records('testi');

 
3rd party libraries
The convention is to place 3rd party files in application/vendor. For instance, if you had an installation of Doctrine, you would place it in application/vendor/doctrine.

1
2
require Kohana::find_file('vendor', 'Swift-4.0.5/lib/swift_required');
$transport = Swift_SmtpTransport::newInstance(...); // This is autoloaded for me by Swiftmailer

PHPExcel – create PDF, CSV, Excel:
application/vendor/phpexcel/PHPExcel.php & PHPExcel
then in your controller:

1
2
require Kohana::find_file('vendor', 'phpexcel/PHPExcel');
$objPHPExcel = new PHPExcel();

Email – you can use this module:
https://github.com/Luwe/Kohana-Email
or directly use swiftmailer:
http://swiftmailer.org/

1
2
3
4
5
6
7
8
9
10
11
12
require Kohana::find_file('vendor', 'swift/swift_required');
//Create the Transport
$transport = Swift_SmtpTransport::newInstance('localhost', 25);
//Create the Mailer using your created Transport
$mailer = Swift_Mailer::newInstance($transport);
//Create a message
$message = Swift_Message::newInstance('Email')
->setFrom(array('from@email.net' => 'From'))
->setTo(array('to@email.net'))
->setBody('An email');
//Send the message
$result = $mailer->send($message);

 
Helper
http://kohanaframework.org/3.2/guide/api/Arr
If you want to add some custom helper (generally used statically) or library (instantiated / object), for example: application/classes/participants.php

1
2
3
4
5
<?php defined('SYSPATH') or die('No direct script access.');
class Participant {
    static function currentweek() {
    }
}

in your controller:

1
$weektodisplay = Participant::currentweek();

 
Tips
To get the params in a controller (depending on your routes in application/bootstrap.php)

1
$this->request->param('id')

Debug:

1
echo Debug::vars();

To get the current controller:

1
echo Request::current()->uri();

To point at the right directory use:

1
<?php echo URL::base(); ?>

To get a custom column from users (auth):

1
echo Auth::instance()->get_user()->week;

Date:

1
2
Date::formatted_time('now', 'm-d-Y');
//constant Date::WEEK Date::YEAR

Way to use the model and the post for edition in form:

1
2
3
4
5
// Received the POST
if (isset($_POST) && Valid::not_empty($_POST)) {
    // keep but not saved
    $participant->values($_POST, array('email', 'username','password'))
}

To redirect use:

1
$this->request->redirect('manage/dashboard');

To make a link use:

1
<?php echo HTML::anchor('playlist', 'Playlist', array('style' => 'color: #FFF')); ?>

Function inside controller:
inside action_x():

1
array_walk($trackings, array($this, '_replaceplaylistendtime'), $params);

outside action_x():

1
2
3
static function _replaceplaylistendtime(&$value, $key, $p) {
    ...
}

 
Jquery
Use the latest minified version on google server:
http://code.jquery.com/jquery.min.js

User interface:
http://ninjaui.com/
http://jqueryui.com/
http://flowplayer.org/tools/index.html

Ajax:
In your “ajax” controller / action

1
2
3
4
5
if ($this->request->is_ajax()) {
    $id = json_decode($_POST['refresh']);
    $this->auto_render = FALSE;
    echo json_encode(array('result' => $id));
}

In your jquery:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$.ajax({
                type: "POST",
                url: "<?php echo URL::base(); ?>home/refresh",
                data: "refresh=1",
                async: true,
                dataType: "json",
                success: function(resultArray, textStatus, XMLHttpRequest)
                {
                    var result = parseInt(resultArray['result']);
                    alert(result);
                },
                error: function(request, textStatus, errorThrown)
                {
                    alert('error refreshing the session');
                }
});

 
Bookmarks
http://kohanaframework.org/3.2/guide/kohana/tutorials/
http://kohanaframework.org/3.2/guide/api or http://kohana.nerdblog.pl/api/

 
Deploying:
See this for more information about deploying Kohana application
http://nerdblog.pl/2011/09/05/deploying-kohana-3-2-application-in-production/

04

04 2010

biscuit box computer

i am sure there’s plenty of projects like this one. the idea is to get a “cheap” and “small” full featured linux box. a good keyword to start with: itx motherboard cpu combo. this one have hdmi 1080p, wifi, dual-core 1.6 ghz. i paid 147$ used. got also 1 gig of used ram for 20$. total of 167$ CAD. weight is 0.8 kg for the box and 0.3 kg for the supply. Just 18W at idle, and 23W full load (someone measured it with a Kill-a-watt device). the power supply is 90W. for the storage, i went with a laptop hard-drive (it’s smaller). i paid 100$ for a 500 gig, 7200 rpm, sata.

all this information will be outdated, now.

running a realtime kernel with enlightenment and blender:

01

04 2010

Spinach Curry

A very strange looking but tasty vegetarian curry.

Ingredients:
500g spinach
1 big onion
2-3 tomatoes
5 cloves of garlic
2cm of ginger
1 tbs cummin seed
1 tbs curcuma
1.5 tbs coriander powder
1 tbs galam masala
1/4 tbs cayen powder
3/4 tbs cream or yoghurt
1 tbs salt
150 g. paneer (indian cheese)

06

03 2010