ソースに絡まるエスカルゴ

貧弱プログラマの外部記憶装置です。

【KiCad/Library Loader】Library Loaderを使ってKiCadに電子部品を追加する

 KiCadの勉強をしていたところ、必要な電子部品がデフォルトのライブラリになくちょっと困っていました。
 検索していると自作をする方法が出てきたのですが、できる限り楽をしたいと探したところ、KiCadで読み込むための電子部品データがすでに色々あることがわかりました。

 そしてそれらのデータはLibrary Loaderというのを使えば楽にKiCadに取り込めることがわかったので、今回はその備忘録になります。


 では、始めます。


1:Library Loaderのインストール
 以下のページを開きます。

 赤枠で囲った「Download」ボタンをクリックしてzipファイルをダウンロードします。

 ダウンロードしたzipファイルを解凍すると以下のように2つのフォルダができますが「Library Loader」の方のフォルダを開きます。

 Library Loaderフォルダ内にあるインストーラをダブルクリックして起動します(画像のバージョンとは違っている場合があります)。

 インストーラを起動すると以下のような画面が表示されるので「Next」をクリックします。基本的にデフォルトのまま進んで問題ありません。

 インストール先と使用ユーザの選択画面になるので、特に変更する必要がない場合はデフォルトのままで「Next」をクリックします。

 インストールが始まったら終わるまで待ちます。

 インストールが終了すると以下のような表示になるので「Close」をクリックしてインストーラを終了させます。

 デスクトップに以下のアイコンが追加されていればインストール完了です。


2:Library Loader初回起動時のアカウント登録と設定
 デスクトップのショートカットアイコンをダブルクリックして「Library Loader」を起動させます。

 初回起動時には以下のようにSamacSysのアカウント登録を求められます。このアカウント登録を行わないとLibrary Loaderが使えないので、各項目を入力して登録を行ってください。

 すでにSamacSysアカウントを持っている場合は「Login/Reset Password」のタブから各項目を入力してログインします。

 アカウント登録後、Library Loader画面の「Downloads Folder」の「Browse」をクリックして、電子部品のファイルが置かれたときに自動的に追加してくれるフォルダを指定します。基本的にはデフォルトのダウンロードフォルダを指定するのが楽かと思います。

「Your ECAD Tool」のプルダウンから使用するソフトを設定します。今回はKiCadなので「KiCad EDA」を選択します。

 次にLibrary Loaderの「Settings」ボタンをクリックします。

 以下の画面が表示されるので「Browse」ボタンを押して、Library Loaderの部品追加ファイルの保存先を指定し「OK」をクリックします。

 これでLibrary Loader側の設定は完了です。


3:KiCadでのライブラリ読み込み設定
 2まで終わったらKiCadにシンボルとフットプリントの追加設定をします。

 KiCadを起動させて「シンボルエディター」を開きます。

 シンボルエディター画面が立ち上がったら「設定」→「シンボルライブラリーを管理」をクリックします。

 シンボルライブラリー画面の下の方にある「テーブルに既存ライブラリーを追加」のアイコンをクリックします。

 2で設定した保存フォルダにできた「SamacSys_Parts.lib」を選択して「OK」をクリックします。

 追加されていることを確認し「OK」をクリックします。

 これでシンボルの追加設定が終わったので、シンボルエディターを閉じます。

 次に「フットプリントエディター」を開きます。

 フットプリントエディター画面が立ち上がったら「設定」→「フットプリントライブラリーを管理」をクリックします。

 フットプリントライブラリー画面の下の方にあるフォルダのアイコンをクリックします。

 2で設定した保存フォルダにできた「SamacSys_Parts.pretty」を選択して「OK」をクリックします。

 追加されていることを確認し「OK」をクリックします。

 これでフットプリントの追加設定も終了です。


4:Library Loaderを使って電子部品を追加する
 ようやく設定がすべて終わったので、実際にLibrary Loaderを使って電子部品を追加してみます。

 まずはLibrary Loaderを起動させます。

 Library Loaderのメニューにある「Search for Part(Change)」をクリックし、出てきたダイアログから探したいサイトを選択して「OK」をクリックします。今回はデフォルトの「SamacSys」を選択しています。

 するとブラウザが開いて以下のパーツ検索のサイトが表示されます。

 なんでも良いのですが、今回は秋月にあるTVDP01-G73BB with Black capの部品を検索してみます。

 ヒットしたので商品名をクリックして詳細ページを開きます。

 型番や形が合っているかを確認して「Download ECAD Model」をクリックします。

 サインインしてない場合は以下のページに飛ぶので、右上の「Sign In」をクリックします。

 2のLibrary Loader初回起動時に作成したSamacSysアカウントのメールアドレスとパスワードを入力し、「Sign In」をクリックします。

 サインインできない場合は新規にアカウント登録を行ってください。

 Library Loaderが起動した状態でECAD Modelのzipファイルのダウンロードが終了するとパーツが追加されます。

 ちなみにすでにパーツが登録されている場合は、以下のように上書きするかどうか尋ねられるでどちらにするかを選択します。

 あとはKiCadを開けばシンボルとフットプリントが追加され、使えるようになっています。


 以上がLibrary Loaderを使ってKiCadに電子部品を追加する方法になります。

 設定は面倒ですが、すでに作られているモデルをダウンロードするだけで楽に追加できるので何かと便利だと思います。
 また今回はSamacSysで部品を検索しましたが、他にも以下のサイトでもアカウント登録すれば電子部品のECAD Modelをダウンロードできるようなので、アカウントを作っておくと良いかもしれません。


・参考資料

【GAS/Googleスプレッドシート】Googleドライブのフォルダ内のファイル一覧とファイルリンク、最終更新日時を取得する

 Googleドライブのとあるフォルダでファイルを管理しようと思っていたのですが、煩雑になりがちなのでフォルダ内のファイル一覧のスプレッドシートが欲しいと思うようになってきました。
 その一覧のファイルでファイルへのリンクと最終更新日時とかをGoogle Apps Script(GAS)の処理で簡単に見れるようにしたいと色々調べて試行錯誤したところ、実現できたので今回はその備忘録になります。


 では、始めます。


1:新規にスプレッドシートを作成する
 どこでも良いので、Googleドライブ上で右クリックして「Googleスプレッドシート」をクリックして作成します。

 何でもよいですが、ここでは新規作成したスプレッドシートの名を「ファイル一覧取得」として開き、以下のように表のひな形を作成します。各表の中身は空にしておきます。

 最終更新日時の列のデータ部分は以下のように「日時」の表示にしておきます。

 これでGASを実行して入力する表ができました。


2:Drive API ドキュメントのサービスを追加する
 表のひな形を作ったら、スプレッドシートのメニューにある「拡張機能」→「Apps Script」をクリックします。

 無題のプロジェクトのGASの画面が開くので、何でもよいですがここではプロジェクト名を「ファイル一覧取得」として「名前を変更」をクリックします。

 次に今回必要なサービスを追加します。左側メニューの「サービス」と書かれている横にある「+」をクリックします。

 サービスを追加の画面が表示されるので「Drive API ドキュメント」を選択した状態で「追加」ボタンをクリックします。

 サービスの下に「Drive」が表示されていれば、サービスの追加はできています。


3:GASを記述する
 サービスを追加できたら、左側メニューにある「コード.gs」をクリックし、以下のスクリプトを記述します。

// 定数
const FOLDER_URL_ROW = 1; // フォルダURL行番号
const FOLDER_URL_COL = 2; // フォルダURL列番号
const START_ROW = 4;      // データ入力開始行番号
const START_COL = 1;      // データ入力開始列番号
const NO_FILE_CELL = -1;  // ファイル名が見つからなかった時の行番号


// スプレッドシートを開いたときに実行される関数
function onOpen() {
  // ファイルをOpenした時にメニューを追加
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var menuEntries = [];

  menuEntries.push({name: "ファイル一覧取得", functionName: "getFilesInfo"});

  // メニューを追加
  ss.addMenu("追加メニュー", menuEntries);
}

// 構造体(ファイル名, リンクURL, 最終更新日時)
function fileInfo(name, link, lastUpdate) {
  this.name = name;
  this.link = link;
  this.lastUpdate = lastUpdate;
}

// ファイル情報を取得して対象シートに記入する関数
function getFilesInfo() {
  let fileNames = [];
  let fileLinks = [];
  let fileLastUpdate = [];

  // シートを取得
  let ss = SpreadsheetApp.getActiveSpreadsheet();
  let activeSheet = ss.getActiveSheet();

  // フォルダのリンクからフォルダのIDを取得
  let folderLink = activeSheet.getRange(FOLDER_URL_ROW, FOLDER_URL_COL).getValues()[0][0];
  let split = folderLink.split('/');
  let folderId = split[split.length-1];

  // ファイル一覧を取得(降順)
  let files = DriveApp.getFolderById(folderId).getFiles();

  // ファイル名、URL、最終更新日を取得
  while (files.hasNext()) {
    let file = files.next();
    fileNames.push(file.getName());
    fileLinks.push(file.getUrl());
    fileLastUpdate.push(file.getLastUpdated());
  }

  // ファイルの個数を取得
  const size = fileNames.length;

  // 行を追加して履歴をコピー
  activeSheet.insertColumnAfter(START_COL+2);
  const copyRange = activeSheet.getRange(START_ROW-1, START_COL+2, activeSheet.getMaxRows(), 1);
  const destination = activeSheet.getRange(START_ROW-1, START_COL+3, activeSheet.getMaxRows(), 1);
  copyRange.copyTo(destination, SpreadsheetApp.CopyPasteType.PASTE_NORMAL);
  activeSheet.getRange(START_ROW-1, START_COL+3).setValue("ファイル更新履歴");

  // 最終行を取得(対象行の最終行から上に検索して最初に出てきたデータ行)
  let lastRow = activeSheet.getRange(activeSheet.getMaxRows(), START_COL).getNextDataCell(SpreadsheetApp.Direction.UP).getRow();

  // セルに記入
  for (let i = size-1; i >= 0; i--) {
    let targetRow = getFileNameRow(activeSheet, lastRow, fileNames[i]);
    if (targetRow == NO_FILE_CELL) {
      // 新規ファイルの場合は行を追加
      lastRow++;
      activeSheet.getRange(lastRow, START_COL).setValue(fileNames[i]);
      activeSheet.getRange(lastRow, START_COL+1).setValue(fileLinks[i]);
      activeSheet.getRange(lastRow, START_COL+2).setValue(fileLastUpdate[i]);
    } else {
      // 既存ファイルの場合はURLと更新日時を更新
      activeSheet.getRange(targetRow, START_COL+1).setValue(fileLinks[i]);
      activeSheet.getRange(targetRow, START_COL+2).setValue(fileLastUpdate[i]);
    }
  }

  SpreadsheetApp.getUi().alert("記入処理が終了しました。");
}

// ファイル名の行を取得する関数
function getFileNameRow(activeSheet, lastRow, filename) {
  for (let i = START_ROW; i <= lastRow; i++) {
    let cellValue = activeSheet.getRange(i, START_COL).getValue();
    if (cellValue == filename) {
      return i;
    }
  }
  return NO_FILE_CELL;
}

 詳しくは内容を読んでいただきたいのですが、簡単に説明します。

 onOpen()関数でスプレッドシートのファイルが開いたとき、スプレッドシートの上メニュー部分に「追加メニュー」というのを追加しています。そしてその中の「ファイル一覧取得」をクリックするとメインとなるスクリプトが実行されるように設定しています。

 getFilesInfo()がメインとなる関数で、フォルダのURLからID部分だけを抜き出してファイル一覧を取得しています。「let files = DriveApp.getFolderById(folderId).getFiles();」でファイル一覧を取得しているのですが、どうやら降順に取得しているようなので最後の記入するところで逆から処理するようにしています。
 ファイル名、URL、最終更新日時を取得した後、最終更新日時の列を丸ごとコピーして一つとなりの行に追加しています。このようにすることでファイルの更新履歴も追えるようにしています。
 更新日時列のコピー後、新規ファイルであれば新しく行を追加、既存のものはURLと更新日時を更新、という処理にしています。

 またエラー処理は細かくやっていないので、そのあたりは注意してください。


4:実際に実行する
 3のスクリプトを記述できたら、一度スプレッドシート自体を開き直します。

 フォルダURLのセル(B1)に一覧として表示させたいURLを記入します。以下のようなxxxx...となる部分が固有の値になっているものをコピペして入力してください。

https://drive.google.com/drive/folders/xxxxxxxxxxxxxxxxxxxxxxxxxxx

 入力できたら今回GASで追加した「追加メニュー」→「ファイル一覧取得」をクリックします。

 初回の場合は以下のように承認が必要になるので「承認」をクリックします。

 クリックすると以下のようにアカウント一覧が表示されるので、承認したいアカウントを選択します。

 このアプリはGoogleで確認されていません、という画面が出てくるので左下の「詳細」をクリックします。

 「ファイル一覧取得(安全ではないページ)に移動」をクリックします。

 許可を求める画面になるので、確認して「許可」をクリックします。

 これで使用できるようになったので、もう一度「追加メニュー」→「ファイル一覧取得」をクリックして実行します。

 スクリプトがエラーなく終了すると、以下の画面が出てくるので「OK」をクリックします。

 以下のようにファイル一覧とURL、最終更新日時が記入されています。

 ちなみに今回対象としたGoogleドライブの中身は以下のようになっています。対象フォルダ内にあるフォルダは対象外となっています。


 以上が、GASでGoogleドライブのフォルダ内のファイル一覧とファイルリンク、最終更新日時を取得する方法になります。

 例えば定期的なcsvなどのファイル一覧をこのように管理する、というのも一つの方法だと思います。
 色々と使える幅は広いように感じているので、この記事を読んでいただいた方の参考になれば幸いです。


・参考資料

【KiCad】KiCadをインストールしてみる

 今までちょっとした電子回路を作成しユニバーサル基板にはんだ付けをして色々と作ってきましたが、ちゃんとした基板を一度作ってみたいと思うようになってきました。

 KiCadと呼ばれるオープンソースでちゃんとした発注用の基板を作成できることを知ったので、今回はKiCadをインストールしてみる記事になります。


 では、始めます。


1:KiCadのインストール
 まずは以下の公式ページを開きます。

 ページを開いたところにある「Download」をクリックします。

 インストール対象のOS一覧が表示されるので選択します。今回はWindows11にインストールしたいのでWindowsを選択しました。

 ダウンロードするサーバを選択ですが、どこを選んでも問題ないと思います。今回は一番上のところを選択しました。

 クリックすると寄付をお願いする画面になりますが、自動的にインストーラのダウンロードが始まります。インストーラは1GBほどあるので気長に待ちます。

 ダウンロードしたインストーラをダブルクリックで起動させます。

 インストーラの指示に従ってインストールを行います。基本的にはデフォルトのまま「Next」をクリックしていくだけで問題ないです。

 最後に「Finish」をクリックすればKiCadのインストールは完了です。


2:KiCadを起動してみる
 デスクトップに追加されたKiCadのアイコンをダブルクリックして起動させます。

 初回起動時に以下のような表示が出るので、過去バージョンを使っていた場合は上の方を選択して設定をインポートしてください。初めてKiCadを使う場合は下の方を選択します。今回は初めて使うので下の方を選択しています。

 初期設定が完了すると以下のような画面になります。


 以上がKiCadをインストールしてみるまでになります。ただインストールしただけですが、色々と機能があるようなので勉強して自作基板を発注するところまでやってみたいと思っています。


・参考資料

【git】勝手に改行コードが変更される場合の対処

 Windowsにgitを入れちょうどシェルスクリプトを書いている時に「いつの間にか改行コードが変わっている」ことに気が付きました。

 今回はその改行コードの自動変更の設定を解除したりする方法の備忘録になります。詳しい説明は参考資料に挙げているページ様が詳しいのでそちらを参照してください。


 では、始めます。


1:gitでの改行の設定
 gitの公式サイトからインストーラをダウンロードし、それをWindowsでインストールした場合、特に何も設定を変更していない場合は以下のような設定になっているはずです。

チェックアウト時 コミット時
LF → CRLF CRLF → LF

 またGitのインストール後に以下のコマンドを実行した中の「core.autocrlf」の項目から今の設定を確認することができます。

$ git config -l


2:gitでの改行設定の変更
 gitの改行設定を変更する場合は以下のコマンドを実行します。

$ git config --[スコープ] core.autocrlf [設定値]

 設定値のところはtrue/input/falseのいずれかを設定します。それぞれどのようになるかは以下の表の通りになります。

設定値 チェックアウト時 コミット時
true LF → CRLF CRLF → LF
input 変更しない CRLF → LF
false 変更しない 変更しない

 自分の場合は改行コードが変換されると嫌なので、現在ユーザ共通としてfalseを設定するために以下のコマンドを実行しました。

$ git config --global core.autocrlf false

 確認ちゃんとglobalに設定されているかを確認するために以下のコマンドを実行します。

$ git config -l --global

 この結果に「core.autocrlf=false」が含まれていれば設定完了です。


 以上がgitで勝手に改行コードが変更される場合の対処方法になります。

 シェルの場合、改行コードが変更されると動かなくなるので気づかないうちにそういう変換なされていてかなり混乱しました。

 改行コードが自動的に変換される方が楽な場合もありますが、デフォルトでは改行コードは変換されないようにしてほしかったですね…。


・参考資料

【python】関数の引数として関数を渡す

 pythonを書いていて関数の引数として関数を渡したい時が出てきたので調べたところ、できるようだったので今回はその備忘録になります。


 では、始めます。


1:関数の引数として関数を渡す方法
 やり方は簡単で、以下のように引数として関数名を指定するだけです。

#-*- coding:utf-8 -*-
# 引数で渡したい関数
def called_func():
    print("called_func")

# 引数の関数を実行する関数
def call_func(func):
    func()

# 引数に関数を渡して実行
call_func(called_func)

 やり方がわかったので実際に簡単なサンプルソースを動かしてみます。


2:サンプルソース
 以下のソースコードを実行してみてください。

・sample.py

#-*- coding:utf-8 -*-
def add(a, b):
    return a + b

def multiply(a, b):
    return a * b

# 引数の関数を実行して結果を表示する関数
def run_func_print(func, a, b):
    print(func(a, b))

def main():
    run_func_print(add, 1, 2)
    run_func_print(add, 8, 9)

    run_func_print(multiply, 1, 2)
    run_func_print(multiply, 8, 9)

if __name__ == '__main__':
    main()

 実行するとちゃんと引数の関数が実行されていることがわかると思います。


3:注意すべき点
 引数に関数を渡せることを知っていれば便利だとは思いますが、注意すべきところがあります。

 例えば以下のソースコードを実行するとエラーになります。

・sample2.py

#-*- coding:utf-8 -*-
def add2(a, b, c):
    return a + b + c

def run_func_print(func, a, b):
    print(func(a, b))

def main():
    run_func_print(add2, 1, 2)

if __name__ == '__main__':
    main()

 これは引数として渡した関数(add2)の引数は3つであるのに、実行する関数(run_func_print)では引数を2つしか渡していないためにエラーとなっています。

 このように引数に関数を渡した場合、渡す関数の引数に何らかの変更があるとその影響をそのまま受けるため、引数の関数を実行する関数にも修正が必要となります。

 関数同士がかなり密接に影響し合うため、注意していないとバグやエラーを出してしまうことになります。


 以上がpythonで関数の引数として関数を渡す方法になります。

 個人で開発する場合には裏技的な感じで使用するのもアリかもしれないですが、チームや仕事としてやる場合にはバグを誘発したりメンテナンス性や可読性が著しく下がると思うのでやらない方が良いかと個人的には思います。

 そもそも関数を渡して処理させるような設計にしない方がよほど重要だと思います。


・参考資料

【python/OpenCV】OpenCVのimread関数で扱うパスに全角を含めてはいけない

 タイトルにもありますが、OpenCVのimread関数で画像を読み込もうとしたらエラーになってずっと割と躓いてしまったので、今回はその備忘録になります。
 詳しくは参考資料に挙げているページ様を参照してください。


 では、始めます。


1:OpenCVのimread関数の引数のパスに全角が入っているとエラーになる
 まずはエラーを再現するために、以下のソースコードをコピペして保存してみてください。

・imread_test.py

#-*- coding:utf-8 -*-
import os, sys
import cv2

my_dir_path = os.path.abspath(os.path.dirname(sys.argv[0]))
my_file_path = my_dir_path + "\\テスト.png"

print(my_file_path) # パスの表示

frame = cv2.imread(my_file_path) # 画像取得

 保存出来たらソースファイルと同じ階層に「テスト.png」という画像を作成して保存します。

 そしてソースコードを実行すると以下のようなエラーになります。

$ python imread_test.py
C:\xxxx\テスト.png
[ WARN:0@0.015] global D:\a\opencv-python\opencv-python\opencv\modules\imgcodecs\src\loadsave.cpp (239) cv::findDecoder imread_('C:\xxxx\テスト.png'): can't open/read file: check file path/integrity

 Windowsの場合だと文字化けしているかもしれませんが、同じようにエラーになるかと思います。

 読み込むファイルパスのどこかに一つでも「全角文字」があると発生するので、絶対に全角が入らないパスが来るという前提以外はimread関数で画像を読み込むのはやめた方が良いようです。

 ちなみにファイルを保存する時に使う関数「imwrite」ではエラーが出ないことがありますが、全角文字部分が文字化けして正しく動かない場合があります。


2:imread関数を使わずに画像を読み込む・保存する
 ではどうやって画像を読み込み、保存するかという話ですが、Pillowで画像を開きNumPyへ変換してその後OpenCVの画像形式に変更します。保存はこの逆の手順を行います。

 具体的には以下のようにします。

・pillow_numpy.py

#-*- coding:utf-8 -*-
import os, sys

import cv2
import numpy as np
from PIL import Image

my_dir_path = os.path.abspath(os.path.dirname(sys.argv[0]))
my_file_path = my_dir_path + "\\テスト.png"
print(my_file_path) # パスの表示

# Pillowで画像を開いてNumpyへ変換
img_org = np.array(Image.open(my_file_path))

# OpenCVの形式に変換
cv2_img = cv2.cvtColor(img_org, cv2.COLOR_BGR2RGB)

# 全角を含むファイル名で保存
pil_img = Image.fromarray(cv2.cvtColor(cv2_img, cv2.COLOR_BGR2RGB))
pil_img.save(my_dir_path + "\\てすと.png")

 上記を実行すると今度はエラーなく画像が読み込まれ、保存ファイル名も指定した全角文字のものになります。

 この他にも色々と手順はあるので、詳しくは参考資料に挙げているページ様を参照してください。


 以上がOpenCVで画像を読み込んだり保存したりする際の注意点になります。

 今までたまたま意図せず全角文字が入らない状態で使っていたので気づきませんでしたが、パスに全角が入っているとエラーになるというのはなかなかのトラップだと思いました。
 外国では全角を使わないところが多いので、パスに全角が入る場合があるというのに対応してないのかもしれないですね…。


・参考資料

【Arduino/python】Arduino UnoのEthernetシールドでUDP通信

 以下の記事で、とりあえずArduino UnoのEthernetシールドを使ってみました。

 今回はArduino UnoのEthernetシールドとPC(python)とでUDPの送受信の通信をやってみた備忘録になります。ちなみにWindows環境で行う前提なので注意してください。

 では、始めます。


1:MACアドレスIPアドレス、ポートを決める
 UDP通信を行う際には「どの端末からどの端末へ送信するか」の情報が必要なので、それらを決める必要があります。

 PC(MacでもWindowsでも)で以下のコマンドを実行して使用されているIPアドレスを確認し、使っていないIPアドレスを選択します。

$ arp -a

 今回は以下のようにしました。MACアドレスはシールドに記載されいていればそれを使用し、書かれていない場合は使用している機器と被らないようなMACアドレスを使うようにしてください。

項目名 Arduino Uno PC
MACアドレス DE:AD:BE:EF:FE:ED 特に設定なし
IPアドレス 192.168.1.177 192.168.1.178
ポート 8888 5000


2:PC側のIPアドレスを固定する
 過去にWindowsでのIPアドレス固定方法の記事を書いているので、それに従ってIPアドレスを固定してください。


3:Arduino Uno側のプログラム
 書き込むプログラムは以下の通りです。

 MY_MAC_ADDRESS、MY_IP、MY_PORT_NUM、TARGET_SERVER、TARGET_PORTはそれぞれ1で決めたものに適宜書き換えてください。

 またEthernetシールドに搭載されているチップによって読み込むライブラリが違うので、そこも注意してください。

・UnoUdpTest.ino

/**
 * ArduinoのイーサネットシールドでUDP送受信
 */
#include <SPI.h>
#include <Ethernet.h> // W5100の場合はこちらを使う
#include <EthernetUdp.h> // W5100の場合はこちらを使う
// #include <Ethernet2.h> // W5500の場合はこちらを使う
// #include <EthernetUdp2.h> // W5500の場合はこちらを使う

const byte MY_MAC_ADDRESS[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // Arduino Uno側MACアドレス(適宜書き換える)
const byte MY_IP[]          = { 192, 168, 1, 177 }; // Arduino Uno側IPアドレス(適宜書き換える)
const int  MY_PORT_NUM      = 8888;                 // Arduino Uno側のポート(適宜書き換える)

const char TARGET_SERVER[] = "192.168.1.178"; // 送信先のアドレス(適宜書き換える)
const int  TARGET_PORT     = 5000;            // 送信先のポート(適宜書き換える)

EthernetUDP Udp; // パケットの送信と受信をするUDPのインスタンス


void setup() {
  Serial.begin(9600);
  Serial.println("--- setup start ---");

  // イーサネットサーバの開始
  Ethernet.begin(MY_MAC_ADDRESS, MY_IP);

  // イーサネットシールドが付いているかの判定
  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
    Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
    while (true) {
      delay(1); // do nothing, no point running without Ethernet hardware
    }
  }
  // イーサネットケーブルが接続されているかの判定
  if (Ethernet.linkStatus() == LinkOFF) {
    Serial.println("Ethernet cable is not connected.");
  }

  // UDP通信の開始
  Udp.begin(MY_PORT_NUM);

  Serial.println("--- setup end ---");
}

void loop() {
  int parseSize = Udp.parsePacket();

  if (parseSize) {
    // 受信した内容をそのままUDP送信
    byte sendMessage[parseSize];
    Udp.read(sendMessage,parseSize);
    sendUdp(TARGET_SERVER, TARGET_PORT, sendMessage, parseSize);
  }

  delay(500);
}

/**
 * UDP送信を行う関数
 */
void sendUdp(const char *address, int sendPort, byte *byteArray, int packetSize) {
  Udp.beginPacket(address, sendPort);
  Udp.write(byteArray, packetSize);
  Udp.endPacket();

  Serial.print("sendUdp:[ ");
  for (int i = 0; i < packetSize; i++) {
    Serial.print(char(byteArray[i]));
  }
  Serial.println(" ]");
}

 少し解説すると、UDPでの通信を受信したらその内容をそのまま送り返すという処理をしています。UDPで送信する際には送信する内容をSerialで表示させています。


4:PC側のpythonプログラム
 次にPC側のpythonプログラムです。

 TARGET_PORT、TARGET_ADDRESS、MY_PORTは1で決めたものに適宜書き換えてください。

udp_send_recieve.py

#-*- coding:utf-8 -*-
#
# PC側でのUDP送受信
#
from socket import socket, AF_INET, SOCK_DGRAM
import time

# 送信先の設定(適宜書き換える)
TARGET_PORT    = 8888
TARGET_ADDRESS = "192.168.1.177"

# PC側の設定(適宜書き換える)
MY_HOST = ''
MY_PORT = 5000


def main():
    # UDP通信用のソケット
    udp_socket = socket(AF_INET, SOCK_DGRAM)
    udp_socket.bind((MY_HOST, MY_PORT))

    count = 0
    udp_send_flg = True

    try:
        while True:
            if udp_send_flg:
                msg = "count:{}".format(count)
                # 送信
                udp_socket.sendto(msg.encode(), (TARGET_ADDRESS, TARGET_PORT))

                count = count + 1
                if count >= 10:
                    count = 0
            else:
                # 受信
                msg, address = udp_socket.recvfrom(8192)
                print(f"message: {msg} from: {address}")

            udp_send_flg = not udp_send_flg
            time.sleep(1)
    except Exception as e:
        print(e)
    finally:
        udp_socket.close()

if __name__ == '__main__':
    main()

 少し解説すると、1秒ごとにUDPの送信と受信を繰り返す処理をしています。受信した内容はprintで表示しています。


5:実行して確認
 Arduino Uno側にプログラムを書き込み、各種ケーブルをPCにつないだ状態で4のpythonを実行します。

 すると以下のようにpython側から送られたUDPのメッセージをArduino Uno側で受け取り、それをそのまま送り返していることが確認できます。


 以上がArduino UnoのEthernetシールドとPC(python)とでUDPの送受信の通信をやってみた内容になります。

 UDP通信は相手に届いたかどうかの確認はないので送りっぱなしになりますが、それを問題としないのであれば有用かと思います。

 また今回はArduino UnoのEthernetシールドを使いましたが、同じチップを搭載しているのであればESP32などの他のものでもおそらく同じようにライブラリを使ってできると思われる(未検証)ので、似たような形で実装できるかと思います。


・参考資料