2024年6月9日日曜日

Seleniumをpowershellから使う(1)

 

webページの操作を自動化できるSelenium。これをpowershellから使おうっていう試み。

色んな会社のシステムってWebブラウザを使ってることが多いんじゃないかと思う。で、クソ遅いとかシステムによって使用感が統一されていないとか色々不満があると思う。、、、知らんけど。いや、知っている。まじgmks。
で、Seleniumを使うと、RPAのようにPCを占有することもなくWebブラウザでの操作を自動化できるので、いくつかのシステムの連携もローカルでこなせるわけで、使わない手はない。ところで、RPAのはしり、UWSCが登場したのが1999年。2010年ごろマイブームが来たんだけど、PCを占有されるってので、使い方が難しいなーって思ってた。結局、ぼーっっと待ってるんよ。終わるのを。もちろんブラウザ以外のアプリも操作できるというRPAの利点はある。

で、参考サイトはこちら(https://qiita.com/_YuruTa_/items/91224e038ae565fb1a83)(いつも先人の足跡をたどっているだけなのに、えらそうなこと言うんじゃないって怒られそうやな、、、)。
まずは管理者モードでPowerShellを起動する。で、
install-module -name Selenium
って入力する。そすっと
こうなる。迷わず「Y」押してENTER。
なんか知らんけど、信頼されていないらしい。信頼を得るには地道な積み重ねが必要だよなーと思いつつ、「Y」ENTER。そすっと、上のほうにこしょこしょ青い画面が出た後、何事もなかったようにプロンプトが表示される。(ここまででつまづいたら上記参考サイトをみるべし)、、で、たぶんこれでいいんだろう。
で、参考サイトに倣って、
get-command -module selenium
ってすると、なんと、
  • CommandType Name Version Source
  • ----------- ---- ------- ------
  • Alias Enter-SeUrl 3.0.1 selenium
  • Alias Find-SeElement 3.0.1 selenium
  • Alias Start-SeLegacyEdge 3.0.1 selenium
  • Function Clear-SeAlert 3.0.1 selenium
  • Function Get-SeCookie 3.0.1 selenium
  • Function Get-SeElement 3.0.1 selenium
  • Function Get-SeElementAttribute 3.0.1 selenium
  • Function Get-SeElementCssValue 3.0.1 selenium
  • Function Get-SeKeys 3.0.1 selenium
  • Function Get-SeSelectionOption 3.0.1 selenium
  • Function Get-SeWindow 3.0.1 selenium
  • Function Invoke-SeClick 3.0.1 selenium
  • Function Invoke-SeScreenshot 3.0.1 selenium
  • Function New-SeScreenshot 3.0.1 selenium
  • Function Open-SeUrl 3.0.1 selenium
  • Function Remove-SeCookie 3.0.1 selenium
  • Function Save-SeScreenshot 3.0.1 selenium
  • Function Send-SeClick 3.0.1 selenium
  • Function Send-SeKeys 3.0.1 selenium
  • Function SeOpen 3.0.1 selenium
  • Function SeShouldHave 3.0.1 selenium
  • Function Set-SeCookie 3.0.1 selenium
  • Function SeType 3.0.1 selenium
  • Function Start-SeChrome 3.0.1 selenium
  • Function Start-SeEdge 3.0.1 selenium
  • Function Start-SeFirefox 3.0.1 selenium
  • Function Start-SeInternetExplorer 3.0.1 selenium
  • Function Start-SeNewEdge 3.0.1 selenium
  • Function Start-SeRemote 3.0.1 selenium
  • Function Stop-SeDriver 3.0.1 selenium
  • Function Switch-SeFrame 3.0.1 selenium
  • Function Switch-SeWindow 3.0.1 selenium
ってなる。すなわち-AllowPrereleaseがないとSelenium3がインストールされる。まぁこれでチャレンジしてみるっス。で、web driverをとってくる。chromewebdriver.exeでぐぐると、ここ(https://developer.chrome.com/docs/chromedriver/downloads?hl=ja)がひっかかる。で、微妙にわかりにくいけど、
からの
からの
これを、コピーして、ブラウザのアドレスに貼り付けてダウンロード。
落ちてきたzipファイルの中からchromedriver.exeをよさげなところにコピペしておく。

さらに、google chromeブラウザの実行ファイルの場所を調べておく。だいたい「C:\Program Files\Google\Chrome\Application」にある。

では、普通にPowerrshellを起動する。で、例によって作業用のフォルダを作って、そこでvscodeを起動する。(mkdirとかcdとかした後、code .ってやる人は少数派だろうな、、、)

で、参考サイト通りにやると、はまる。seleniumに色々と教えてあげながら動かしてあげるといいらしい。なんじゃもんじゃやって、こんなかんじ。ファイル名はいつものようにいつ終わるかわからんstudy1.ps1。
なんちゃら_pathはPCによって違う(前述のように置いた場所やら調べた場所やらを書くんやで)。
  1. write-output "hello selenium!"
  2. $binary_path="C:\Program Files\Google\Chrome\Application\chrome.exe"
  3. $driver_path="C:\Users\hoge\Documents\work\powershell\selenium_study\chromedriver-win64-125.0.6422.141"
  4. $start_url="https://www.google.co.jp"
  5. $driver=start-sechrome -binarypath $binary_path -webdriverdirectory $driver_path
  6. enter-seurl -url $start_url -driver $driver
  7. $element=find-seelement -driver $driver -classname "gLFyf"
  8. send-sekeys -element $element -keys "sunday engineer"
  9. send-sekeys -element $element -keys "`n"
  10. start-sleep 5
  11. stop-sedriver -driver $driver
  12. write-output "completed"
で、vscodeの再生ボタン(2つあるうちの左が「実行」で右は「デバッグ」なので、左のほう)
を押すと。chromeが立ち上がって、勝手に検索して、勝手に終了する。ターミナルはこうなっている。
はいー。とりあえずうまくいったと思いますよー。

seleniumを使って、じゃんじゃん自動化や。ローコード・ノーコードくそくらえ。コードを書きまくるんや。そして惜しげもなく(というか恥ずかしげもなく)公開しまくる。オープンソース万歳!

、、、あ、どうやってelementを探すん?すなわち、なんで、
$element=find-seelement -driver $driver -classname "gLFyf"
で、検索のテキストボックスを指定できるのがわかるん?とかは次の機会に(あれば)。

だんだん人間としての品がなくなってきた気がする。いや、そもそも下賤なものでござりますゆえ。

2024年6月6日木曜日

RC-S300をpowershellから動かしてみる

 

RC-S300を買ってみたよ。winscard.dllの関数をcallすることで簡単に読み取りとかできる(もちろん読み取れるようにしてあるデータだけ)。が、なぜかpowershellから動かしてみようと思う。

さて、C#から動かしている事例はまぁまぁあるし、PC/SC wrapper classes for .NET(https://www.nuget.org/packages/PCSC/)を使うという手もある。が、あえて、ここは最近マイブームのpowershellを使ってみる。
で、参考サイトはこちらhttps://qiita.com/rhene/items/725dfe4a6b6307731cbfていうかこちらの記事をまるっとpowershellに移植する。powershellにはC#を取り込めるので、APIの宣言とかはそのまま流用する(というかそうする必要がある)。Main部分をpowershellに翻訳していくが、powershellでは記述が難しい部分はdllをインポートする(APIを宣言してある)ためのC#コードに追加して乗り切る。
まずは、定数
nfc_constant.ps1
  1. $nfc_constant_cscode = @'
  2. public const uint SCARD_S_SUCCESS = 0;
  3. public const uint SCARD_E_NO_SERVICE = 0x8010001D;
  4. public const uint SCARD_E_TIMEOUT = 0x8010000A;
  5. public const uint SCARD_SCOPE_USER = 0;
  6. public const uint SCARD_SCOPE_TERMINAL = 1;
  7. public const uint SCARD_SCOPE_SYSTEM = 2;
  8. public const int SCARD_STATE_UNAWARE = 0x0000;
  9. public const int SCARD_STATE_CHANGED = 0x00000002;
  10. public const int SCARD_STATE_PRESENT = 0x00000020;
  11. public const UInt32 SCARD_STATE_EMPTY = 0x00000010;
  12. public const int SCARD_SHARE_SHARED = 0x00000002;
  13. public const int SCARD_SHARE_EXCLUSIVE = 0x00000001;
  14. public const int SCARD_SHARE_DIRECT = 0x00000003;
  15. public const int SCARD_PROTOCOL_T0 = 1;
  16. public const int SCARD_PROTOCOL_T1 = 2;
  17. public const int SCARD_PROTOCOL_RAW = 4;
  18. public const int SCARD_LEAVE_CARD = 0;
  19. public const int SCARD_RESET_CARD = 1;
  20. public const int SCARD_UNPOWER_CARD = 2;
  21. public const int SCARD_EJECT_CARD = 3;
  22. // SCardStatus status values
  23. public const int SCARD_UNKNOWN = 0x00000000;
  24. public const int SCARD_ABSENT = 0x00000001;
  25. public const int SCARD_PRESENT = 0x00000002;
  26. public const int SCARD_SWALLOWED = 0x00000003;
  27. public const int SCARD_POWERED = 0x00000004;
  28. public const int SCARD_NEGOTIABLE = 0x00000005;
  29. public const int SCARD_SPECIFICMODE = 0x00000006;
  30. '@
  31. set-variable -name SCARD_S_SUCCESS -value 0 -option constant
  32. set-variable -name SCARD_E_NO_SERVICE -value 0x8010001D -option constant
  33. set-variable -name SCARD_E_TIMEOUT -value 0x8010000A -option constant
  34. set-variable -name SCARD_SCOPE_USER -value 0 -option constant
  35. set-variable -name SCARD_SCOPE_TERMINAL -value 1 -option constant
  36. set-variable -name SCARD_SCOPE_SYSTEM -value 2 -option constant
  37. set-variable -name SCARD_STATE_UNAWARE -value 0x0000 -option constant
  38. set-variable -name SCARD_STATE_CHANGED -value 0x00000002 -option constant
  39. set-variable -name SCARD_STATE_PRESENT -value 0x00000020 -option constant
  40. set-variable -name SCARD_STATE_EMPTY -value 0x00000010 -option constant
  41. set-variable -name SCARD_SHARE_SHARED -value 0x00000002 -option constant
  42. set-variable -name SCARD_SHARE_EXCLUSIVE -value 0x00000001 -option constant
  43. set-variable -name SCARD_SHARE_DIRECT -value 0x00000003 -option constant
  44. set-variable -name SCARD_PROTOCOL_T0 -value 1 -option constant
  45. set-variable -name SCARD_PROTOCOL_T1 -value 2 -option constant
  46. set-variable -name SCARD_PROTOCOL_RAW -value 4 -option constant
  47. set-variable -name SCARD_LEAVE_CARD -value 0 -option constant
  48. set-variable -name SCARD_RESET_CARD -value 1 -option constant
  49. set-variable -name SCARD_UNPOWER_CARD -value 2 -option constant
  50. set-variable -name SCARD_EJECT_CARD -value 3 -option constant
  51. # SCardStatus status values
  52. set-variable -name SCARD_UNKNOWN -value 0x00000000 -option constant
  53. set-variable -name SCARD_ABSENT -value 0x00000001 -option constant
  54. set-variable -name SCARD_PRESENT -value 0x00000002 -option constant
  55. set-variable -name SCARD_SWALLOWED -value 0x00000003 -option constant
  56. set-variable -name SCARD_POWERED -value 0x00000004 -option constant
  57. set-variable -name SCARD_NEGOTIABLE -value 0x00000005 -option constant
  58. set-variable -name SCARD_SPECIFICMODE -value 0x00000006 -option constant
C#のコードのまま取り込めるのかと思ったけど、うまくいかなかったのでpowershell的な定義を追加。C#記述はそのままそうっとしておく。
次はwinscard.dllを呼び出すためのおまじない。
nfc_pcsc.ps1
  1. $nfc_pcsc_cscode = @'
  2. [DllImport("winscard.dll")]
  3. public static extern uint SCardEstablishContext(uint dwScope, IntPtr pvReserved1, IntPtr pvReserved2, out IntPtr phContext);
  4. [DllImport("winscard.dll", EntryPoint = "SCardListReadersW", CharSet = CharSet.Unicode)]
  5. public static extern uint SCardListReaders(
  6.     IntPtr hContext, byte[] mszGroups, byte[] mszReaders, ref UInt32 pcchReaders);
  7. [DllImport("winscard.dll")]
  8. public static extern uint SCardReleaseContext(IntPtr phContext);
  9. [DllImport("winscard.dll", EntryPoint = "SCardConnectW", CharSet = CharSet.Unicode)]
  10. public static extern uint SCardConnect(IntPtr hContext, string szReader,
  11.         uint dwShareMode, uint dwPreferredProtocols, ref IntPtr phCard,
  12.         ref IntPtr pdwActiveProtocol);
  13. [DllImport("winscard.dll")]
  14. public static extern uint SCardDisconnect(IntPtr hCard, int Disposition);
  15. [StructLayout(LayoutKind.Sequential)]
  16. public class SCARD_IO_REQUEST
  17. {
  18.     internal uint dwProtocol;
  19.     internal int cbPciLength;
  20.     public SCARD_IO_REQUEST()
  21.     {
  22.         dwProtocol = 0;
  23.     }
  24. }
  25. [DllImport("winscard.dll")]
  26. public static extern uint SCardTransmit(IntPtr hCard, IntPtr pioSendRequest, byte[] SendBuff, int SendBuffLen, SCARD_IO_REQUEST pioRecvRequest,
  27.         byte[] RecvBuff, ref int RecvBuffLen);
  28. [DllImport("winscard.dll")]
  29. public static extern uint SCardControl(IntPtr hCard, int controlCode, byte[] inBuffer, int inBufferLen, byte[] outBuffer, int outBufferLen, ref int bytesReturned);
  30. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
  31. public struct SCARD_READERSTATE
  32. {
  33.     internal string szReader;
  34.     internal IntPtr pvUserData;
  35.     internal UInt32 dwCurrentState;
  36.     internal UInt32 dwEventState;
  37.     internal UInt32 cbAtr;
  38.     [MarshalAs(UnmanagedType.ByValArray, SizeConst = 36)]
  39.     internal byte[] rgbAtr;
  40. }
  41. [DllImport("winscard.dll", EntryPoint = "SCardGetStatusChangeW", CharSet = CharSet.Unicode)]
  42. public static extern uint SCardGetStatusChange(IntPtr hContext, int dwTimeout, [In, Out] SCARD_READERSTATE[] rgReaderStates, int cReaders);
  43. [DllImport("winscard.dll")]
  44. public static extern int SCardStatus(IntPtr hCard, string szReader, ref int cch, ref int state, ref int protocol, ref byte[] bAttr, ref int cByte);
  45. [DllImport("kernel32.dll", SetLastError = true)]
  46. public static extern IntPtr LoadLibrary(string lpFileName);
  47. [DllImport("kernel32.dll")]
  48. public static extern void FreeLibrary(IntPtr handle);
  49. [DllImport("kernel32.dll")]
  50. public static extern IntPtr GetProcAddress(IntPtr handle, string procName);
  51. public static uint MySCardTransmit(
  52.     IntPtr hCard,
  53.     byte[] SendBuff,
  54.     int SendBuffLen,
  55.     int cbPciLength,
  56.     byte[] RecvBuff,
  57.     ref int RecvBuffLen
  58. ){
  59.     uint ret;
  60.     SCARD_IO_REQUEST ioRecv = new SCARD_IO_REQUEST();
  61.     ioRecv.cbPciLength = cbPciLength;
  62.     IntPtr handle = LoadLibrary("Winscard.dll");
  63.     IntPtr pci = GetProcAddress(handle, "g_rgSCardT1Pci");
  64.     FreeLibrary(handle);
  65.     ret = SCardTransmit(hCard, pci, SendBuff, SendBuffLen, ioRecv,RecvBuff, ref RecvBuffLen);
  66.     return ret;
  67. }
  68. '@
MySCardTransmitってのは参考サイトのコードの中でpowershellで表現が難しかった部分をC#側に記述したもの。ここ以外は工夫はない。
で、次は本命のスクリプト
nfc_read.ps1
  1. . ".\nfc_constant.ps1"
  2. . ".\nfc_pcsc.ps1"
  3. #add-type -name nfc_constant -namespace user -memberdefinition $nfc_constant_cscode
  4. add-type -name nfc_pcsc -namespace user -memberdefinition $nfc_pcsc_cscode
  5. $hContext=1
  6. <# #############################
  7. 1. SCardEstablishContext
  8. ############################# #>
  9. write-output "***** 1. SCardEstablishContext *****"
  10. $ret = [user.nfc_pcsc]::SCardEstablishContext($SCARD_SCOPE_USER, 0, 0, [ref]$hContext)
  11. if ($ret -ne $SCARD_S_SUCCESS){
  12.     $msg=$null
  13.     if($ret -eq $SCARD_E_NO_SERVICE){
  14.         $msg="No service found."
  15.     }else{
  16.         $msg="Failed to connect the service. code = " + $ret
  17.     }
  18.     write-output $msg
  19.     exit
  20. }
  21. if($hContext -eq 0){
  22.     write-output "Failed to take the context."
  23.     exit
  24. }
  25. write-output "Connected to the service."
  26. <# #############################
  27. 2. SCardListReaders
  28. ############################# #>
  29. write-output "***** 2. SCardListReaders *****"
  30. $pcchReaders=0
  31. $ret = [user.nfc_pcsc]::SCardListReaders($hContext, $null, $null, [ref]$pcchReaders)
  32. if ($ret -ne $SCARD_S_SUCCESS){
  33.     write-output "Failed to detect reader."
  34.     exit
  35. }
  36. [array]$bszReaders=new-object byte[] ($pcchReaders*2)
  37. $ret = [user.nfc_pcsc]::SCardListReaders($hContext, $null, $bszReaders, [ref]$pcchReaders)
  38. if ($ret -ne $SCARD_S_SUCCESS){
  39.     write-output "Failed to get reader name."
  40.     exit
  41. }
  42. $mszReaders=[System.Text.Encoding]::Unicode.GetString($bszReaders)
  43. $nullindex=$mszReaders.IndexOf("`0`0")
  44. $readerName=$mszReaders.substring(0,$nullindex)
  45. write-output "Reader detected."
  46. write-output $readerName
  47. <# #############################
  48. 3. SCardConnect
  49. ############################# #>
  50. write-output "***** 3. SCardConnect *****"
  51. $hCard=0
  52. $activeProtocol=0
  53. $ret = [user.nfc_pcsc]::SCardConnect($hContext, $readerName, $SCARD_SHARE_SHARED, $SCARD_PROTOCOL_T1, [ref]$hCard, [ref]$activeProtocol);
  54. if ($ret -ne $SCARD_S_SUCCESS){
  55.     write-output "Failed to connect to cards. code = "+$ret
  56.     exit
  57. }
  58. write-output "Connected to a card."
  59. <# #############################
  60. 4. SCardTransmit
  61. ############################# #>
  62. write-output "***** 4. SCardTransmit *****"
  63. $maxRecvDataLen=256
  64. [array]$recvBuffer=new-object byte[] ($maxRecvDataLen+2)
  65. $sendBuffer=@(0xff, 0xca, 0x00, 0x00, 0x00)
  66. $pcbRecvLength=$recvBuffer.Length
  67. $cbSendLength=$sendBuffer.Length
  68. $ret = [user.nfc_pcsc]::MySCardTransmit($hCard, $sendBuffer, $cbSendLength,255, $recvBuffer, [ref]$pcbRecvLength)
  69. if ($ret -ne $SCARD_S_SUCCESS)
  70. {
  71.     write-output "Failed to send to NFC card. code = " + $ret
  72.     exit
  73. }
  74. $szRecv = ([System.Text.Encoding]::ASCII.GetString($recvBuffer)).substring(0,$pcbRecvLength)
  75. write-output "Received data from the card."
  76. write-output $szRecv | Format-Hex
  77. <# #############################
  78. 5. SCardDisconnect
  79. ############################# #>
  80. write-output "***** 5. SCardDisconnect *****"
  81. $ret = [user.nfc_pcsc]::SCardDisconnect($hCard, $SCARD_LEAVE_CARD)
  82. if ($ret -ne $SCARD_S_SUCCESS)
  83. {
  84.     write-output "Failed to disconnect the card. code = " + $ret
  85.     exit
  86. }
  87. write-output "Disconnected the card."
コンソール出力を英文化したのは決してかぶれているからではない。VSCodeの化け回避のためなの(υ´•̥̥̥ ﻌ •̥̥̥`υ)
では、動かしてみる。ちょっと前にPN5180のモジュールを買ったときにおまけでついていたタグを読んでみる。
こんなん。で、
まぁ、うまくいったらしい。

Powershell、、、意外と面白い。コンパイラやインタプリタが不要なのがよい。記述がちょっと呪文系だけど、まぁ言語なんてそんなもん。C#で記述して取り込めることを利用してWindowsAPIをCallできるので、応用できる範囲も広い。こういうので、こまいことをちゃちゃっとやれるような、そんな能力が給料に反映され、みんながデジタル技術の獲得を目指すことで本当のDXがちょっと近づいてくるんじゃないだろうかな、、、って発想は自分で手を動かしている小者にしか湧いてこないよなー。世の中そんなもん。せめてお互い称賛しあえるような雰囲気になってほしい。

2024年6月2日日曜日

FT232HにdotnetIoTでSPIは

 

dotnet IoTでFT232Hをいじってみるんやけど、何かがおかしいんよ。

dotnet IoTからのFt232HでSPIを出すってのをやってみようと思う。SPIのターゲットはとりあえず考えない。
で、作成した回路はこんな感じ。
USB電圧を多回転可変抵抗で分圧して、それを基準電圧としてオペアンプの定電圧源回路にぶっこんでいる。これを2回路つくってあって、そのうちの一つをターゲット電圧として、ターゲットに供給するとともにFT232HからのSPIを秋月のFXMA108を使ったレベル変換ICでターゲット電圧ドメインに変換している。4066は可変抵抗で分圧した電圧を供給するか、GNDレベルを供給するかを選択できるようにしてある。これはFT232HのGPIO機能で操作する。
久しぶりのブレッドボードで、ちょっと苦戦した。いや、苦戦というよりは躊躇した。躊躇している時間って冷静に考えると、それは無駄時間。あーでもないこーでもない頭で考えるのならそれを文字起こしすればいいと思う。すっきり取りかかれる、、、仕事なら。今日は仕事じゃないので、躊躇することも許してほしい。
で、苦労の末に電源供給部は正しく動作して、SPIも何となく動作した(dotnet IoTのリファレンスではCSを-1にすれば指定しなないってことになる的な記述があったんだけど、この設定は物理的に存在するピンを必ず指定しないといけないらしいってのを気づくのにクソ時間かかった)んだけど、、、何かが違う。
8ビット送信後SCKが初期状態にならないまま次の8ビット送信が開始されているように見える、、、つまり使えんやん。自分が何か間違っていると思って、dotnet IoTのリファレンスやら見まくった。で、結局のところググってみたところ、こんなのがヒットした。
https://electronics.stackexchange.com/questions/360086/make-ftdi-2232d-do-spi-mode-1-properly-data-seems-1-2-clock-cycle-off
https://forums.adafruit.com/viewtopic.php?t=124869
つまりFT2XXHチップのMPSSEモードでSPIを撃つときはMode0かMode2しかまともに撃てないんだぜってことらしい、、、あかんやん。1バイトづつ撃てばいけるような気もするけど、心が折れた。
今回のソースコードはこちら。

  1. using System.Device.Gpio;
  2. using System.Device.Spi;
  3. using Iot.Device.Ft232H;
  4. using Iot.Device.FtCommon;
  5. namespace ft232h_device_test
  6. {
  7.     class Program
  8.     {
  9.         public static int[]? pins;
  10.         public static GpioController? gpioController;
  11.         public static SpiDevice? spi;
  12.         public static int pin_csn,pin_dummy;
  13.         static void Main(string[] args)
  14.         {
  15.             byte[] txdata=new byte[3]{0x55,0x55,0x55};
  16.             byte[] rxdata=new byte[3]{0x00,0x00,0x00};
  17.             var devices = FtCommon.GetDevices();
  18.             Console.WriteLine($"{devices.Count} available device(s)");
  19.             foreach (var device in devices)
  20.             {
  21.                 Console.WriteLine($" {device.Description}");
  22.                 Console.WriteLine($" Flags: {device.Flags}");
  23.                 Console.WriteLine($" Id: {device.Id}");
  24.                 Console.WriteLine($" LocId: {device.LocId}");
  25.                 Console.WriteLine($" Serial number: {device.SerialNumber}");
  26.                 Console.WriteLine($" Type: {device.Type}");
  27.             }
  28.             if (devices.Count == 0)
  29.             {
  30.                 Console.WriteLine("No device connected");
  31.                 return;
  32.             }
  33.             Ft232HDevice ft232h=new Ft232HDevice(FtCommon.GetDevices()[0]);
  34.             gpioController=ft232h.CreateGpioController();
  35.             pin_dummy=Ft232HDevice.GetPinNumberFromString("D7");
  36.             SpiConnectionSettings settings = new SpiConnectionSettings(0) {ClockFrequency = 100000, DataBitLength = 8,Mode=SpiMode.Mode1,ChipSelectLine=pin_dummy,ChipSelectLineActiveState=PinValue.Low};
  37.             spi = ft232h.CreateSpiDevice(settings);
  38.             pin_csn=Ft232HDevice.GetPinNumberFromString("D4");
  39.             gpioController.OpenPin(pin_csn, PinMode.Output);
  40.             gpioController.Write(pin_csn,PinValue.High);
  41.             pins=new int[4];
  42.             pins[0]=Ft232HDevice.GetPinNumberFromString("C0");
  43.             pins[1]=Ft232HDevice.GetPinNumberFromString("C1");
  44.             pins[2]=Ft232HDevice.GetPinNumberFromString("C2");
  45.             pins[3]=Ft232HDevice.GetPinNumberFromString("C3");
  46.             for(int i=0;i<4;i++){
  47.                 gpioController.OpenPin(pins[i], PinMode.Output);
  48.                 gpioController.Write(pins[i],PinValue.Low);
  49.             }
  50.             OUT1_0V_OUT2_0V();
  51.             Console.WriteLine("Entering test loop");
  52.             while(true){
  53.                 Thread.Sleep(500);
  54.                 OUT1_ON_OUT2_0V();
  55.                 Thread.Sleep(500);
  56.                 OUT1_ON_OUT2_ON();
  57.                 SendSpi(txdata,rxdata,3);
  58.                 Thread.Sleep(500);
  59.                 OUT1_0V_OUT2_ON();
  60.                 Thread.Sleep(500);
  61.                 OUT1_0V_OUT2_0V();
  62.             }
  63.         }
  64.         static void SendSpi(byte[] tx,byte[] rx,int n)
  65.         {
  66.             if((spi!=null)&&(gpioController!=null)){
  67.                 Span<byte> toSend = stackalloc byte[n];
  68.                 Span<byte> toRead = stackalloc byte[n];
  69.                 for(int i=0;i<n;i++){
  70.                     toSend[i]=tx[i];
  71.                 }
  72.                 gpioController.Write(pin_csn,PinValue.Low);
  73.                 spi.TransferFullDuplex(toSend,toRead);
  74.                 gpioController.Write(pin_csn,PinValue.High);
  75.                 for(int i=0;i<n;i++){
  76.                     rx[i]=toRead[i];
  77.                 }
  78.             }
  79.         }
  80.         static void OUT1_ON_OUT2_ON()
  81.         {
  82.             if((gpioController!=null)&&(pins!=null)){
  83.                 gpioController.Write(pins[0],PinValue.Low);
  84.                 gpioController.Write(pins[2],PinValue.High);
  85.                 gpioController.Write(pins[3],PinValue.Low);
  86.                 gpioController.Write(pins[1],PinValue.High);
  87.             }
  88.         }
  89.          static void OUT1_ON_OUT2_0V()
  90.         {
  91.             if((gpioController!=null)&&(pins!=null)){
  92.                 gpioController.Write(pins[0],PinValue.Low);
  93.                 gpioController.Write(pins[2],PinValue.High);
  94.                 gpioController.Write(pins[3],PinValue.High);
  95.                 gpioController.Write(pins[1],PinValue.Low);
  96.             }
  97.         }
  98.          static void OUT1_0V_OUT2_ON()
  99.         {
  100.             if((gpioController!=null)&&(pins!=null)){
  101.                 gpioController.Write(pins[0],PinValue.High);
  102.                 gpioController.Write(pins[2],PinValue.Low);
  103.                 gpioController.Write(pins[3],PinValue.Low);
  104.                 gpioController.Write(pins[1],PinValue.High);
  105.             }
  106.         }
  107.          static void OUT1_0V_OUT2_0V()
  108.         {
  109.             if((gpioController!=null)&&(pins!=null)){
  110.                 gpioController.Write(pins[0],PinValue.High);
  111.                 gpioController.Write(pins[2],PinValue.Low);
  112.                 gpioController.Write(pins[3],PinValue.High);
  113.                 gpioController.Write(pins[1],PinValue.Low);
  114.             }
  115.         }
  116.     }
  117. }
ちなみにうちのLinuxをxubuntu24にクリーンインストールしたらsudo dotnet runってしないとftdiデバイスを認識してくれなくなった。usbadmとかrulesとかいじらんといかんかったような、、、

2024年5月12日日曜日

ngspyceを試す

 

ngspyceを使ってみる。実は、PySpiceでの相互誘導の記述方法がわからなかったので、ngspiceのdllを単に利用しているngspyceを使ってみる。

ngspyceは公式にPyPiに登録されていないようなので自力で入れる。
こちら(https://github.com/ignamv/ngspyce/tree/master)からCodeのボタンを押して、Download ZIPを押して、適当なところに解凍する(Linuxの場合はパーミッションを設定できる場所じゃないといかん、すなわちsmbfs上とかはだめ)。
とりあえず、/home/hoge/tempにngspyce-masterフォルダをぶっこんだとすると、
cd /home/hoge/temp
pip install ./ngspyce-master/
ってすると、インストールしてくれる。

では、値を変えながらシミュレーションして結果を重ね書きするって課題について、
  1. import ngspyce as ns
  2. import numpy as np
  3. import matplotlib.pyplot as plt
  4. def ngspice_simulation(cvalue):
  5.     ns.circ(['.title Low-Pass RC Filter',
  6.              'R1 vin vout 1e3',
  7.              'C1 vout 0 1e-6',
  8.              'V1 vin 0 dc 0 ac 1',
  9.              '.end'])
  10.     ns.alter('C1',C=cvalue)
  11.     ac_results=ns.ac(mode='dec',npoints=10,fstart=1,fstop=1e6)
  12.     freqs=np.abs(ns.vector('frequency'))
  13.     vout=ns.vector('vout')
  14.     return freqs,vout
  15. freqs,vout1=ngspice_simulation(1e-6)
  16. freqs,vout2=ngspice_simulation(0.1e-6)
  17. freqs,vout3=ngspice_simulation(0.01e-6)
  18. fig = plt.figure()
  19. ax1=plt.subplot(2,1,1)
  20. ax1.grid(True)
  21. ax1.plot(freqs,20*np.log10(np.absolute(vout1)), 'b-')
  22. ax1.plot(freqs,20*np.log10(np.absolute(vout2)), 'g-')
  23. ax1.plot(freqs,20*np.log10(np.absolute(vout3)), 'r-')
  24. ax1.set_ylabel('Amplitude [dB]')
  25. plt.xscale('log')
  26. ax2=plt.subplot(2,1,2)
  27. ax2.grid(True)
  28. ax2.plot(freqs,np.angle(vout1), 'b-')
  29. ax2.plot(freqs,np.angle(vout2), 'g-')
  30. ax2.plot(freqs,np.angle(vout3), 'r-')
  31. ax2.set_xlabel('frequency [Hz]')
  32. ax2.set_ylabel('Phase [rad.]')
  33. plt.xscale('log')
  34. fig.tight_layout()
  35. plt.show()

相変わらずのいきなりな感じでこうなる。
回路は一度に入れ切らないといけなくて、後から追加って記述はできないらしい(なぜかできると信じ込んでちょっと悩んだ)。
で、

まぁ、うまくいっている。

ngspiceそのものの回路記述がすでにあるのなら、こっちのほうがラク。ていうか本当にngspiceのdllをwrapしているだけっぽい。それでも、自力でdllをcallする関数やら構造体やら記述するよりは全然助かる。

こういう小さいソフトが助かるんよ。何もかもやってくれる大規模なソフトは不要な機能まで理解する時間が必要になる(少なくともマニュアルを読み飛ばす時間が必要になる)のでムダが多い。

https://ja.wikipedia.org/wiki/UNIX%E5%93%B2%E5%AD%A6


2024年5月11日土曜日

PySpice使ってみる(2)

 

ngspiceってのがある。結果の表示をPySpiceってやつでもちっとうまいことでるんやないか。って試み。

前回、シミュレーションすることができた。ついに(ってほどじゃないやろ)、値を変更しながらのシミュレーションで結果を重ね書きってのをやる。で、

  1. import math
  2. import numpy as np
  3. import matplotlib.pyplot as plt
  4. import PySpice.Logging.Logging as Logging
  5. logger = Logging.setup_logging()
  6. from PySpice.Plot.BodeDiagram import bode_diagram
  7. from PySpice.Spice.Netlist import Circuit
  8. def ngspice_simulation(cvalue):
  9.     circuit = Circuit('Low-Pass RC Filter')
  10.     circuit.SinusoidalVoltageSource('input', 'vin', circuit.gnd, amplitude=1)
  11.     R1 = circuit.R(1, 'vin', 'vout', 1e3)
  12.     C1 = circuit.C(1, 'vout', circuit.gnd, cvalue)
  13.     break_frequency = 1 / (2 * math.pi * float(R1.resistance * C1.capacitance))
  14.     print("Break frequency = {:.1f} Hz".format(break_frequency))
  15.     simulator = circuit.simulator(temperature=25, nominal_temperature=25)
  16.     analysis = simulator.ac(start_frequency=1, stop_frequency=1e6, number_of_points=10, variation='dec')
  17.     freqs=np.array(analysis.frequency)
  18.     vout=np.array(analysis.vout)
  19.     return freqs,vout
  20. freqs,vout1=ngspice_simulation(1e-6)
  21. freqs,vout2=ngspice_simulation(0.1e-6)
  22. freqs,vout3=ngspice_simulation(0.01e-6)
  23. fig = plt.figure()
  24. ax1=plt.subplot(2,1,1)
  25. ax1.grid(True)
  26. ax1.plot(freqs,20*np.log10(np.absolute(vout1)), 'b-')
  27. ax1.plot(freqs,20*np.log10(np.absolute(vout2)), 'g-')
  28. ax1.plot(freqs,20*np.log10(np.absolute(vout3)), 'r-')
  29. ax1.set_ylabel('Amplitude [dB]')
  30. plt.xscale('log')
  31. ax2=plt.subplot(2,1,2)
  32. ax2.grid(True)
  33. ax2.plot(freqs,np.angle(vout1), 'b-')
  34. ax2.plot(freqs,np.angle(vout2), 'g-')
  35. ax2.plot(freqs,np.angle(vout3), 'r-')
  36. ax2.set_xlabel('frequency [Hz]')
  37. ax2.set_ylabel('Phase [rad.]')
  38. plt.xscale('log')
  39. fig.tight_layout()
  40. plt.show()
まぁいつものいきなりなやつでこうなる。
シミュレーション部分を関数化しているけど、どういう感じで関数化するのかはセンスが問われるところ。引数とか、戻り値とか。今回は必要な分だけ対象にしている(最も手抜きシンプル)。
で、結果、
はい。うまくいった。

ngspiceの機能を補うとともにpythonでの特殊な計算の実装の可能性もあるpyspice。まぁまぁいいね。(pythonではahkabって回路シミュレーターも選択肢なんだけど、ahkabは非線形素子の計算がゲロクソ遅いとか問題がある。状況に応じて使い分けるのが良いと思われる。)