wassup?

新ブログ→memo.wass80.xyz

mRubyの動くLED時計をESP32で作る [後編]

f:id:wass80:20190502132250p:plain

wass80.hateblo.jp

の後編です。

mRubyで時計のプログラムを書けるようにする

といっても,もくもくとAPIを実装するだけです。

用意したAPIはこれだけあります。

github.com

こういうプログラムをHTTPで書き込んで時計で実行できるようになりました。

i = 0

# ラインアート
lines = []
aline = [20, 10, 3, 8, 2, 3, 2, -1]

# 次の時間の線を生成する
def succ_line l
    x0, y0, x1, y1, dx0, dy0, dx1, dy1 = l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7]
    x0 += dx0; y0 += dy0; x1 += dx1; y1 += dy1
    (dx0 *= -1; x0 = 0) if x0 < 0
    (dy0 *= -1; y0 = 0) if y0 < 0
    (dx1 *= -1; x1 = 0) if x1 < 0
    (dy1 *= -1; y1 = 0) if y1 < 0
    (dx0 *= -1; x0 = 31) if x0 > 31
    (dy0 *= -1; y0 = 31) if y0 > 31
    (dx1 *= -1; x1 = 31) if x1 > 31
    (dy1 *= -1; y1 = 31) if y1 > 31
    [x0, y0, x1, y1, dx0, dy0, dx1, dy1]
end
8.times {|t|
  lines << aline
  aline = succ_line(aline)
}

time_format = "%H:%M"
message = "Welcome to KMC! Please Enjoy Yourself!"
# Loopの開始
Task::loop do
  # 描画削除
  Led::clear 0
  # 色指定 R, G, B (0 ~ 7)
  Led::color 0, 1, 0 
  lines.each {|l|
    # 線描画 x1, y1, x2, y2
    # 座標は(0 ~ 31, 0 ~ 31)
    Led::line l[0], l[1], l[2], l[3]
  }
  lines.shift()
  lines << succ_line(lines[-1])

  # フォント指定(番号)
  Led::font 2
  Led::color 7, 3, 3
  
  # 時刻更新
  Time::update
  
  # 文字出力 x, y, 文字列
  # Time::str(strftime文字列)
  Led::text 1, 12, Time::str(time_format)

  Led::font 0
  Led::color 5, 0, 5
  # 流れる表示 時間, 遅さ, y, 文字列
  Led::show i.div(2), 23, message
  
  Led::font 5
  # cmdで入力された文字列
  w = Task::cmd()
  Led::color 0, 5, 5
  Led::show i.div(2), 1, w 

  # [注意] Led::flushを実行しないと画面が更新されません。
  Led::flush
 
  i += 1
end

これは,[前編]の動画のプログラムです。 ラインアートを背景で動かしています。

mRuby拡張により配列や文字列を使えるので,普通のrubyプログラムのように書くことができます。

APIの実装

例えば Led::line の場合,

Cで実装されている部分はこのように,ブレゼンハムのアルゴリズムを持ってきます。

void b_line(int x0, int y0, int x1, int y1, int r, int g, int b) {
  mrb_int dx = abs(x1-x0), dy = abs(y1-y0),
          sx = (x0 < x1) ? 1 : -1, sy = (y0 < y1) ? 1 : -1,
          err = dx-dy;
 
  while (true){
    b_set(x0, y0, r, g, b);
    if (x0 == x1 && y0 == y1) break;
    mrb_int e2 = 2 * err;
    if (e2 > -dy) {
      err = err - dy;
      x0 = x0 + sx;
    }
    if (e2 < dx) {
      err = err + dx;
      y0 = y0 + sy;
    }
  }
}

mRubyの言葉に持ち上げます。

static mrb_value ledline(mrb_state* mrb, mrb_value self) { 
  mrb_int x0, y0, x1, y1;
  mrb_get_args(mrb, "iiii", &x0, &y0, &x1, &y1);
  b_line(x0, y0, x1, y1, p_r, p_g, p_b);
  return self;
}

そして Led::lineで呼べるように,環境に登録します

  struct RClass *Led = mrb_define_module(mrb, "Led");
  mrb_define_class_method(mrb, Led, "line", ledline, MRB_ARGS_REQ(4));

簡単ですね。

辛い点

  • ESP32のメモリが足りないので,重いプログラムが動かない
    • slackから対局できるオセロ時計が作られたが,メモリ不足で最後まで対局できなかった。
  • 日本語のエンコードISO-2022-JPを選んでしまった。
    • 入れた日本語フォントがJISコードでの文字対応だったのでこのエンコードを選んだが,めんどくさいエンコードだった。
  • 最初 Led::flushLed::flash になっていた
  • なぜ整数の割り算 3/2が小数1.5に評価されてしまうんだろう…。ここでRubyとの非互換性ができてしまった歴史的経緯が知りたい。

部員が作ってくれたプログラムたち

この他にも様々プログラムを書いてもらえました。

現在のデフォルトの時計は,室内気温や天気の表示を行うものになっています。

室内気温の情報は昔に作ったこのシステムの情報を使っています。

wass80.hateblo.jp

感想

  • ESP32を扱うのはサンプルコードが豊富なので割と簡単。
  • はんだ付けとか実装のほうが難しい。
  • 実際に動くものができるのは楽しいので良いですね。
  • Exploitがめっちゃ簡単にできそうなコードをCだと書いてしまうので,安全なIoT開発手法はもっとほしい…
    • RustでESP32開発とかできたらいいんですが,まだLLVM backendがないんですよね。ほしい。