FlightGearのデータを外部表示させよう(5) | virt_flyのブログ

virt_flyのブログ

フライトシミュレーターソフトのFlightGearで仮想飛行を楽しむブログです。

OLEDで表示する速度計と高度計↑Raspberry Pi Picoと2枚のOLEDを使って、FlightGearの飛行中のデータを反映する速度計、2針の高度計を実現
 

2枚のOLEDで計器を2つに

 

■2系統あるI2Cを使って2枚のOLEDに描画


この間、フライトシミュレーターのFlightGearで飛行中のデータを、Wi-FiやUSB接続でRaspberry Pi Picoに送り、OLED画面にミニPFD(Primary Flight Display、水平儀・速度・高度)を描かせてきました。今回は、このOLEDを2枚使って、ミニPFDとなんちゃってHSI(Horizontal Situation Indicator、方位指示器)をこしらえてみたいと思います。

OLEDを2枚使うとなれば、その方法は概ね次の2つでしょう。

一つは、今使っている0.96インチのOLEDは2つのI2Cアドレス(0x3C、0x3D)を持つことができるので、アドレスを2つに分けて並列接続する方法。もう一つは、PicoにはI2Cが2系統あり、これを利用する方法です。

前者の場合は、OLED基板裏面のジャンパパッド部分の抵抗を付け替えることでアドレスを切り替えるのですが、工作のハードルが少々高く、今回は見送って後者を採用しました。

I2Cを2系統使うということは、1枚目のOLEDのSDAピンをGP0、SCLをGP1に、2枚目のOLEDのSDAをGP2、SCLをGP3に接続します。この辺りのプログラムの書き方は、実際を後に示しますので確認してください。

 

いじるプログラムは、基本的にPico側パイソンプログラム(main.py)。必要に応じて使うFlightGearデータを書き込むxmlファイル(fgdata.xml)です。パソコン側のパイソンプログラムやFlightGearの起動オプションはこれまで通りで、変更はありません。

 

楽するために、計器のプログラムの作成には生成AI利用しました。とはいっても、次に述べるように楽ができるかどうかは、生成AIの使い手次第です。

 

■生成AIはロジック偏重

Raspberry Pi Pico OLEDでFlightGearデータ表示

↑改良したミニPFDと単なる方位コンパスのなんちゃってHSI

 

前回のブログ「生成AIを御する」にて触れたことですが、生成AIも使い手次第。ときには迷路に誘い込まれ、動いていたプログラムを復旧するのに四苦八苦することもあります。

今回も特にひどかったのが、流れる文字列を画面の特定箇所でマスクを使い非表示にし、その上に別な文字列を固定表示させようとしたときに起こりました。

マスクが少しずれていて、文字列が一部重なりあって読みにくくなっていたのですが、生成AIは文字列の間隔を詰めてズレの範囲に収めようと試みたり、マスクの幅を広げるのはいいが逆の方向に広げるものだから余計なところが非表示になってしまうというお粗末さ。

こうした場合、マスクをズレた分動かし、必要ならサイズを変更する、というのが普通じゃないかと思うのですが。まさに、居酒屋で客が騒いでうるさい時、BGMの音を大きくしてかき消そうとするようなもの。ますますやかましく事態は悪化するばかりです。本来的には、騒がしい客に少し静かにするように注意するとか、自国ファーストで平和外交を疎んじる今であれば、四の五の言わずに相手の顔面に一発お見舞いし黙らせる、というところでしょう。

我慢できなくなって指摘するまで生成AIも気が付かない。ロジックを重視しがちと生成AI自ら弁解していました。これまで動いていたプログラムを借りて少し変えたいとき、うまくいかなければ変えた部分を疑うべきなのに、生成AIはこれまで動いていたプログラムをいじりだそうとすることがしょっちゅうです。

 

今回も、ミニPFDと方位コンパスだけのなんちゃってHSIを2枚のOLEDで実現した後、該当箇所を取り換えて速度計と高度計を作ろうとしたときのことです。うまく動かなかったら早速変えたところと関係のない箇所を変えようとしだしました。いままでちゃんと動いていたところを変更して、動かなくなったらどうするの。万事がこれですから、要注意です。他にも右と左、上下もでしょうか、よく間違うようです。迷路に落ち込まない、迷路から早く抜け出すためには、間違っている、おかしいと感じたら早目はやめに指摘して、軌道修正してやることが大事です。

 

■PFDの改良、なんちゃってHSI・簡素な速度計・2針式高度計の作成

OLEDに表示された速度計と方位計

↑ヌルヌル目盛の動くミニPFD、東西南北しか目盛りのないHSI

 

今回のミニPFDでは、水平儀の左右にある速度計と高度計の上下する目盛部分を、ほんのわずかですが上下の数字に隙間をつくり棒線を入れ、四角い枠線の中は実際の値を表示させるなど本物に少しでも似せようと改善しました。あまりに液晶画面が小さいもので、 その努力が実ったとは言えません。

 

HSIといってもただの方位コンパスにすぎませんが、方位を示す角度の数字を加えると、液晶画面の狭さから数字同士が重なってどうにもみっともないものにしかならず、結局東西南北の方位を示すだけの見栄えのしないものになりました。

 

軽飛行機風の速度計と高度計の作成では、方位コンパスの失敗に学び、円形の目盛の外側に数字を配置することにしました。字同士の重なりはなくせましたが、目盛りの円形が少々小さくなり、その中に速度や高度の実際値を表示させるようにしたものですから、針が見えにくくなってしまいました。なお高度計は2針式で、長身が100ft刻み、短針が1000ft刻みを表します。

 

やはり0.96インチのOLEDでは画面が狭すぎますが、成功するかどうかのわからないリアル計器パネルを作るのもどうかと思うし、そのために金をかけるわけにもいきません。それでも、Aliexpressで5個920円(現在790円)という1個200円を切る値段で販売されていたものですから、ついポチってしまいました。

 

とはいえ、4計器や6パック(速度計、姿勢指示器、高度計、旋回計、方位計、昇降計)の実現は、ハードの改造が伴うのでハードルが高そう。とりあえず、ここまでできたことを良しとすべきかも知れません。

 

【参考】

 

速度計と高度計を例に、2枚のOLEDを使ったプログラムを掲載します。

 

使い方は「FlightGearのデータを外部表示する(4)」までのブログ記事をご覧ください。

 

・fgdata.xml

 

<?xml version="1.0"?>
<PropertyList>
 <generic>
  <output>
   <line_separator>\n</line_separator>
   <var_separator>,</var_separator>
   <chunk>
     <name>airspeed</name>
     <node>/velocities/airspeed-kt</node>
   </chunk>
   <chunk>
     <name>altitude</name>
     <node>/position/altitude-ft</node>
   </chunk>
  </output>
 </generic>
</PropertyList>

 

このxmlファイルは、FlightGearのデータファルダ内のProtocolサブフォルダに置きます。

 

・main.py

 

from machine import Pin, I2C
import ssd1306
import sys
import math

# ========= I2C =========
i2c0 = I2C(0, scl=Pin(1), sda=Pin(0))
i2c1 = I2C(1, scl=Pin(3), sda=Pin(2))

oled1 = ssd1306.SSD1306_I2C(128, 64, i2c0)
oled2 = ssd1306.SSD1306_I2C(128, 64, i2c1)

speed = 0
alt = 0

# ========= 右寄せ =========
def draw_right(oled, text, y, right_edge):
  x = right_edge - len(text) * 8
  oled.text(text, x, y)

# ========= USB =========
def read_usb():
  global speed, alt
  try:
    line = sys.stdin.readline()
    if line:
      s = line.strip().split(",")
      if len(s) >= 2:
        speed = float(s[0])
        alt = float(s[1])
  except:
    pass

# ========= 速度計 =========
def draw_speed(oled, speed):
  cx, cy = 64, 32
   r = 22

  oled.fill(0)

  # 外円
  oled.ellipse(cx, cy, r, r, 1)

   # ===== 円内タイトル =====
  oled.text("SPD", cx-12, cy-r+10)

  # ===== 目盛り =====
  for v in range(0, 181, 20):
    if v == 180:
      continue

    ang = math.radians(-90 + (v / 180) * 360)

    x1 = int(cx + (r-2) * math.cos(ang))
    y1 = int(cy + (r-2) * math.sin(ang))
    x2 = int(cx + r * math.cos(ang))
    y2 = int(cy + r * math.sin(ang))
    oled.line(x1, y1, x2, y2, 1)

    xt = int(cx + (r+6) * math.cos(ang))
    yt = int(cy + (r+6) * math.sin(ang))

    if 90 < v < 180:
      xt -= 14
    elif v < 90:
      xt += 4

    oled.text(str(v), xt-6, yt-4)

  # ===== 針 =====
  ang = math.radians(-90 + (speed / 180) * 360)
  x = int(cx + (r-5) * math.cos(ang))
  y = int(cy + (r-5) * math.sin(ang))
  oled.line(cx, cy, x, y, 1)

  # ===== 円内 実測値(右寄せ)=====
  draw_right(oled, str(int(speed)), cy + r - 16, cx + r - 10)

  oled.show()

# ========= 高度計 =========
def draw_alt(oled, alt):
  cx, cy = 64, 32
  r = 22

  oled.fill(0)

  # 外円
  oled.ellipse(cx, cy, r, r, 1)

  # ===== 円内タイトル =====
  oled.text("ALT", cx-12, cy-r+10)

  # ===== 目盛り =====
  for v in range(0, 10):
    ang = math.radians(-90 + v * 36)

    x1 = int(cx + (r-2) * math.cos(ang))
    y1 = int(cy + (r-2) * math.sin(ang))
    x2 = int(cx + r * math.cos(ang))
    y2 = int(cy + r * math.sin(ang))
    oled.line(x1, y1, x2, y2, 1)

    xt = int(cx + (r+6) * math.cos(ang))
    yt = int(cy + (r+6) * math.sin(ang))
    oled.text(str(v), xt-4, yt-4)

  # ===== 長針(100ft)=====
  ang1 = math.radians(-90 + (alt % 1000) / 1000 * 360)
  x1 = int(cx + (r-4) * math.cos(ang1))
  y1 = int(cy + (r-4) * math.sin(ang1))
  oled.line(cx, cy, x1, y1, 1)

  # ===== 短針(1000ft)=====
  ang2 = math.radians(-90 + (alt % 10000) / 10000 * 360)
  x2 = int(cx + (r-10) * math.cos(ang2))
  y2 = int(cy + (r-10) * math.sin(ang2))
  oled.line(cx, cy, x2, y2, 1)

  # ===== 円内 実測値(右寄せ)=====
  draw_right(oled, "{:5d}".format(int(alt)), cy + r - 16, cx + r - 4)

  oled.show()

# ========= LOOP =========
while True:
  read_usb()
  draw_speed(oled1, speed)
  draw_alt(oled2, alt)

ここに示したプログラムはChatGPTの助けを借りて作成しています。

 

このMicroPythonプログラム(main.py)は、Pico Wに書き込みます。

 

・FlightGearの起動オプション・fg-pc_bridge.py・start_fg.bat

 

FlightGearの起動オプションやfg-pc_bridge.py、start_fg.batについては割愛します。
 

 

《関連》