2012年12月31日月曜日

コントロールサーバーとリモコン制御

これまではiRemoconをsocket経由で制御してIRリモコン出力を出すようにコントロールサーバーを組んでいましたが、今回コントロールサーバーを置いているRaspberryPiにリモコンの記録、発光機能を追加したのでiRemoconをやめてRaspberryPiでリモコンも制御するようにコントロールサーバーを変更しました。




これで、すべての制御がRaspberryPiとその配下のXBeeコントローラで完結したのでシンプルになりました。




リモコン制御を追加したついでに外出時の一斉shutdownでTVも落とせるようにリモコンコードを追加しました。
普通のリモコンのTV電源はon/offのトグルボタンなのでリモコンコードを変更して電源offのコマンドをだせるようにして対応しています。

IR発光部を強化

回路が検討しているうちに汚くなってきたのと、いまいちLEDの指向性が強すぎるのか角度によっては反応しなかったりするので、LEDを2個に増やしました。
今までもLED点灯時の電流は90mA程度(LEDはMax100mA品)流れているので出力が弱いということはないはずですが。
短時間nパルス発光なので、LEDを2個+Trでほぼ3.3Vになるようにして抵抗を外しました。
デジカメで発光の様子を比べてみましたが、今度のほうが手元のリモコンと同程度の発光強度が有りそうです。リモコンの効きも良くなりました。
これでうまくケースに収まります。


--> RaspberryPiの赤外線リモコン

RaspberryPiにIRリモコンの送受信を追加

だんだん汚くなってきてしまいましたが、RaspberryPiにIRリモコンの送信部、受信部を追加した状態です。



回路図は書くほどのものではないですが、こんな感じになります。




リモコンフォーマットの解析と展開(その他)

家製協、NEC、SONYフォーマット以外のフォーマットは38kHzの倍の76kHzでサンプリングした後、0のclk数、1のclk数を16bitで表したフォーマットで記録します。
データが大きくなりますが、38kHz搬送波のものは記録できると思います。

-----

/*
 code[0],code[1]    = ID
 code[2],code[3]    = length
 code[4]            = FormatOther 0x04
 code[5] - code[31] = reserve
 code[32] -         = continuous length(16bit BE)
*/
int FormatAnalyzeOther(unsigned int *buf, int bufSize, unsigned char *code, int codeSize) {

  int last = -1;
  int n = 0;
  int c = 0;
  memset(code, 0, codeSize);
  for(int i = 0; i < bufSize * 32; i++) {
    int d = (buf[i / 32] >> (31 - (i % 32))) & 1;
    if((last < 0) && (d == 1)) continue; // skip nosignal

    if(c && (d != last)) { // data edge
      code[32 + n * 2] = c / 256;
      code[32 + n * 2 + 1] = c % 256;
      n++;
      c = 1;
    } else { // data continue
      c++;
    }
    last = d;
  }
  if(last == 0) return -1;
  
  code[2] = (32 + n * 2) / 256;
  code[3] = (32 + n * 2) % 256;
  code[4] = 4;
  return 32 + n * 2;
}

int CodeExpandOther(unsigned char *code, int codeSize, unsigned int *buf, int bufSize) {

  memset(buf, 0, bufSize * 4);
  int len = (((code[2] << 8) | code[3]) - 32) / 2;
  int p = 0;
  int o = 1;
  for(int i = 0; i < len; i++) {
    int c = (code[32 + i * 2] << 8) | code[32 + i * 2 + 1];
    for(int j = 0; j < c; j++) {
      if(o && !(p & 1)) buf[p / 32] |= (1 << (31 - (p % 32)));
      p++;
    }
    o ^= 1;
  }
  p += 31;
  return p / 32;
}

リモコンフォーマットの解析と展開(SONY)

次はSONYフォーマットの解析部分と展開部分です。

-----

/*
 code[0],code[1]    = ID
 code[2],code[3]    = length
 code[4]            = FormatSONY 0x03
 code[5]            = 1TCLK
 code[6]            = AddrLen (5/8/13)
 code[7] - code[31] = reserve
 code[32]           = Data
 code[33],code[34]  = Address
 */
int FormatAnalyzeSONY(unsigned int *buf, int bufSize, unsigned char *code, int codeSize) {

  int last = -1;
  int n = 0;
  int c = 0;
  int on = 0;
  int off = 0;
  int t = 1000;
  int r = 0;
  memset(code, 0, codeSize);
  for(int i = 0; i < bufSize * 32; i++) {
    int d = (buf[i / 32] >> (31 - (i % 32))) & 1;
    if((last < 0) && (d == 1)) continue; // skip nosignal

    if(d != last) { // data edge
      if(c && (last == 0)) {
        on = c;
        if(!n) { // leader
           t = on / 4;
        } else { // customer/data code
          if(((off + t / 2) / t) != 1) return -1;
          if(((on + t / 2) / t) == 2) {
            code[32 + (n-1)/8 + r * 4] |= 1 << ((n-1)%8);
          } else if(((on + t / 2) / t) != 1) {
            return -1;
          }
        }
        n++;
        if(n == 8) n++;
      } else if(c) {
        off = c;
      }
      c = 1;
    } else { // data continue
      c++;
      if(c > t * 20) { // trailer
        if(n) {
          code[32 + 3 + r * 4] = n;
          r++;
        }
        n = 0;
      }
    }
    last = d;
  }
  if(r < 3) return -1;
  n = code[32 + 3];
  if((n != (1 + 8 + 5)) && (n != (1 + 8 + 8)) && (n != (1 + 8 + 13))) return -1;
  if(memcmp(&code[32], &code[36], 3) || memcmp(&code[32], &code[40], 3)) return -1;

  code[3] = 32 + 3;
  code[4] = 3;
  code[5] = t;
  code[6] = n - 8 - 1;
  return 32 + 3;
}

int CodeExpandSONY(unsigned char *code, int codeSize, unsigned int *buf, int bufSize) {

  memset(buf, 0, bufSize * 4);
  int len = (((code[2] << 8) | code[3]) - 32) / 2;
  int t = code[5];
  int alen = code[6];

  int p = 0;
  for(int i = 0; i < 3; i++) {
    // leader
    for(int j = 0; j < 4 * t; j++) {
      if(!(p & 1)) buf[p / 32] |= (1 << (31 - (p % 32)));
      p++;
    }

    //Data
    for(int j = 0; j < 7; j++) {
      int d = (code[32] >> j) & 1;
      p += t;
      for(int k = 0; k < (d?t*2:t); k++) {
        if(!(p & 1)) buf[p / 32] |= (1 << (31 - (p % 32)));
        p++;
      }
    }

    //Addr
    for(int j = 0; j < alen; j++) {
      int d = (code[33 + (j / 8)] >> (j % 8)) & 1;
      p += t;
      for(int k = 0; k < (d?t*2:t); k++) {
        if(!(p & 1)) buf[p / 32] |= (1 << (31 - (p % 32)));
        p++;
      }
    }
    p = 3420 * i;
  }
  p += 31;
  return p / 32;
}

リモコンフォーマットの解析と展開(NEC)

次にNECフォーマットの解析部分と展開部分のコードです。

-----

/*
 code[0],code[1]    = ID
 code[2],code[3]    = length
 code[4]            = FormatNEC 0x02
 code[5]            = 1TCLK
 code[6] - code[31] = reserve
 code[32],code[33]  = CustomerCode
 code[34],code[35]  = Data, ~Data
*/
int FormatAnalyzeNEC(unsigned int *buf, int bufSize, unsigned char *code, int codeSize) {

  int last = -1;
  int n = 0;
  int c = 0;
  int on = 0;
  int off = 0;
  int t = 1000;
  memset(code, 0, codeSize);
  for(int i = 0; i < bufSize * 32; i++) {
    int d = (buf[i / 32] >> (31 - (i % 32))) & 1;
    if((last < 0) && (d == 1)) continue; // skip nosignal

    if(d != last) { // data edge
      if(c && (last == 0)) {
        on = c;
      } else if(c) {
        off = c;
        if(!n) { // leader
          if(((on + off / 2) / off) != 2) return -1;
          t = (on + off) / 24;
        } else { // customer/data code
          if(((on + t / 2) / t) != 1) return -1;
          if(((off + t / 2) / t) == 3) {
            code[32 + (n-1)/8] |= 1 << ((n-1)%8);
          } else if(((off + t / 2) / t) != 1) {
            return -1;
          }
        }
        n++;
      }
      c = 1;
    } else { // data continue
      c++;
      if(c > t * 20) { // trailer
        if(last == 0) return -1;
        if(((on + t / 2) / t) != 1) return -1;
        break;
      }
    }
    last = d;
  }
  n += 6;
  if(code[34] != (code[35] ^ 0xff)) return -1;
  code[2] = (32 + n / 8) / 256;
  code[3] = (32 + n / 8) % 256;
  code[4] = 2;
  code[5] = t;
  return 32 + n / 8;
}

int CodeExpandNEC(unsigned char *code, int codeSize, unsigned int *buf, int bufSize) {

  memset(buf, 0, bufSize * 4);
  int len = ((code[2] << 8) | code[3]) - 32;
  int t = code[5];

  int p = 0;
  // leader
  for(int i = 0; i < 16 * t; i++) {
    if(!(p & 1)) buf[p / 32] |= (1 << (31 - (p % 32)));
    p++;
  }
  p += 8 * t;

  // customer code & data
  for(int i = 0; i < len; i++) {
    for(int j = 0; j < 8; j++) {
      int d = (code[32 + i] >> j) & 1;
      for(int k = 0; k < t; k++) {
        if(!(p & 1)) buf[p / 32] |= (1 << (31 - (p % 32)));
        p++;
      }
      p += (d?t*3:t);
    }
  }

  // trailer
  for(int i = 0; i < t; i++) {
    if(!(p & 1)) buf[p / 32] |= (1 << (31 - (p % 32)));
    p++;
  }
  p += 20 * t;
  p += 31;
  return p / 32;
}

リモコンフォーマットの解析と展開(家製協)

国内のリモコンは大きく分けて3種類有ります。
まずは一般的な家製協(AEHA)フォーマットの解析部分と解析されたデータを展開して戻す部分のコードです。

-----

/*
 code[0],code[1]    = ID
 code[2],code[3]    = length
 code[4]            = FormatAEHA 0x01
 code[5]            = 1TCLK
 code[6] - code[31] = reserve
 code[32],code[33]  = CustomerCode
 code[34] -         = Parity/Data0, Data1, Data2, ... DataN
*/
int FormatAnalyzeAEHA(unsigned int *buf, int bufSize, unsigned char *code, int codeSize) {

  int last = -1;
  int n = 0;
  int c = 0;
  int on = 0;
  int off = 0;
  int t = 1000;
  memset(code, 0, codeSize);
  for(int i = 0; i < bufSize * 32; i++) {
    int d = (buf[i / 32] >> (31 - (i % 32))) & 1;
    if((last < 0) && (d == 1)) continue; // skip nosignal

    if(d != last) { // data edge
      if(c && (last == 0)) {
        on = c;
      } else if(c) {
        off = c;
        if(!n) { // leader
          if(((on + off / 2) / off) != 2) return -1;
          t = (on + off) / 12;
        } else { // customer/data code
          if(((on + t / 2) / t) != 1) return -1;
          if(((off + t / 2) / t) == 3) {
            code[32 + (n-1)/8] |= 1 << ((n-1)%8);
          } else if(((off + t / 2) / t) != 1) {
            return -1;
          }
        }
        n++;
      }
      c = 1;
    } else { // data continue
      c++;
      if(c > t * 20) { // trailer
        if(last == 0) return -1;
        if(((on + t / 2) / t) != 1) return -1;
        break;
      }
    }
    last = d;
  }
  n += 6;
  unsigned char p = code[32] ^ (code[32] >> 4) ^ code[33] ^ (code[33] >> 4) ^ code[34];
  if(p & 0x0f) return -1;
  code[2] = (32 + n / 8) / 256;
  code[3] = (32 + n / 8) % 256;
  code[4] = 1;
  code[5] = t;
  return 32 + n / 8;
}

int CodeExpandAEHA(unsigned char *code, int codeSize, unsigned int *buf, int bufSize) {

  memset(buf, 0, bufSize * 4);
  int len = ((code[2] << 8) | code[3]) - 32;
  int t = code[5];

  int p = 0;
  // leader
  for(int i = 0; i < 8 * t; i++) {
    if(!(p & 1)) buf[p / 32] |= (1 << (31 - (p % 32)));
    p++;
  }
  p += 4 * t;

  // customer code & data
  for(int i = 0; i < len; i++) {
    for(int j = 0; j < 8; j++) {
      int d = (code[32 + i] >> j) & 1;
      for(int k = 0; k < t; k++) {
        if(!(p & 1)) buf[p / 32] |= (1 << (31 - (p % 32)));
        p++;
      }
      p += (d?t*3:t);
    }
  }

  // trailer
  for(int i = 0; i < t; i++) {
    if(!(p & 1)) buf[p / 32] |= (1 << (31 - (p % 32)));
    p++;
  }
  p += 20 * t;
  p += 31;
  return p / 32;
}

RaspberryPiでリモコン送信

更にPCMを使ってIRリモコンの送信側の設定です。
読み込んだデータは反転しているので、データを反転した上でHiの部分を38kHzで変調かけておきます。このため、GPIOCLKは38kHzのclkを作るために倍の76kHzにしました。
データを作成した後でPCMをkickしてデータを出力します。
データの処理ルーチンはこのあとupしておきます。

PCMの出力部分は以下のコードです。

-----

int PCMWrite(unsigned int *buf, int size) { // ret < 0 : error

  pcm[PCMCS] = PCMCSSTBY | PCMCSEN;
  pcm[PCMMODE] = PCMMODECLKDIS | PCMMODECLKM | (31 << SHIFT_PCMMODEFLEN) | (1 << SHIFT_PCMMODEFSLEN); // PCMCLK stop
  pcm[PCMTXC] = PCMTXCCH1WEX | PCMTXCCH1EN | (0 << SHIFT_PCMTXCCH1POS) | (8 << SHIFT_PCMTXCCH1WID);
  pcm[PCMMODE] = PCMMODECLKM | (31 << SHIFT_PCMMODEFLEN) | (1 << SHIFT_PCMMODEFSLEN); // PCMCLK start
  pcm[PCMCS] = PCMCSSTBY | PCMCSTXERR | (3 << SHIFT_PCMCSTXTHR) | PCMCSTXCLR| PCMCSEN; // TXERR TXFIFO Clear
  usleep(120); // 120us (> PCMCLK 26us x 4clk)
  pcm[PCMCS] = PCMCSSTBY | (3 << SHIFT_PCMCSTXTHR) | PCMCSEN;
  // fifo set
  int c = 0;
  while(pcm[PCMCS] & PCMCSTXW) {
    pcm[PCMFIFO] = 0;
    c++;
  }
  int error = 0;
  pcm[PCMCS] = PCMCSSTBY | (3 << SHIFT_PCMCSTXTHR) | PCMCSTXON | PCMCSEN; // TXON

  for(int i = 0; i < size; i++) {
    while(!(pcm[PCMCS] & PCMCSTXW));
    pcm[PCMFIFO] = buf[i];
    if(pcm[PCMCS] & PCMCSTXERR) {
      error = -1;
      break;
    }
  }
  while(!(pcm[PCMCS] & PCMCSTXE));
  pcm[PCMCS] = PCMCSSTBY | PCMCSEN; // TXoff
  return error;
}

2012年12月30日日曜日

RaspberryPiでIRリモコンを読む(PCMの設定)


続いてPCMの設定です。
FRAME_SIZEを32bitにして、32bit分まるごとch1に割り当てます。
PCM_FSはMasterにしないとFS待ちで止まってしまうのでMasterに設定します。
PCMCLKはexternal_clkのinputに設定します。
これで、GPIOCLKで作った38kHzでサンプリングしてFIFOに取り込まれるようになります。
DMAはユーザーランドからはPhysicalAddressが取れないのと、連続メモリの割当が出来ないのでポーリング動作で取り込んでエラーが発生していないことだけを監視しています。
一緒にRXFIFOのレベルを監視してギリギリになっていないことを見られるようにしています。
今のところエラーなく取り込めているようですが、エラーが多発するようならば、linuxの使用メモリをちょっと小さくして後ろ側のメモリを固定でDMAバッファにしてしのごうかと思います。

-----

int PCMRead(unsigned int *buf, int size) { // ret = maxfifo level, ret < 0 error
  
  pcm[PCMCS] = PCMCSSTBY | PCMCSEN;
  pcm[PCMMODE] = PCMMODECLKDIS | PCMMODECLKM | (31 << SHIFT_PCMMODEFLEN) | (1 << SHIFT_PCMMODEFSLEN); // PCMCLK stop
  pcm[PCMRXC] = PCMRXCCH1WEX | PCMRXCCH1EN | (0 << SHIFT_PCMRXCCH1POS) | (8 << SHIFT_PCMRXCCH1WID);
  pcm[PCMMODE] = PCMMODECLKM | (31 << SHIFT_PCMMODEFLEN) | (1 << SHIFT_PCMMODEFSLEN); // PCMCLK start
  pcm[PCMCS] = PCMCSSTBY | PCMCSRXERR | PCMCSRXCLR| PCMCSEN; // RXERR RXFIFO Clear
  usleep(120); // 120us (> PCMCLK 26us x 4clk)
  pcm[PCMCS] = PCMCSSTBY | PCMCSRXON | PCMCSEN; // RXON

  unsigned int d1 = 0xffffffff;
  unsigned int d2 = 0xffffffff;
  int maxfifo = 0;
  int error = 0;
  do {
    d1 = d2;
    while(!(pcm[PCMCS] & PCMCSRXR));
    d2 = pcm[PCMFIFO];
  } while(d2);
  buf[0] = d1;
  buf[1] = d2;
  for(int i = 2; i < size; i++) {
    while(!(pcm[PCMCS] & PCMCSRXR));
    buf[i] = pcm[PCMFIFO];
    if(pcm[PCMCS] & PCMCSRXERR) error = 1;
    int fifo = (pcm[PCMGRAY] >> 16) & 0x3f;
    if(fifo > maxfifo) maxfifo = fifo;
  }
  pcm[PCMCS] = PCMCSSTBY | PCMCSEN; // RXoff
  if(error) return -1;
  return maxfifo;
}

RaspberryPiでIRリモコンを読む(GPIOCLKの設定)

やはりGPIOをユーザーランドから読み込むことは難しいことがわかったので、RaspberryPiのPeripheralで使えそうなものがないか調べてみました。
候補はUART,SPIとPCMあたりですが、SPI、UARTはフォーマットの自由度があまりないのでちょっとむずかしそうです。
PCMなら32bit x 64段のFIFOがあるので100KHzでsamplingしたとしても20ms程度はバファリングされます。裏で重たい処理が動いてなければユーザーランドでもなんとかなるかも。
overflowも検知できるので試してみる価値はありそうです。

BCM2835にはgpio_clkというclock発生器があるのでPCMCLKを赤外リモコンの変調周波数である38kHzにしてPCMを動かすとうまくいくのではないかと思います。

とりあえず、使えそうなGPIO4にGPIOCLK0を繋いで出力設定してみます。
手元にオシロもロジアナもないので先日試しに作ったgpioからのデータをgettimeofday()でタイミングとりながら取り込むソフトと組み合わせてサンプリングして正しい周波数が出ていることを確認していきます。GPIO4からGPIOCLK0を出す設定にし、GPIO23に繋いでサンプリング周期を3us程度で取り込むようにして遅めの10kHzと目的の38kHz、早めの100kHzを出してみてちゃんと周期があっているかをチェックし、下記のコードで正しく出ていることを確認しました。
とりあえず、これでPCMCLKを確保できました。

-----

#define BCM2835_PERI_BASE    0x20000000
#define GPIOCLK_BASE         (BCM2835_PERI_BASE + 0x101000)
#define   GPCLKCTL           0x70/4
#define   GPCLKDIV           0x74/4
#define   GPCLKCTLSET(n,d)   gpioclk[GPCLKCTL + (n)*8] = d
#define   GPCLKCTLGET(n)     gpioclk[GPCLKCTL + (n)*8]
#define   GPCLKDIVSET(n,d)   gpioclk[GPCLKDIV + (n)*8] = d

int GPClkOutput(int f) {

  int clk = 19200000; // osc clk 19.2MHz
  int divi = clk / f;
  int divf = (clk % f) * 1024 / f;
  if(divi > 0xfff) return -1;
  GPCLKCTLSET(0, (0x5a << 24) | (1 << 9) | 1); // disable clk gen
  int i;
  for(i = 0; i < 10000; i++) if(!(GPCLKCTLGET(0) & (1 << 7))) break;
  if(i >= 10000) return -1;
  GPCLKDIVSET(0, (0x5a << 24) | (divi << 12) | divf);
  GPCLKCTLSET(0, (0x5a << 24) | (1 << 9) | (1 << 4) | 1); // enable clk gen
  return 0;
}

Raspberry PiでIRリモコンを読む


うちの場合リビングに2台の同じエアコンがあってリモコンを操作すると両方が動作します。
普通はこれでもいいのですが、たまに片方を止めたい時などリモコンを微妙に手で覆ってコントロールしています。
iRemoconもいいのですが、できればエアコンのHA端子制御につけているXBeeモジュールの先にマイコンでも付けて弱いIR出力を出せるようにしたいと思っています。
RaspberryPiが来たので、まずはリモコンの解析を兼ねて取り込みの方を検討し始めました。
まだクロス環境は構築してないので出来ればdriverは標準のもので済ませたい(たしかkernelをbuildしないとdriverをbuild出来なかった気がします)ので、ユーザーランドからうまく出来ないか試し始めました。

IRレシーバーのICをGPIOに繋いでみました。

とりあえず、mmapでgpioのあたりをアクセスできるようにして、gettimeofday()で100usec毎にGPIOをサンプリングしてみました。
目論みとしてはリモコンは3回繰り返し同じコードを出すので、うち2回程度はコンテキストスイッチに邪魔されずに取れるのではないか、2回とれれば正しいコードを取得できるのでは無いかという感じです。

やってみましたが、見事ダメでした。リモコンのボタンを2回連続で押して最初の4回は途中でコンテキストスイッチが入ったようで情報が欠落しています。2回は全部正しく取れましたが、どうも安定して取れてません。

もう少し他のインターフェイスも研究して、最悪driverを書かないとだめかな。
100usに1bitのサンプリングなのでdriverでtimer割り込み使えばOKなレベルなんですけどね。

2012年12月26日水曜日

RaspberryPiのlinux設定

取り敢えず動くところまで簡単にできそうだったためlinuxはraspbianを選択しました。
色々な方が書いておられるので特に解説はしませんがやったことを書いておきます。
ホストはMacを使っています。

■linuxイメージをSDカードに書き込む
# df
でSDカードのデバイスファイルを確認
/dev/disk4s1  114576     33824     80752    30%    512      0  100%   /Volumes/Untitled
なのでデバイスとしては/dev/disk4

# diskutil umountDisk /dev/disk4
でunmount

http://www.raspberrypi.org/downloads からraspbian "wheezy"をgetしunzip。

# sudo dd bs=1m if=2012-12-16-wheezy-raspbian.img of=/dev/disk4
でSDにイメージを書き込む(約2分くらい)

■linuxの起動
raspberry piにSDカードをセットしEtherと電源、HDMIにTVを繋いで起動
起動中のlogからMACアドレスをメモ。
DHCP,DNSを設定。192.168.0.5にraspberrypiとして登録。
再起動して1分ほど待って起動した頃を見計らってlogin

# ssh pi@raspberrypi
passwd : raspberry
でlogin

pi@raspberrypi ~ $ sudo raspi-config
を実行し設定。
expand_rootfsを実行。
change_timezoneをAsia->Tokyoを選択。
memory_splitを16M
boot_behaviourをdesktop->Disable
に設定しrebootする。

■アカウント関連の設定
pi@raspberrypi ~ $ sudo su
でrootになり
root@raspberrypi:/home/pi # passed
でrootのパスワード
root@raspberrypi:/home/pi # passed pi
でpiのパスワードを設定
root@raspberrypi:/home/pi # exit
でpiに戻る。
pi@raspberrypi ~ $ ssh-keygen
でsshの鍵を生成
# scp ~/.ssh/id_rsa.pub raspberrypi:
でMacのssh公開鍵をコピー
pi@raspberrypi ~ $ cp .ssh/id_rsa.pub .ssh/authorized_keys
pi@raspberrypi ~ $ chmod 600 .ssh/authorized_keys
pi@raspberrypi ~ $ cat id_rsa.pub >> .ssh/authorized_keys
でlogin出来るように設定。

■apt関連
pi@raspberrypi ~ $ sudo apt-get update
pi@raspberrypi ~ $ sudo apt-get upgrade
で最新の状態に。

===ここからはXBeeのコントロールサーバーにするための設定。===

■curlとgiflibを使うので
pi@raspberrypi ~ $ sudo apt-get install libcurl4-openssl-dev
pi@raspberrypi ~ $ sudo apt-get install libgif-dev

■UARTをXBee用に使うための設定
kernelの起動パラメータからttyAMA0をconsoleに指定している部分を外す
/boot/cmdline.txtを編集
dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait
->
dwc_otg.lpm_enable=0 rpitestmode=1 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait
rpitestmodeが何をしているのかわからないけどUARTを使うときは必要らしい。

/etc/inittabの編集
T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100
の行をコメントアウト

これで使える状態になりました。
pi@raspberrypi ~ $ sudo sync
で取り敢えずSDへの書き込みをしておきます。




Raspberry PiをXBeeのコントロールサーバー化

Raspberry PiのlinuxにXBeeのコントロールサーバー部分を移植しました。
Macの場合はdaemon化やKeepAliveをlaunchctlが勝手にやってくれていたのですが、linuxの場合は自分で実装する必要があります。
daemon()でdaemon化したprocessをKeepAliveのプロセスにして、そのchild processでコントロールサーバーを実行しています。
こんな感じになっています。


linuxの設定は別途書きます。

回路はBCM2835のUARTにXBeeを直接接続しています。
3.3V, GND, TXD, RXDの4本だけコネクタから接続しています。



こんな感じにケースに収まっています。

2012年12月25日火曜日

Raspberry Piが届きました

注文していたRaspberryPiが届きました。10/22に注文してから先週の旅行中に届いていたので約2ヶ月ですね。
色々なWebの情報を見ている限りではダンボール箱に入って宅配便で届くと思っていましたが、プラスチックのケースに入って緩衝材に包まれてポストに入っていました。
開封するとこんな感じです。

ケースはそのまま基板を簡単に固定できるフックがついているのでそのまま使えそうです。



電源とEtherの部分をカットしてそのままlinuxを入れてサーバー化してみました。
つけっぱなしにしておくとちょっと温まりますが問題ないレベルです。

2012年12月15日土曜日

セキュリティ


外からアクセスできるようにしたためセキュリティを強化し、プログラムの構成も整理しました。構成を色々変えたので暫くまともに動作しない状況になってしまってました。
ようやく、ちゃんと動き始めたのでまとめておきます。


















iPhoneからWebServerの間はhttpsでサーバー認証、クライアント認証を相互に行なってチェックしています。
WebServer内のapache2とProxyDaemonの間はクライアント認証の証明書からアカウントを拾って名前付きpipe(FIFO)でファイルアクセス権で守っています。
ProxyDaemonとLAN内のControlDaemon間はSSLでサーバー認証、クライアント認証で相互に確認をしています。
ControlDaemonからXBeeの間はAESで暗号化して守っています。
これでひと通り安全なパスが確保できています。


2012年12月4日火曜日

iPhoneのWebアプリ


とりあえず外から制御するためにhttpsでアクセスされた時にsocketでコマンドを投げるcgiを作って運用しています。
アクセスはSafariでcgiを呼び出した状態を『ホーム画面に追加』でアイコン化して使っていました。
ところがこの方法だとSafariの中にページが開いている状態で残っており、うっかりそのページを開いてしまうとreloadしてしまいます。状態を取る系のコマンドの場合は問題ないのですが、実行系のコマンドの場合は再度コマンドを発行してしまいます。
エアコンをつけるコマンドを発行した後ページを閉じずに置いておいて、リモコンでエアコンを消して出かけた後にうっかりSafariを開いてしまうとエアコンがついてしまいます。

これに対応するにはiPhoneのアプリを作るしか無いかと考えていましたが、ググるともっと簡単な方法があるのが判りました。
CGIで返すhtmlの<head>タグの中に
<meta name="apple-mobile-web-app-capable" content="yes">
の記述を追加してSafariから開き『ホーム画面に追加』でアイコン化することで、アイコンを開いた時の挙動が変わります。
これまではSafariの1ページとして開いていたのが独立したアプリとして開いてくれます。

この設定だけだと上部のステータスバーが出てしまいますが、
<meta name="apple-mobile-web-app-status-bar-style" content="black">
を追加することで消すことができます。
さらにiPhoneで見るのにちょうどいいように画面の幅に合わせます。
<meta name="viewport" content="width=1024">
を追加して適当な画面幅になるように横幅を設定します。(1024pixelにしているのはグラフの横幅です)


アプリとしてのアイコンをapple-touch-icon-precomposed.pngの名前でサーバーのDocumentRootに置いておくとアイコン化する時に使ってくれます。





ホームボタンをダブルクリックして出てくる動作中のアプリの一覧にもSafariとは別に出てくるようになり別のアプリとして扱われます。当然ながらSafariのページには出てきていません。



これで一見するとiPhoneのアプリに見えるようになりました。