発電ログにPyEphem

発電実験データのロガープログラム、日が出てるうちは3秒ごとにデータを取ればいいと思うけど、日没後にそれをやるのは非常に無駄で、ログファイルも巨大化するばかり。日没したら止めたい。

しかし日昇時刻も日没時刻も毎日変わる。朝5時から夜8時くらいまで取るようにすれば年間通して対応できるんで、それでいいっちゃいいんだけど、簡単に知る方法があれば使いたい気がする。GoogleとかがそういうAPIとか出してないかな? と思って軽く検索したところ、PyEphemという計算的に出してくれるライブラリを見つけた。

rhodesmill.org

もともとUnix用のXEphemという天文シミュレータソフトがあり、その計算ライブラリがlibastro、PyEphemはそのPythonラッパーのようだ。

軽量でネットワークに繋がってる必要もなく、非常に手軽。1980年代のちょっと簡易的なアルゴリズムを使っているため、1角度秒程度の誤差があるとのことだが、1角度秒って1度の1/3600なので、さすがにどうでもよい。

めちゃめちゃいいライブラリ見つけた〜! と喜んだものの、使ってみるとちょっとクセがある。時間はUTCしか受け付けず、緯度経度の値も弧度法で計算したがる。23時に日が昇るとか言われて、UTCかと思って9時間足しても8時に日が昇るのはすごくおかしい…と混乱したけど、緯度経度がヘンな値になってるのに気づき、調べ直したらそういうことだった。

さいわい時刻についても緯度経度についても「普通の書き方」ができるように拡張されていた。時間はephem.localtimeを使い、緯度経度は文字列として渡すことで勝手に弧度法に変換してくれる。

>>> import ephem, datetime, time
>>> ganeko = ephem.Observer()     # 観測値を宜野湾市我如古に
>>> ganeko.lon = str(127.75102)     # 我如古の東経
>>> ganeko.lat = str(26.25549)     #         北緯
>>> ganeko.elev = 105                   #          高度
>>> sun = ephem.Sun()                 #  太陽をインスタンス化
>>> ephem.localtime(ganeko.next_rising(sun))   # 次の日昇時刻
datetime.datetime(2023, 2, 28, 6, 53, 53, 993424)
>>> ephem.localtime(ganeko.next_setting(sun))   # 次の日没時刻
datetime.datetime(2023, 2, 27, 18, 11, 36, 953290)

時刻はdatetime.datetimeクラスで返してくれるので、いろんなことが非常に楽。 たとえばprint()に食わせれば普通のUNIXっぽい時刻表記で見せてくれる。

>>> print(ephem.localtime(ganeko.next_setting(sun)) )
2023-02-27 18:29:04.952017

沖縄の日没は遅いけど、もう6時半くらいまで沈まないとは。

また、このオブジェクトには時間を足したり引いたりするtimedeltaが使えるので、たとえば日没までの秒数も簡単にわかる。

>>> now = datetime.datetime.now()
>>> ephem.localtime(ganeko.next_setting(sun)) - now
datetime.timedelta(seconds=18072, microseconds=49575)

秒数を数値として返すにはtimedelta.secondsを参照すればよい。

>>> to_sunset = ephem.localtime(ganeko.next_setting(sun)) - now
>>> to_sunset.seconds
18072
>>> 

そんなわけで、日中は次のデータを取りに行くまで3秒だけ待ち、日が沈んだら翌朝まで寝るようにするには、

(次の日没時刻の日付 = 今の日付) かつ (次の日昇時刻の日付 > 今の日付)であるとき
    3秒スリープ
そうでないとき
  (次の日昇時刻 - 今の時刻)の秒数だけスリープ

というアルゴリズムでいける。これをプログラムに書き下すと、

ganeko.date = datetime.datetime.utcnow()    # オブザーバーを現在時刻で更新
now = ephem.localtime(ganeko.date)        # ローカル時間で比較
nextrise = ephem.localtime(ganeko.next_rising(sun)) # 次の日昇時刻を取る
nextset = ephem.localtime(ganeko.next_setting(sun)) # 次の日没時刻を取る
if (nextrise.date()  > now.date() and nextset.date()  == now.date()):   # 次の日昇日 > 今日、かつ、次の日没日 = 今日なら
      time.sleep(3)     # 次のデータを取るまで3秒待つ
else:     #  そうでなければ
      sleepsec =  ( nextrise - now).seconds    # 次の日昇までの秒数を計算し
      print('Sleeping %s sec.' % sleepsec)  #  「寝るよ」と宣言して
      time.sleep( sleepsec )      #   次の日昇までの時間を寝る

となる。*1

明け方にこれをログ取りプログラムに仕込み、よっしゃよっしゃ! と寝て、10時半過ぎに起きたところ、出力はこうなっていた:

kmf@pi3:~/bin $ ./solarwatt.py 
2023/02/27 04:30:56, 0.0, 0.0, 0.0, 99.4, 0.0, 0.0, 98.5
Sleeping 8632 sec.
2023/02/27 06:54:49, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 99.7
Sleeping 0 sec.
2023/02/27 06:54:49, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 99.7
Sleeping 0 sec.
2023/02/27 06:54:49, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 99.7
Sleeping 0 sec.
2023/02/27 06:54:49, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 99.7
Sleeping 0 sec.
2023/02/27 06:54:49, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 99.7
Sleeping 0 sec.
2023/02/27 06:54:49, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 99.7
Sleeping 0 sec.
2023/02/27 06:54:49, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 99.7
Sleeping 0 sec.
2023/02/27 06:54:49, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 99.7
Sleeping 0 sec.
2023/02/27 06:54:49, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 99.7
Sleeping 86399 sec.

日昇時刻まで寝てちゃんと起きたのに、すぐにまた寝て0秒後に目を覚まし、またすぐに寝て…を繰り返し、最後に明日の日昇時刻まで寝てる。経過時間的には、この部分は一瞬のうちに9回ループしてるので、要するに、朝一瞬起きたけどすぐまた寝た、ということだ。若者か。

Sleepingのメッセージが毎回出てることから、条件分岐にバグがあることがわかる。プログラムを見直したところ、3秒寝る条件をベタ書きしてたために1文字書き間違ってて、必ず else: 節に落ちるようになっていた。ゆえにまた寝ますね。おやすみなさい・・・ぐう! ということである。

いまは上記のようにすっきり見えるようにしてあり、ちゃんと動いてる。データは11:04:29からになっちゃったけど、まあ初期の実験データをちょっと可視化したいだけなので大した影響はない。

そんなわけで、可読性大事! という話でした。

それにしても、PyEphemは南中高度や昼の長さも手元で簡単に計算できるので、ソーラー遊びにはずいぶん使いでがありそう。太陽高度の軌跡と天候データを組み合わせれば確度の高い発電量予測が得られるので、実発電データと突き合わせれば故障検出なんかもできるだろう。

いま設置してるパネルは地面に対して10°と25°の傾きをもたせてあるのだが、このたった15度の違いが25%ほどの発電効率差を生む場合があり、太陽とパネルが正対した状態からの角度のズレの影響は単にcosθ分の減少ではなく、閾値があるように感じているところ。

これもPyEphemで得られる太陽高度の正確なデータとパネルの設置角度を組み合わせることで、直進光の実験施設など持っていなくても、というか、ウチのちょう適当に設置した仮設実験パネルを使ってすら、非常にきちんとした効率曲線が描けそうである。

*1:2023/03/02訂正。単純なdatetime.datetime.now()だと翌日も起動しなかった。