virt_flyのブログ -2ページ目

virt_flyのブログ

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

Pico WでPFD作成!FlightGearデータで水平儀が動く

↑Pico Wを使ったミニPFDができました Wi-Fiで届いたFlightGearのデータをもとに水平儀が動くのは楽しい

 

Wi-Fi接続、0.96インチOLED表示、ミニPFD

操縦がしやすいのはテープ式表示の計器

 

前回の当ブログでは、FlightGearのデータを引き出してRaspberry Pi Pico WによりOLED画面に速度と高度、方位の値が表示できたことを取り上げました。

 

しかし、数字だけのデジタル表示では、リアルタイムであっても変化が速すぎる場合には、数値が読み取れないことがしばしばで、これでは気が付けば墜落にもなりかねず、計器としては何の役にも立たないことになります。

 

もう少し使い勝手の良い表示形式を検討することにしました。

 

■異なる表示形式を試す

 

・バー形式はする意味がない

 

テープ式計器表示のフライトディスプレイ

↑OLED画面上部の横のバーが速度、右端の縦のバーが高度

 

速度と高度の表示をバー形式にしてみました。バーにしたから何なのさです。結果として、バー表示は操縦にななんの意味も感じられないものでした。

 

・テープ形式は小さな画面には不向き

 

テープ式計器表示:速度、高度、方位

↑OLED画面の描画のタイミングで、方位の表示が欠けています 左右の速度と高度の数値目盛が上下します

 

計器が数字やバー表示では、機体をどれ位傾けたらよいか、どの程度でやめようかという加減がつかめません。計器がテープ形式で、針が固定に対して目盛りが動く方が、その動きの緩急で微妙な加減ができて格段と操縦がしやすくなるはずです。

 

しかし、今回はOLED画面が小さいために目盛りの間隔が大きく取れず、速度(①)も高度(②)も疑似的とは言わないまでも数値目盛りだけのテープ形式にとどまり、実際のところただ数字が並んで動くだけでは、微妙な目盛りの動きが再現できず、残念ながら操縦に役立ちません。小さい画面にはテープ形式の表示は不向きと言わざるをえません。

 

なお、方位はコンパス(③)にしてみました。

 

・ミニPFD は見て楽しむ分にはよいが

 

PFD表示:速度・高度・方位・水平儀

↑ミニPFDという感じのOLED画面

 

OLED画面が小さくて、計器が3つともなれば、十分なものはできないというのに、勢いでさらに中央に水平儀様のものまで設けて、ちょっとしたPFD(Primary Flight Display)化になりました。

 

左上が速度(①)、右上が高度(②)、一番下が方位(③)を示し、中央が簡素な水平儀(ATI)になっていて、ライン状の地平線(④)と機体のマーク(⑤)が表示されます。写真では、左旋回しながら上昇中であることがわかります。

 

見て楽しむ分にはよいのですが、速度や高度などが数値表示ではやはり実用的ではありません。

 

■水平儀とテープ式計器


・HUD風ミニPFD

 

ミニPFD 速度・高度・方位・水平儀表示

↑ミニPDFをHUD風に

 

PDF化するなら、速度や高度、方位もテープ式表示にして、水平儀の周囲に配置したい。そうすることで、慣れ親しんだFlightGearのHUD(ヘッドアップディスプレイ)風になったら、なんだか操縦も上手くできそうな気がしてきます。

 

とはいえ、0.96インチのOLED画面では狭く、多少は改善しましたが、速度や高度、方位のテープ式表示は、まだまだ実用に及びません。

 

サイズの大きいLCDディスプレイを使うか、PDF形式はやめて一つ一つの計器に独立したディスプレイを割り当てるかすれば改善できるかもしれませんが、0.96インチOLEDではこれくらいがよい塩梅なのかもしれません。

 

《参考》

 

最後にとりあげたHUD風ミニPFDについて、ソフト一式について掲載します。使い方は前回のブログ記事をご覧ください。

 

・fgdata.xml

 

<?xml version="1.0"?>
<PropertyList>
 <generic>
  <output>
   <line_separator>\n</line_separator>
   <var_separator>,</var_separator>
   <chunk>
     <name>groundspeed-kt</name>
     <node>/velocities/groundspeed-kt</node>
   </chunk>
   <chunk>
     <name>altitude-ft</name>
     <node>/position/altitude-ft</node>
   </chunk>
   <chunk>
     <name>true-heading-deg</name>
     <node>/orientation/true-heading-deg</node>
   </chunk>
   <chunk>
     <name>pitch</name>
     <node>/orientation/pitch-deg</node>
   </chunk>
   <chunk>
     <name>roll</name>
      <node>/orientation/roll-deg</node>
   </chunk>
  </output>
 </generic>
</PropertyList>

 

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

 

・main.py

 

import socket
import network
from machine import Pin, I2C
import ssd1306
import time
import math

# ---------- WiFi ----------
ssid = "
Wi-Fiネットワーク名"
password = "パスワード"

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

print("Connecting WiFi")

while not wlan.isconnected():
  time.sleep(1)

print("Connected")
print(wlan.ifconfig())

# ---------- OLED ----------

WIDTH = 128
HEIGHT = 64

i2c = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)
oled = ssd1306.SSD1306_I2C(WIDTH, HEIGHT, i2c)

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("0.0.0.0", 5501))
sock.settimeout(0.01)

speed = 0
alt = 0
heading = 0
pitch = 0
roll = 0

# ---------- 速度テープ ----------

def draw_speed_tape():

  center = int(speed/10)*10

  for i in range(-3,4):

    v = center + i*10
    y = 32 - i*8

    if 10 < y < 56:
      oled.text(str(v),0,y,1)

  oled.text(">",24,32,1)
  oled.text("SPD",8,56,1)

# ---------- 高度テープ ----------

def draw_alt_tape():

  center = int(alt/10)*10

  for i in range(-3,4):

    v = center + i*10
    y = 32 - i*8

    if 10 < y < 56:
      oled.text(str(v),96,y,1)
  oled.text("<",88,32,1)
  oled.text("ALT",88,56,1)

# ---------- 方位テープ ----------

def draw_heading():

    center = int(heading/10)*10

    for i in range(-6,7):

    h = (center + i*10) % 360
    x = 64 + i*10

    if 5 < x < 120:

      if h % 30 == 0:
        oled.text(str(h), x-6, 0, 1)

      else:
        oled.pixel(x,6,1)

   oled.text("^",62,10,1)
   oled.text("HDG",54,16,1)

# ---------- 水平儀 ----------

def draw_horizon():

  cx = 64
  cy = 36

  r = math.radians(-roll) # ← rollを反転

  length = 80

  x1 = int(cx - length*math.cos(r))
  y1 = int(cy - length*math.sin(r) + pitch)

  x2 = int(cx + length*math.cos(r))
  y2 = int(cy + length*math.sin(r) + pitch)

  oled.line(x1,y1,x2,y2,1)

  # 機体マーク
  oled.line(cx-6,cy,cx+6,cy,1)

# ---------- メインループ ----------

while True:

  try:
    data, addr = sock.recvfrom(256)

    parts = data.decode().split(",")

    speed = float(parts[0])
    alt = float(parts[1])
    heading = float(parts[2])
    pitch = float(parts[3])
    roll = float(parts[4])

  except:
   pass

  oled.fill(0)

  draw_heading()
  draw_speed_tape()
  draw_alt_tape()
  draw_horizon()

  oled.show()

 

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

 

・起動オプション

 

--generic=socket,out,10,192.168.0.xxx,5501,udp,fgdata

 

FlightGearの起動オプションに、上述の通り入力します。,192.168.0.xxx部分には送信先であるPico WのIPを記述します。

 

なお、ここで示したプログラムはCopilotとChatGPTの助けを借りて作成しています。

 

 

《関連》

 

 

 

 

 

 

 

FlightGearシミュレータとOLED表示

↑FlightGearの航空機の速度、高度、方位の変化がOLED上に刻々と示されていきます

 

Wi-Fi接続、0.96インチOLED表示

OLED画面に速度・高度・方位

 

前回の当ブログでは、フライトシミュレーターであるFlightGearのデータを引き出して、Ambientへの送信にとりくみました。

 

FlightGearの飛行中の各計器の示す値をよく見えるようにしたいというのに、Ambientにグラフ表示させていてもしかたがありません。

 

今回は、とりあえずOLEDに数値表示ができないかを試すことにしました。

 

■構成

構成は次のようなものです。

 

 FlightGear

 ↓ UDP

 Wi-Fi

 ↓

 Rasopberry Pi Pico W

 ↓ I2C

 OLED

 

■必要なものと手順

 

注意事項やPythonの利用が前提、Raspberry Pi PicoはMicroPythonの使用が前提という点は、前回同様です。

 

【ハード】

 

今回は、小型ののOLEDディスプレイに表示ということなので、機器の用意が必要になります。

 

・Raspberry Pi Pico W…Wi-Fi機能付きマイクロコントローラー

 

・OLEDディスプレイ…手持ちの0.96インチ4ピンI2C対応 ドライバSSD1306またはSSD1315

 

・他…工作用にブレッドボード、ジャンパー線など

 

OLEDとPico Wとの配線は次の通り

 

OLED Pico W
VCC 3.3V(Pin 36)
GND GND(Pin 38など)
SCL GP1(Pin 2)
SDA GP0(Pin 1)

 

Raspberry Pi Pico WとOLEDでFlightGearの飛行データを表示

↑一部隠れていますが、ブレッドボードに組まれた回路

 

【ソフト】

 

・fgdata.xml

 

プロトコルを記述したこのxmlファイルについても、前回とまったく同じものを使います。省略しますので、前回を参照こと。このファイルを、FlightGearのデータファルダ内のProtocolサブフォルダに置くのも前回同様です。

 

・main.py

 

Pico Wに書き込むMicroPythonプログラム(main.py)は、Wi-Fi接続とUDP受信の設定を記述しています。Pico Wへの書き込みはThonnyが便利です。

 

import socket
import network
from machine import Pin, I2C
import ssd1306
import time

# WiFi
ssid = "Wi-Fiネットワーク名"
password = "パスワード"

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

while not wlan.isconnected():
  time.sleep(0.5)

print("IP:", wlan.ifconfig()[0])

# OLED
i2c = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)
oled = ssd1306.SSD1306_I2C(128, 64, i2c)

# UDP sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("0.0.0.0", 5501))

oled.fill(0)
oled.text("Waiting FG...", 0, 0)
oled.show()

while True:
  data, addr = sock.recvfrom(1024)
  text = data.decode().strip()
  values = text.split(",")

  if len(values) >= 3:
    spd = values[0]
    alt = values[1]
    hdg = values[2]

    oled.fill(0)
    oled.text("SPD {:>6}".format(spd), 0, 0)
    oled.text("ALT {:>6}".format(alt), 0, 16)
    oled.text("HDG {:>6}".format(hdg), 0, 32)
    oled.show()

 

・起動オプション

 

FlightGearの起動オプションには、送信先であるPico WのIPを記述します。

実行は、CMD(コマンドプロンプト)にて次の通り入力して行います。なお、path

や飛行機名、空港名、Pico WのIPは例です。

 

"C:\Program Files\FlightGear 2024.1\bin\fgfs.exe" ^
--fg-root="C:/Users/自分のフォルダ名/FlightGear/Downloads/fgdata_2024_1" ^
--fg-aircraft="C:/Users/自分のフォルダ名/FlightGear/Custom Aircraft" ^
--aircraft=F1M2 ^
--airport=RJBB ^
--runway=24L ^
--generic=socket,out,10,192.168.0.xxx,5501,udp,fgdata

 

ちなみに、Pico WのIPの確認は、Thonnyのシェルにて以下の通り入力し実行すれば、シェルに表示されます。

 

import network
wlan = network.WLAN(network.STA_IF)
wlan.ifconfig()

 

■リアルに変化するOLED画面

 

結果、OLED画面では流れるように数字が変化していきます。その数字はFlightGear画面のHUDの目盛りから読み取れるものとまさに一致しており正確です。

 

これはいいですね。もっと見やすく大きくて計器風のディスプレイをならべたら、フライトシミュレーターの実体化ができそうじゃありませんか。

 

 

《追記》

FlightGearの/protocolに置いたfgdata.xmlは、FlightGearのpropertyから目的の状態変数を取り出してUDPで送信するために書いたものです。速度と高度、方位を取り上げましたが、実際には速度には対気速度と対地速度、方位には磁方位と真方位の違いがあり、これはFlightGearでも同じ。今回fgdata.xmlには、試行錯誤の都合でgroundspeed-kt(対地速度)とtrue-heading-deg(真方位)を記述しました。気になる方は、当該箇所のそれぞれ2か所をairspeed-ktやheading-degに書き改めてください。

 

 

《関連》

 

 

 

 

 

 

FlightGearで外部表示させた速度・高度・方位データ

↑FlightGearのデータをAmbientdeで表示させることができた グラフ化する意味はないと言えばないが

 

Wi-Fi接続、可視化クライアントサービス

Generic protocol+UDP+Ambientで実現

 

眼が悪くなり、FlightGearの飛行機の計器の値やHUDの目盛りがまるで見えなくなったのが苦痛で、手元に置いたタブレットに表示させたり、計器をこしらえて表示できたなら、と思うこの頃です。

 

先ごろ電子工作で、温度センサーの値をデータ可視化クライアントサービスのAmbientに送信してグラフを描かせることができたものだから、少々欲がでてきました。

 

FlightGearのGeneric protocolやUDPを利用した例もあるようなので、試してみようかなということで、今回のテーマは「FlightGearのデータを外部表示させよう」です。

 

いろいろ試行錯誤しつつ方向を見定めるようにしてきましたが、順次FlightGearのGeneric protocolを利用して目的のプロパティの値をUDPで送信、送信先は慣れたAmbientにするとの方向ですすめ、最終的に成功しましたので報告とします。

 

Ambientでグラフを描いても仕方がないのですが、まずは外部送信を試すことが目的でしたので、今回はご勘弁を。

 

■必要なものと手順

 

ここではWindowsで説明します。

今回は、速度と高度、方位の3項目を送信することにしましたが、他にも様々な値を扱うことができるでしょう。

 

・注意事項

 

最新のFlightGearはディレクトリ構造が変更になったようで、以前ならROOTディレクトリ直下にサブディレクトリとしてあったものが、別途指定した場所に変わってしまっており、FlightGear起動時のオプション指定もややこしくなりました。

 

・Python

 

Pythonでプログラムしますので、あらかじめPythonがインストールされている必要があり、requestsなど必要なものはpip installします。

※Pythonインストール時には、「Add Python 3.x to PATH」にチェックを入れること

 

なお、Raspberry Pi PicoはMicroPythonを使用します。

 

・fgdata.xml

 

プロトコルはfgdata.xmlに記述し、FlightGearのデータファルダ内のProtocolサブフォルダに置きます。

 

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

 

・fg_ambient.py

 

データを送信させるためのPythonプログラムを記述し、fg_ambient.py名でC:/Users/自分のフォルダ名/下に置きます。

 

import socket
import json
import requests
import time
import math

# FlightGearからのUDP受信設定
UDP_IP = "127.0.0.1"
UDP_PORT = 5501 # FlightGearの送信ポートに合わせる

# Ambientの設定(自分のチャネルIDとライトキーを入れる)
AMBIENT_CHANNEL_ID = チャネルID
AMBIENT_WRITE_KEY = "ライトキー"
AMBIENT_URL = f"http://ambidata.io/api/v2/channels/{AMBIENT_CHANNEL_ID}/data"

# UDPソケットの準備
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))

print("受信開始...")

while True:
  try:
    data, addr =sock.recvfrom(1024)
    decoded = data.decode("utf-8").strip()
    print("受信データ:", decoded)
    values = decoded.split(",")

     # FlightGearの送信順に合わせてインデックスを調整
     groundspeed = float(values[0]) # 速度(ノット)
     altitude = float(values[1]) # 高度(フィート)
     heading = float(values[2]) # 方位(度)

     # 無効な値をチェック
     if any(map(lambda x: x is None or math.isnan(x), [groundspeed, altitude, heading])):
      print("無効なデータを検出、送信スキップ")
      continue

     print(f"Groundspeed: {groundspeed}, Altitude: {altitude}, Heading: {heading}")

     # Ambientに送信するデータ
     payload = {
       "writeKey": AMBIENT_WRITE_KEY,
       "d1": groundspeed,
       "d2": altitude,
       "d3": heading
     }

     headers = {"Content-Type": "application/json"}
     response = requests.post(AMBIENT_URL, data=json.dumps(payload), headers=headers)

     if response.status_code == 200:
       print(" Ambientに送信成功!")
     else:
       print(f" 送信失敗: {response.status_code} - {response.text}")

     time.sleep(1) # 送信間隔を1秒に調整

     except Exception as e:
       print("エラー:", e)
       time.sleep(2) # エラー時は少し待って再試行

 

実行は、CMD(コマンドプロンプト)で以下の通り入力して行います。

 

C:/Users/自分のフォルダ名>python fg_ambient.py

 

 

・起動オプション

 

FlightGearの起動オプションでfgdata.xmlの読み込を指定します。

実行は、CMD(コマンドプロンプト)で以下の通り入力して行います。pathや飛行機、空港は例です。

 

C:/Users/自分のフォルダ名〉"C:\Program Files\FlightGear 2024.1\bin\fgfs.exe" --fg-root="C:/Users/自分のフォルダ名/FlightGear/Downloads/fgdata_2024_1" --fg-aircraft="C:/Users/自分のフォルダ名/FlightGear/Custom Aircraft" --aircraft=F1M2 --airport=RJBB --runway=24L --generic=socket,out,0.1,127.0.0.1,5501,udp,fgdata

 

下の画像は、パイソンプログラムfg_ambient.pyを実行したCMD画面です。FlightGearで飛行中のデータが表示されています。

 

FlightGearデータ、速度・高度・方位をAmbientに表示

↑Pythonプログラムfg_ambient.pyを実行したCMD画面 飛行中の速度、高度、方位の値が流れています

 

今回成功したのは、fgdata.xmlの記述に<node>を使用、<var_separator>を明示したからかと思われます。Copilotに助けられました。

 

 

《関連》