趣味のプロジェクト

 

北向き道路の家の一階に鏡で光を取り込むプロジェクト 梅村恭司 2004・12・1

 

アブストラクト

アマチュア無線の衛星通信でのアンテナの方向をかえるローテータを利用し、2階のベランダにコンピュータ制御の鏡を設置し、それを1階のそとにある凸面鏡と組み合わせて、光を取り込む事例を報告する。

 

キーワード:太陽採光 アマチュア無線 ローテータ 太陽軌道

 

1.はじめに、

 北向き道路の家というのは、道に接している面が北であるような土地に立っている家である。南側には別の家が立っているため、その1階については、冬の時期に直接日光が差し込まない家が多いが、著者の自宅もそのような家である。集合住宅のように敷地が広く、冬でも直接日光が差し込むように配慮されているところから転居すると、その差にびっくりすることになる。昼に光が差し込まないという問題は、昼間仕事にでているものにとっては切実ではないが、日中に家にいて家事をするものには切実な問題である。

 根本的な解決策としては、(1)家を小さなものに建て替える。(2)転居するというものもあるが、技術的におもしろくないしコストもかかる。インターネットで調べると「ひまわり」というシステムがあって、光ファイバーで日光を家に引き入れるというものがある。調べてみるとこれは60万円から200万円までのコストがかかる。東京にある展示スペースにいってみて現物も確認したが、このシステムで実現できるようなことを、鏡を利用して実現することを行った。

 ここでは、アマチュア無線の衛星通信でのアンテナの方向をかえるローテータを利用し、2階のベランダにコンピュータ制御の鏡を設置し、それを1階のそとにある凸面鏡と組み合わせて、光を取り込むことについて報告する。結果として、ある施工例としてひまわりと同等なシステムが20万円程度の材料費で構築できたことを報告する。ただし、それにつかった人件費や予備実験のコスト、および、今後の信頼性や設置の自由度をまでを考慮すると、販売されている「ひまわり」との優劣は状況によって変化する。

 

2.アンテナローテータ

アマチュア無線のアンテナローテータは、もともとは指向性のあるアンテナを通信対象の方向に向けるために使用される機器で、アンテナを水平方向で回転させるモータと、それを有線リモートコントロールする機器の組み合わせである。アマチュア無線で、衛星通信の試みが行われるときに、垂直方向にも回転できる機能をもつモデルが市販され、さらにそれをコンピュータのシリアル回線で制御できるインタフェースまでが市販されるようになった。ここで利用したのは「スタンダード」という会社の製品で「G5500」というモデルである。

 

 

図1ローテータを利用した制御鏡

 

この製品を利用すると、秒速30メートル以下の風の範囲で1平方メートルまでの構造物の方向を制御できる。また、短波帯のアンテナは10キロ以上の重さがあるが、それにも耐えることができる。そして、角度の制御は1度刻みでできる。制御する立場から

みると、野外仕様という特徴と、トルクが強力という特徴をもつサーボモータである。一般的なサーボモータにくらべて、回転速度は非常にゆっくりで、1回転するのに1分以上かかるが、太陽に追従するには十分である。通常のサーボモータを組み合わせて、鏡の制御を実現することもできるように思えるが、鏡を野外に設置し、かつ、それが雨ざらしであるというのは自作の機械では実現が難しい環境あり、このような機器を自作することは実際上難しい。ローテータでの一番の問題は制御精度である、八木アンテナなどの制御であれば、1度の精度は十分であるが、鏡の制御で十分かどうかを注意して設定しなければならない。

 コンピュータによる制御は単純で、シリアル回線でコントローラに向かって、「W180 090」のような文字を送付すると、水平方向で180度の位置、垂直方向で90度の位置にモータが移動して停止する。回転角度を検出するセンサがモータに組み込まれており、繰り返しで位置を変えても、ギアのすべりなどがおきても、制御が正しく行われるような構造になっている。

 

図2 ローテータの制御装置の外観

 

. 構造物と鏡

 構造物は、ホームセンターで購入できる塗装済み鉄パイプを中心に、必要に応じて木材を組み合わせて構築した。鉄パイプをジョイントする部材や、木材と接合する部材が市販されている。これを利用することで、塗装をするという手間が省けた。

 制御する鏡(制御鏡)は、アルミ枠の軽量な鏡を3つ利用した。回転する鏡30cm弱四方のひろさで、となりあう鏡との位置の差は30cmである。およそ、25cmに対して、1cm程度の傾斜で、擬似的な凹面鏡を構成するようにして、固定した。鏡の中心の垂線は750cm先で移転に交わることになるので、太陽光を反射した場合に、およそ3メートルから4メートル先で、3つの鏡の光が重なるようになる想定である。固定の鏡は、道路にでるときに様子をみるために使用する凸面鏡を利用した。凸面鏡の直径は、およそ30cmである。

 鏡の角度が1度変化すると、およそ5メートル先で10cmほど位置が変化することになるので、鏡の大きさから考えて、制御鏡と固定鏡との距離は5m以下という設計になる。

図3 固定鏡の取り付け状態

 

. 制御のためのモデル

 

制御の精度が10cm程度であるので、鏡の腕の長さが無視できなくなる。ローテータの構造上、水平方向に回転すると位置がずれてしまう。したがって、制御するためのモデルは、腕を考慮した。

太陽は、グリニッジ標準時の正午に、どの季節にも東経0度に太陽があると近似して制御し、地軸のずれによる太陽の高度は、正負の方向に23.4度の範囲で、一年を周期とした余弦曲線を描くと近似した。太陽の方向を計算するには設置場所の北緯と東経が必要である。この北緯と東経は、カーナビの機能を利用して求めたが、誤差から考えて、全国地図から値を求めることも問題はない。

 

図4 座標軸と構造モデル

 

制御鏡の水平軸の南からの上から見て反時計回りの角度をαとし,鏡の方向の水平から尾傾きをβとする。また,それぞれの構造物の大きさをa, b, c, rで表現することにする。このモデルと座標軸に従うと,以下のようになる。

 

鏡の法線方向ベクトルmは、

 m = ( −cos βsin α,  cos βsin α,  sin β)

 

太陽の方向ベクトルsun

 sun = (cos h cos f sin t sin h sin f,  cos f cos t,  sin h cos f sin t + cos h sin f)

 

 f = (23.4π/180 ) cos ( 2π(day/365.24)),  .... 冬至からの日数による太陽の季節変動角度

  t = 2π(hour 6) / 24        ... ローカル時間からの太陽の地軸上での角度

 h は,設置場所の北緯のラジアン表記

 

ここで,制御鏡から固定鏡までのベクトルtargetは,

 target  =a r cos α,  b r sin α,  −c)

 

したがって,制御鏡からでる反射光の方向ベクトルreflect

  reflect = target / | target |

 

. 光の反射の条件と制御問題の定式化

 

システムが目的の状態にあるためには二つの制約が存在する。

 

光は,制御鏡の表に当たらなければならない。これは下記の条件として表現できる。

    m ・ reflect  >  0

 

入射光と反射光と鏡の法線ベクトルの関係は,下記の条件として表現できる。

 sun = 2 ( reflect m)  m reflect

 

 時間と場所からsunが決定し,設置場所から,a, b, c, r が定まるので,αとβを制御して,上記の制約を満たすよういαとβを求めればよいのだが,制御できる角度は連続量でないので,その近似しか意味がない。残念ながら,このようなαとβは解析的にはきれいに解けない。求めるとすれば,数値計算をすることになる。これをプログラムしてもよいが,αとβを求めたあとに近似するのではなくて,下記の条件を満たすベクトルsについて,目的のコストを定義し、それが一番低いところの整数値を求めることにする。sはαとβとabcrから直接定まるのでこのコストの計算プログラム上単純になる。以下に、プログラムで解く問題の定式化を示す。

 

度の単位で制御できる2つの角度αとβでUを最小化する。ただし, 

= | sun s | かつ

s = 2 ( reflect m)  m reflect かつ

m ・ reflect  > 0

 

 

6. 制御プログラムの概要

 

プログラムは付録に記述するが,単純に山登り方で行うと,光の表にあたるという境界条件の縁に沿って移動し,最適解にたどりつかない可能性があるので,角度をおおまかな刻みで変化して,初期値を定める。また,制御単位が度程度であるので,微分係数を計算することなしに,前後の角度でコストが低いものがあるかを調べて,まわりにコストが低い点がなくなったときに停止するという方針で記述する。

 Linuxでシリアル回線を制御する方法についてのシステムコールはLinux How Toを調べて実装した。制御にあたっては,制御装置がなんらかの応答を返す可能性があるが,それは単純に無視し,制御が確実に行われたかどうかの確認は(制御装置の仕様では可能だが)実装しなかった。

 

. システムの調整

 実際に制御プログラムを動作させたとしても,正確に対象を設置するのは難しく,効率的な調整作業が必要である。このために,制御鏡の角から糸で錘をたらして,下記のような調整をおこなった。

(0)ローテータを支える垂直軸が,重力に対して平行であるか錘への糸との関係を確認する。

(1) α=0,β=π/2となるように「W000 090」として,その位置で鏡が垂直になるように水平方向の軸(ローテータと制御鏡を接続している軸)のボルト止めた。

(2) α=2π, β= π/2となるように「W360 090」として,錘の位置が(1)と同じになるようにローテータの制御装置の水平レンジ調整をした。

(3) α= 2π,β=0とα= 2π,β=πで,鏡が水平になるようにローテータ制御装置の垂直調整のための0点調整とレンジ調整を行した。

(4) 日中,日のあたるところで,r = a = b = 0,c>0の条件で,反射光が垂直に(錘と平行になるように)垂直軸の軸のボルト(ローテータの支持部)を止めた。

(5)固定の鏡に日光があたるように,r, a, b, c,の値を計測して,プログラムの定数を調整した。

図5調整中の鏡と糸で垂れ下がった錘

 

この調整では,緯度経度や時間などは,設置する角度にくらべて十分正確に設定できることを前提としていが,実際にそれらについては秒の単位までの情報が正確に得られるので前提とするのは妥当である。

 

. システムの評価

 

 システムを動作させて,効果を測定した。固定凸面鏡の角度のために,光を十分集めるには一つでは不足することが判明して,凸面鏡を2つにし,反射光が十分に取れるかどうかの計測を行った。光は,当初の予定通り10cm程度の移動はあるが,固定の鏡に光をうつすことができて,十分目的を満足している。

午前中の光の量は問題がないが,午後になると鏡の位置の関係で,固定鏡のある平面に近いところに制御鏡が移動してしまい,光の量が低下してしまった。しかしながら、固定鏡の位置を移動すると、こんどは効果的に室内に光を導けないという問題が生じる。現在のところ、午前中に光があることで、十分効果があるという利用者の意見に従っている。

 

. 既存のほかの方法との比較

 

 鏡を動かさない場合には,今回と大きさの鏡の組で,鏡の間が4m程度であると10分も光が続かない。このシステムでは午前中は光の取入れができ,システムを作成した効果があったといえる。動作のために使用するエネルギーについては,数分に一度,一秒程度だけモータを動かすだけなので,ほとんどのエネルギー消費は制御につかうコンピュータである。光の強度は,300Wの電球程度と推定されるので,利用できる時間が一年1/10であったとして30W以下ならば,エネルギー効果があるといえる。

 太陽採光を目的とした製品である「ひまわり」という製品と比較すると,制御鏡と固定鏡の位置関係と距離の制約が最大の差である。ひまわりは,光ファイバーで光を導くので,通常の配線とおなじように20m程度であれば,どのような位置関係でも光を導けるが,今回の方法はその距離は見通しでなくてはいけなくて,かつ,5mが限界である。また,鏡の位置関係によっては,光の量が減るという問題もある。しかしながら,実際の実現コストではここで説明した方法のほうが低く,光の量を増やすのには,鏡を大きくするという方法がとれるというところが異なる。

 

 

図6 太陽採光中の室内の状態 (午前9時)

 

10 まとめ,

 ここでは、アマチュア無線の衛星通信でのアンテナの方向をかえるローテータを利用し、2階のベランダにコンピュータ制御の鏡を設置し、それを1階のそとにある凸面鏡と組み合わせて、光を取り込むことについて報告する。結果として、ある施工例としてひまわりと同等なシステムが20万円程度の材料費で構築できたことを報告する。ただし、それにつかった人件費や予備実験のコスト、および、今後の信頼性や設置の自由度をまでを考慮すると、販売されている「ひまわり」との優劣は状況によって変化する。

 

付録A 制御プログラムソースコード


/* 座標系, : x, :y, 天上:z */

 

/* 太陽の現在時刻の方向を求める関数 */

 

/* 鏡の取り付け軸が南が0 (鏡は西を向いている), 東に回転する方向を正のアルファとする*/

/* 鏡の取り付け回転で,鏡が水平に向かっているときを0, 上を向く方向を正のベータとする*/

 

/* 鏡の法線ベクトルは, (sin alpha  cos beta, cos alpha cons beta, sin beta) */

 

/* 原点から太陽への方向ベクトル( sun_x, sun_y, sun_z) , 日付と時間と北緯東経で決まる*/

 

/* 鏡からの目的の物体へベクトルを( target_x, target_y, target_z)とするが、これは

   鏡の位置で変化する */

 

/* プログラムの方針:

   ()鏡の方向を変化させて, 鏡の法線ベクトルを求める。

   ()鏡の法線ベクトルと鏡から目的物体へのベクトルの内積を求める。

   ()鏡の方向を変化させて目的物体から鏡の中央をでて反射する光の単位方向ベクトルを求める。

 

   ()鏡のとりうる姿勢のうち, ()が正で、()と太陽への方向ベクトルの差が最小の姿勢を出力する。 */

 

#include <stdio.h>

#include <math.h>

#define pi 3.14159265

 

#define HOKUI (34.0 + (1.0 / 60.0) * 23.5)

#define TOKEI (137.0 + (1.0 / 60.0) * 44.0)

#define alpha_offset ((double) pi * 90.0 / 180.0)

#define beta_offset (0.05 + (double) pi * 90.0 / 180.0)

#define Arm  (-0.4) /* 0.4 /* メートル */

#define Hight (-2.0) /* メートル */

#define To_South  -0.2 /* メートル */

#define To_East    0.35 /* メートル */

 

/* 設置場所の太陽の方向ベクトル*/

double sun_x, sun_y, sun_z;

/* 同じ経度, 赤道上の太陽の方向ベクトル*/

double sun_eq_x, sun_eq_y, sun_eq_z;

/* 鏡の法線ベクトル */

double mx, my, mz;

/* オブジェクトへの方向ベクトル */

double tx, ty, tz;

/* オブジェクトから反射する光の方向ベクトル*/

double rx, ry, rz;

/* 制御目標数値 */

int alpha, beta;

 

/* 冬至から現在までの日数 */

int day;

/* 午前0時から,現在までの時間 by hour */

double hour;

 

double sin_alpha[361];

double cos_alpha[361];

double sin_beta[361];

double cos_beta[361];

 

double norm(double x, double y, double z)

{

  return sqrt(x * x + y * y + z * z);

}

 

void init_table(void)

{

  int i;

  for(i=0;i<=360;i++) {

    sin_alpha[i] = sin( ((double) pi * 2.0* i) / (360.0) - alpha_offset);

    cos_alpha[i] = cos( ((double) pi * 2.0* i) / (360.0) - alpha_offset);

    sin_beta[i] = sin( ((double) pi * 2.0* i) / (360.0) - beta_offset);

    cos_beta[i] = cos( ((double) pi * 2.0* i) / (360.0) - beta_offset);

  }

}

 

void compute_m(int a, int b)

{

  mx =    cos_beta[b] * sin_alpha[a];

  my =  - cos_beta[b] * cos_alpha[a];

  mz =    sin_beta[b];

}

 

void compute_t(int a, int b)

{ double norm; double x, y, z;

  x = To_South + Arm * cos_alpha[a];

  y = To_East + Arm * sin_alpha[a];

  z = Hight;

  norm = sqrt( x * x + y * y + z * z);

  tx = x / norm;

  ty = y / norm;

  tz = z / norm;

}

 

void compute_r(int a, int b)

{

  double product;

  compute_m(a, b);

  compute_t(a, b);

  product = mx * tx + my * ty + mz * tz;

  rx = 2 * product * mx - tx;

  ry = 2 * product * my - ty;

  rz = 2 * product * mz - tz;

}

 

#include <sys/time.h>

#include <unistd.h>

#include <time.h>

#include <string.h>

void compute_sun(void)

{ long sec;

  int status;

  struct timeval tv;

  struct tm * tmp;

 

  /* 太陽恒星角度 ( 数日程度の誤差は許可) ラジアン*/

  double fd;

  /* 同一経度の赤道上での太陽と東の水平線との角度ラジアン*/

  double tau;

 

  /* 北緯 (ラジアン) */

  double theta;

 

  memset(&tv, 0, sizeof(tv));

  status = gettimeofday(&tv, NULL);

  if(status != 0) exit(1);

  sec = tv.tv_sec;

  tmp = gmtime(&sec);

  day = tmp->tm_yday;

  hour = (double) tmp->tm_hour + (double) tmp->tm_min * (1.0 / 60.0) + (double) tmp->tm_sec * (1.0 / 3600.0);

  hour = hour + ((double) TOKEI / 360.0) * 24.0;

  while(hour >= 24.0) hour = hour - 24.0;

  while(hour < 0.0) hour = hour + 24.0;

  fprintf(stderr, "Date: %02d/%02d %02d:%02d:%02d GMT ",

          tmp->tm_mon+1, tmp->tm_mday,

          tmp->tm_hour, tmp->tm_min, tmp->tm_sec);

  fprintf(stderr, "Local hour=%5.3f ", hour);

 

  fd = 2 * pi * (23.4 / 350.0) * cos( 2 * pi * (double) ( day + 8) / 365.0);

  tau = 2 * pi * (hour - 6.0) / 24.0;

  theta = 2 * pi * (double) HOKUI / 360.0;

 

  fprintf(stderr, "offset=%5.3f\n", fd);

 

  sun_eq_x = sin(fd);

  sun_eq_y = cos(fd) * cos(tau);

  sun_eq_z = cos(fd) * sin(tau);

 

  sun_x = cos(theta) * sun_eq_x + sin(theta) * sun_eq_z;

  sun_y = sun_eq_y;

  sun_z = - sin(theta) * sun_eq_x + cos(theta) * sun_eq_z;

 

}

/* Slow but safe operation

void compute_alpha_beta(void)

{

  int a, b;

  double cost, c;

  compute_sun();

  a = 180;

  b = 90;

  compute_r(a, b);

  cost = norm(sun_x - rx, sun_y - ry, sun_z -rz);

  for (a = 0; a<=360; a++) {

    for(b = 0; b<=90; b++) {

      compute_r(a, b);

      if(sun_x * mx + sun_y * my + sun_z * mz > 0.0) {

         c = norm(sun_x - rx, sun_y - ry, sun_z -rz);

         if(c < cost) {

           cost = c;

           alpha = a;

           beta =b;

         }

      }

    }

  }

}

*/

void compute_alpha_beta(void)

{

  int a, b;

  double cost, c;

  compute_sun();

  a = 180;

  b = 90;

  compute_r(a, b);

  cost = norm(sun_x - rx, sun_y - ry, sun_z -rz);

  for (a = 0; a<=360; a+=30) {

    for(b = 0; b<=90; b+=10) {

      compute_r(a, b);

      if(sun_x * mx + sun_y * my + sun_z * mz > 0.0) {

         c = norm(sun_x - rx, sun_y - ry, sun_z -rz);

         if(c < cost) {

           cost = c;

           alpha = a;

           beta =b;

         }

      }

    }

  }

  a = alpha;

  b = beta;

 LOOP:

  if(a < 360) {

    compute_r(a+1, b);

    c = norm(sun_x - rx, sun_y - ry, sun_z -rz);

    if(c < cost) {

      cost = c;

      a = a+1;

      goto LOOP;

    }

  }

  if(a > 0) {

    compute_r(a-1, b);

    c = norm(sun_x - rx, sun_y - ry, sun_z -rz);

    if(c < cost) {

      cost = c;

      a = a-1;

      goto LOOP;

    }

  }

  if(b < 90) {

    compute_r(a, b+1);

    c = norm(sun_x - rx, sun_y - ry, sun_z -rz);

    if(c < cost) {

      cost = c;

      b = b+1;

      goto LOOP;

    }

  }

  if(b > 0) {

    compute_r(a, b-1);

    c = norm(sun_x - rx, sun_y - ry, sun_z -rz);

    if(c < cost) {

      cost = c;

      b = b-1;

      goto LOOP;

    }

  }

  cost = c;

  alpha = a;

  beta = b;

  return;

}

 

#include <errno.h>

#include <fcntl.h>

#include <stdio.h>

#include <stdlib.h>

#include <termios.h>

#include <sys/time.h>

#include <sys/types.h>

#include <unistd.h>

 

static char output[256];

 

static int last_alpha = -1;

static int last_beta = -1;

 

int main(int argc, char ** argv)

{ struct termios ios; int fd; int status; fd_set rfds; struct timeval time;

 fd = open("/dev/ttyS0", O_RDWR);

 if(fd < 0) exit(1);

 status = tcgetattr(fd, &ios);

 if(status < 0) exit(1);

 ios.c_lflag = 0; /* Raw Mode, No Echo */

 ios.c_lflag |= CS8;

 ios.c_iflag = 0; /* No control for input (experimental)*/

 ios.c_oflag = 0; /* No control for output (experimental)*/

 ios.c_cc[VMIN] = 255;

 ios.c_cc[VTIME] = 1;

 cfsetispeed(&ios, B9600);

 cfsetospeed(&ios, B9600);

 status = tcsetattr(fd, TCSANOW, &ios);

 if(status < 0) exit(1);

 fprintf(stderr, "output=/dev/ttyS0 8bit 9600 Baud\n");

 

 if(argc > 1) {

    exit(1);

 }

 init_table();

 while(1) {

   compute_alpha_beta();

   compute_r(alpha, beta);

   fprintf(stderr, "Sun:(%5.3f, %5.3f, %5.3f) ", sun_x, sun_y, sun_z);

   fprintf(stderr, "M:(%5.3f, %5.3f, %5.3f) ", mx, my, mz);

   sprintf(output, "W%03d %03d", 360 - alpha, beta);

   fprintf(stderr, "C:%s\n", output);

   if((sun_z > 0.0) && ((last_alpha != alpha)||(last_beta != beta))) {

     last_alpha = alpha;

     last_beta = beta;

   LOOP:

     /* read if serial line has data */

     FD_ZERO(&rfds);

     FD_SET(fd, &rfds);

     time.tv_sec=0; time.tv_usec=0;

     select(fd+1, &rfds, 0, 0, &time);

     if(FD_ISSET(fd, &rfds) ) {

       char ch;

       read(fd, &ch, 1);

       fputc(ch, stderr);

       fflush(stderr);

       goto LOOP;

     }

     write(fd, output, strlen(output));

     write(fd, "\r\n", 2);

   }

   sleep(60);

 }

 close(fd);

 exit(0);

}