PureBasic SerialPort使用方法

PureBasicでのシリアルポートの使い方についてメモを書いておきます。

 

Arduinoでループバックアダプタを作る

動作テストのために単純なループバックをするコードを書いておきます。

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  if (Serial.available() > 0) {
    digitalWrite(LED_BUILTIN, HIGH);
    static uint8_t buf;
    buf = Serial.read();
    Serial.write(buf);
    digitalWrite(LED_BUILTIN, LOW);
  }
}

上記内容をArduinoに書き込みすることで、シリアルに送った内容をそのまま返すループバックのアダプタになります。

 

Windows上でシリアルポートの一覧を出す方法

 

Mac OS X上でシリアルポートの一覧を出す方法

デバイス名も含めて出したいとなると結構面倒なことがわかりました。

いくつかのアプローチがあるようです。

  • ls /dev/
  • system_profiler
  • ioreg

 

Mac OS X : ls /dev/ を使用する方法

ターミナルからlsコマンドを使用して調べる方法です。

一般的に使われる手法ですが、デバイス名は判別できません。

あくまで存在するデバイスのポート名のみが確認できます。

% ls /dev/cu.* 
/dev/cu.BLTH			/dev/cu.usbmodem14301
/dev/cu.Bluetooth-Incoming-Port	/dev/cu.usbmodem14401

% ls /dev/tty.*
/dev/tty.BLTH				/dev/tty.usbmodem14301
/dev/tty.Bluetooth-Incoming-Port	/dev/tty.usbmodem14401

cu.デバイスとtty.デバイスが存在します。

これは、コールアウトとコールインデバイスの違いのようで、ttyだとDCD(Data Carrier Detect)がアサートされないとOpen時にブロックされるとあります。

出力する目的で使用するのであれば、Mac OS X上の場合は、cu.デバイスを使用するのがセオリーなのではないでしょうか。

現に、Mac版のArduino IDEのシリアルポートでリストアップされるデバイスは、cu.デバイスになっています。

情報:

 

PureBasicのRunProgramから実行する場合は、lsでのワイルドカード指定がglob展開されません。

/cu.*などとワイルドカード指定して一致するものを探す際に、シェル(Bashやzsh)がファイル名を展開しているため、ワイルドカードをRunProgramでlsに渡しても、そのままの意味(ワイルドカード付きのファイル名「cu.*」1つ)を探してしまうことになります。

そのため、lsコマンドではなく、globを内部に実装しているfindコマンドに渡す必要があります。

% find /dev -name 'cu.*' 2>/dev/null
/dev/cu.BLTH
/dev/cu.Bluetooth-Incoming-Port
/dev/cu.usbmodem14301
/dev/cu.usbmodem14401

シェル上でのglob展開を使用しないfindコマンド使用例。

Macの場合「find: /dev/fd/3: Not a directory」というエラーが出るので、標準エラー出力を/dev/nullに送ってます。

 

Structure SerialDevice
  Port.s
EndStructure

Global Dim SerialPortList.SerialDevice(0)

cmdFind = RunProgram("/usr/bin/find", "/dev -name cu.*", "", #PB_Program_Open | #PB_Program_Read)

i.l = 0
If cmdFind
  While ProgramRunning(cmdFind)
    If AvailableProgramOutput(cmdFind)
      ReDim SerialPortList(i)
      SerialPortList(i) \ Port = ReadProgramString(cmdFind)
      i = i + 1
    EndIf
  Wend
  CloseProgram(cmdFind)
EndIf

For i.l = 0 To ArraySize(SerialPortList())
  Debug SerialPortList(i) \ Port
Next

PureBasicで書くとこんな感じでしょうか。

 

Mac OS X : system_profiler を使用する方法

% /usr/sbin/system_profiler SPUSBDataType
2024-02-22 12:48:45.511 system_profiler[24632:1902355] SPUSBDevice: IOCreatePlugInInterfaceForService failed 0xe00002be
2024-02-22 12:48:45.511 system_profiler[24632:1902355] SPUSBDevice: IOCreatePlugInInterfaceForService failed 0xe00002be
2024-02-22 12:48:45.512 system_profiler[24632:1902355] SPUSBDevice: IOCreatePlugInInterfaceForService failed 0xe00002be
2024-02-22 12:48:45.512 system_profiler[24632:1902355] SPUSBDevice: IOCreatePlugInInterfaceForService failed 0xe00002be
2024-02-22 12:48:45.512 system_profiler[24632:1902355] SPUSBDevice: IOCreatePlugInInterfaceForService failed 0xe00002be
2024-02-22 12:48:45.513 system_profiler[24632:1902355] SPUSBDevice: IOCreatePlugInInterfaceForService failed 0xe00002be
2024-02-22 12:48:45.513 system_profiler[24632:1902355] SPUSBDevice: IOCreatePlugInInterfaceForService failed 0xe00002be
USB:

    USB 3.1 Bus:

      Host Controller Driver: AppleIntelICLUSBXHCI
      PCI Device ID: 0x8a13 
      PCI Revision ID: 0x0003 
      PCI Vendor ID: 0x8086 

    USB 3.1 Bus:

      Host Controller Driver: AppleIntelICLUSBXHCI
      PCI Device ID: 0x38ed 
      PCI Revision ID: 0x0010 
      PCI Vendor ID: 0x8086 

        Seeed XIAO M0:

          Product ID: 0x802f
          Vendor ID: 0x2886
          Version: 1.04
          Serial Number: 7EF958DA5055344A322E3120FF010B15
          Speed: Up to 12 Mb/s
          Manufacturer: Seeed
          Location ID: 0x14400000 / 10
          Current Available (mA): 500
          Current Required (mA): 100
          Extra Operating Current (mA): 0

        Arduino MKRZero:

          Product ID: 0x804f
          Vendor ID: 0x2341
          Version: 1.00
          Serial Number: 6DD43F3A5050323339202020FF103030
          Speed: Up to 12 Mb/s
          Manufacturer: Arduino LLC
          Location ID: 0x14300000 / 7
          Current Available (mA): 500
          Current Required (mA): 500
          Extra Operating Current (mA): 0

    Apple T2 Bus:

      Host Controller Driver: AppleUSBVHCIBCE

        Touch Bar Backlight:

          Product ID: 0x8102
          Vendor ID: 0x05ac (Apple Inc.)
          Version: 2.01
          Serial Number: 0000000000000000
          Manufacturer: Apple Inc.
          Location ID: 0x80700000

        Touch Bar Display:

          Product ID: 0x8302
          Vendor ID: 0x05ac (Apple Inc.)
          Version: 2.01
          Serial Number: 0000000000000000
          Manufacturer: Apple Inc.
          Location ID: 0x80600000

        Apple Internal Keyboard / Trackpad:

          Product ID: 0x027e
          Vendor ID: 0x05ac (Apple Inc.)
          Version: 9.33
          Serial Number: FM712820080N1R0BR+TNZ
          Manufacturer: Apple Inc.
          Location ID: 0x80500000

        Headset:

          Product ID: 0x8103
          Vendor ID: 0x05ac (Apple Inc.)
          Version: 2.16
          Serial Number: 000000000000
          Manufacturer: Apple
          Location ID: 0x80400000

        Ambient Light Sensor:

          Product ID: 0x8262
          Vendor ID: 0x05ac (Apple Inc.)
          Version: 2.01
          Serial Number: 000000000000
          Manufacturer: Apple Inc.
          Location ID: 0x80300000

        FaceTime HD Camera (Built-in):

          Product ID: 0x8514
          Vendor ID: 0x05ac (Apple Inc.)
          Version: 2.01
          Serial Number: CC212223730J3Y384
          Manufacturer: Apple Inc.
          Location ID: 0x80200000

        Apple T2 Controller:

          Product ID: 0x8233
          Vendor ID: 0x05ac (Apple Inc.)
          Version: 2.01
          Serial Number: 0000000000000000
          Manufacturer: Apple Inc.
          Location ID: 0x80100000

2020年モデルの13インチMacBookPro(4ポートThunderbolt3)上で確認した結果です。

なにやらエラー出力?と思われるものも表示されていますが、USBに接続しているマイコンボード名も取れるようです。

しかし、/dev/cu.*のポート名とのマッピングがいまいち取りづらいですね・・・。

cu.usbmodem****1(Location IDの上部4桁と一致)とマッチングが取れると思われるでしょうが、cu.に続く「usbmodem」の文字列はドライバ依存のため、繋いでいるデバイスによって変わる可能性があります。

となると、cu.*デバイスに対する情報取得の意味では、実質使用できないコマンドと思われます。

 

ちなみに、man system_profilerで確認してみると、いくつかのオプションが利用可能なようです。

  • -xml(XMLで出力)
  • -json(JSONで出力)
  • -timeout(デフォルトで180秒指定)

情報:

 

Mac OS X : ioreg を使用する方法

これはMac OS Xで使用されているI/O Kitというフレームワークのレジストリを表示するコマンドで、IOに関係する情報を取り出すことができます。

man ioregで確認してみたところ、いくつかのオプションが利用可能なようです。

  • -a(XMLで出力)
  • -c(オブジェクトのプロパティ)
  • -k(キーと一致したオブジェクトを表示、子ノードは機能せず?)
  • -l(表示されているすべてのオブジェクトのプロパティを表示)
  • -n(名前と一致したオブジェクトを表示、前方一致)
  • -r(一致オブジェクトをルートにしたサブツリー表示、-c,-k,-n時有効)
  • -x(データと数値を16進数で表示)

シリアルポートを取得する上での基本のコマンドは

ioreg -lr -c IOUSBHostDevice

となります。

これは、-lオプションで、すべてのプロパティを表示し、-rオプションで一致したものだけ表示、-cオプションでIOUSBHostDeviceに限定しています。

この指定を忘れると、テキスト情報で3MB以上もの情報が流れてくるので、実装によっては危険です。

そのため、-c,-rは必ずつけたほうが良く、-lオプションを入れないとポート情報の表示がされませんでしたので、これも必須となります。

% /usr/sbin/ioreg -l -r -c IOUSBHostDevice
+-o Arduino MKRZero@14300000  <class IOUSBHostDevice, id 0x10002b200, registered, matched, active, busy 0 (627 ms), retain 34>
  | {
  |   "kUSBSerialNumberString" = "6DD43F3A5050323339202020FF103030"
  |   "bDeviceClass" = 239
  |   "bDeviceSubClass" = 2
  |   "iSerialNumber" = 3
  |   "Built-In" = No
  |   "kUSBFailedRemoteWake" = Yes
  |   "IOServiceDEXTEntitlements" = (("com.apple.developer.driverkit.transport.usb"))
  |   "iProduct" = 2
  |   "USB Serial Number" = "6DD43F3A5050323339202020FF103030"
  |   "USB Vendor Name" = "Arduino LLC"
  |   "USBSpeed" = 1
  |   "IOPowerManagement" = {"PowerOverrideOn"=Yes,"CapabilityFlags"=32768,"MaxPowerState"=2,"DevicePowerState"=2,"DriverPowerState"=0,"ChildrenPowerState"=1,"CurrentPowerState"=2}
  |   "bNumConfigurations" = 1
  |   "kUSBProductString" = "Arduino MKRZero"
  |   "IOServiceLegacyMatchingRegistryID" = 4295143938
  |   "kUSBVendorString" = "Arduino LLC"
  |   "USB Product Name" = "Arduino MKRZero"
  |   "iManufacturer" = 1
  |   "idVendor" = 9025
  |   "Device Speed" = 1
  |   "kUSBCurrentConfiguration" = 1
  |   "idProduct" = 32847
  |   "bcdDevice" = 256
  |   "UsbDeviceSignature" = <41234f8000013644443433463341353035303332333333393230323032304646313033303330ef02010202000a0000>
  |   "sessionID" = 242600890807977
  |   "USB Address" = 7
  |   "non-removable" = "no"
  |   "IOCFPlugInTypes" = {"9dc7b780-9ec0-11d4-a54f-000a27052861"="IOUSBHostFamily.kext/Contents/PlugIns/IOUSBLib.bundle"}
  |   "IOClassNameOverride" = "IOUSBDevice"
  |   "USBPortType" = 0
  |   "bDeviceProtocol" = 1
  |   "locationID" = 338690048
  |   "kUSBAddress" = 7
  |   "bcdUSB" = 512
  |   "IOGeneralInterest" = "IOCommand is not serializable"
  |   "bMaxPacketSize0" = 64
  | }
  | 
  +-o AppleUSBHostLegacyClient  <class AppleUSBHostLegacyClient, id 0x10002b203, !registered, !matched, active, busy 0, retain 8>
  |   {
  |     "IOPowerManagement" = {"DevicePowerState"=0,"CurrentPowerState"=1,"CapabilityFlags"=65536,"MaxPowerState"=2,"DriverPowerState"=1}
  |   }
  |   
  +-o AppleUSBCDCCompositeDevice  <class AppleUSBCDCCompositeDevice, id 0x10002b206, !registered, !matched, active, busy 0, retain 4>
  |   {
  |     "IOClass" = "AppleUSBCDCCompositeDevice"
  |     "CFBundleIdentifier" = "com.apple.driver.usb.cdc"
  |     "IOProviderClass" = "IOUSBHostDevice"
  |     "kUSBPreferredConfiguration" = 1
  |     "IOProbeScore" = 60000
  |     "IOMatchedAtBoot" = Yes
  |     "IOMatchCategory" = "IODefaultMatchCategory"
  |     "IOMatchDefer" = Yes
  |     "bDeviceSubClass" = 2
  |     "bDeviceProtocol" = 1
  |     "CFBundleIdentifierKernel" = "com.apple.driver.usb.cdc"
  |     "IOPrimaryDriverTerminateOptions" = Yes
  |     "bDeviceClass" = 239
  |   }
  |   
  +-o IOUSBHostInterface@0  <class IOUSBHostInterface, id 0x10002b209, registered, matched, active, busy 0 (8 ms), retain 10>
  | | {
  | |   "USBSpeed" = 1
  | |   "iInterface" = 0
  | |   "IOServiceLegacyMatchingRegistryID" = 4295143947
  | |   "bInterfaceProtocol" = 0
  | |   "bAlternateSetting" = 0
  | |   "idProduct" = 32847
  | |   "bcdDevice" = 256
  | |   "USB Product Name" = "Arduino MKRZero"
  | |   "locationID" = 338690048
  | |   "bInterfaceClass" = 2
  | |   "bInterfaceSubClass" = 2
  | |   "IOCFPlugInTypes" = {"2d9786c6-9ef3-11d4-ad51-000a27052861"="IOUSBHostFamily.kext/Contents/PlugIns/IOUSBLib.bundle"}
  | |   "UsbExclusiveOwner" = "AppleUSBACMControl"
  | |   "USBPortType" = 0
  | |   "bConfigurationValue" = 1
  | |   "bInterfaceNumber" = 0
  | |   "USB Vendor Name" = "Arduino LLC"
  | |   "IOServiceDEXTEntitlements" = (("com.apple.developer.driverkit.transport.usb"))
  | |   "idVendor" = 9025
  | |   "bNumEndpoints" = 1
  | |   "USB Serial Number" = "6DD43F3A5050323339202020FF103030"
  | |   "IOGeneralInterest" = "IOCommand is not serializable"
  | |   "IOClassNameOverride" = "IOUSBInterface"
  | | }
  | | 
  | +-o AppleUSBACMControl  <class AppleUSBACMControl, id 0x10002b20d, registered, matched, active, busy 0 (1 ms), retain 7>
  |     {
  |       "IOProbeScore" = 60000
  |       "CFBundleIdentifier" = "com.apple.driver.usb.cdc.acm"
  |       "IOMatchDefer" = Yes
  |       "IOClass" = "AppleUSBACMControl"
  |       "IOProviderClass" = "IOUSBHostInterface"
  |       "bInterfaceClass" = 2
  |       "CFBundleIdentifierKernel" = "com.apple.driver.usb.cdc.acm"
  |       "IOMatchCategory" = "IODefaultMatchCategory"
  |       "bInterfaceSubClass" = 2
  |       "InterruptPipeReturn" = 0
  |       "bInterfaceProtocol" = 0
  |     }
  |     
  +-o IOUSBHostInterface@1  <class IOUSBHostInterface, id 0x10002b20a, registered, matched, active, busy 0 (227 ms), retain 7>
  | | {
  | |   "USBSpeed" = 1
  | |   "iInterface" = 0
  | |   "IOServiceLegacyMatchingRegistryID" = 4295143948
  | |   "bInterfaceProtocol" = 0
  | |   "bAlternateSetting" = 0
  | |   "idProduct" = 32847
  | |   "bcdDevice" = 256
  | |   "USB Product Name" = "Arduino MKRZero"
  | |   "locationID" = 338690048
  | |   "Product Name" = "Arduino MKRZero"
  | |   "bInterfaceClass" = 10
  | |   "bInterfaceSubClass" = 0
  | |   "IOCFPlugInTypes" = {"2d9786c6-9ef3-11d4-ad51-000a27052861"="IOUSBHostFamily.kext/Contents/PlugIns/IOUSBLib.bundle"}
  | |   "USBPortType" = 0
  | |   "bConfigurationValue" = 1
  | |   "bInterfaceNumber" = 1
  | |   "USB Vendor Name" = "Arduino LLC"
  | |   "IOServiceDEXTEntitlements" = (("com.apple.developer.driverkit.transport.usb"))
  | |   "idVendor" = 9025
  | |   "IODEXTMatchCount" = 1
  | |   "bNumEndpoints" = 2
  | |   "USB Serial Number" = "6DD43F3A5050323339202020FF103030"
  | |   "IOGeneralInterest" = "IOCommand is not serializable"
  | |   "IOClassNameOverride" = "IOUSBInterface"
  | | }
  | | 
  | +-o AppleUSBACMData  <class AppleUSBACMData, id 0x10002b210, registered, matched, active, busy 0 (2 ms), retain 7>
  |   | {
  |   |   "IOClass" = "AppleUSBACMData"
  |   |   "CFBundleIdentifier" = "com.apple.driver.usb.cdc.acm"
  |   |   "IOProviderClass" = "IOUSBHostInterface"
  |   |   "IOTTYBaseName" = "usbmodem"
  |   |   "idProduct" = 32847
  |   |   "IOProbeScore" = 49998
  |   |   "bInterfaceSubClass" = 0
  |   |   "IOMatchCategory" = "IODefaultMatchCategory"
  |   |   "IOMatchDefer" = Yes
  |   |   "idVendor" = 9025
  |   |   "CFBundleIdentifierKernel" = "com.apple.driver.usb.cdc.acm"
  |   |   "IOTTYSuffix" = "14301"
  |   |   "bInterfaceClass" = 10
  |   | }
  |   | 
  |   +-o IOSerialBSDClient  <class IOSerialBSDClient, id 0x10002b216, registered, matched, active, busy 0 (1 ms), retain 5>
  |       {
  |         "IOClass" = "IOSerialBSDClient"
  |         "CFBundleIdentifier" = "com.apple.iokit.IOSerialFamily"
  |         "IOProviderClass" = "IOSerialStreamSync"
  |         "IOTTYBaseName" = "usbmodem"
  |         "IOSerialBSDClientType" = "IOSerialStream"
  |         "IOProbeScore" = 1000
  |         "IOResourceMatch" = "IOBSD"
  |         "IOMatchedAtBoot" = Yes
  |         "IOMatchCategory" = "IODefaultMatchCategory"
  |         "IOTTYDevice" = "usbmodem14301"
  |         "IOCalloutDevice" = "/dev/cu.usbmodem14301"
  |         "IODialinDevice" = "/dev/tty.usbmodem14301"
  |         "IOGeneralInterest" = "IOCommand is not serializable"
  |         "IOPersonalityPublisher" = "com.apple.iokit.IOSerialFamily"
  |         "CFBundleIdentifierKernel" = "com.apple.iokit.IOSerialFamily"
  |         "IOTTYSuffix" = "14301"
  |       }
  |       
  +-o Google Chrome  <class AppleUSBHostDeviceUserClient, id 0x10002b214, !registered, !matched, active, busy 0, retain 7>
      {
        "IOUserClientCreator" = "pid 13262, Google Chrome"
        "IOUserClientDefaultLocking" = Yes
      }
      

+-o Seeed XIAO M0@14400000  <class IOUSBHostDevice, id 0x10002b359, registered, matched, active, busy 0 (612 ms), retain 36>
  | {
  |   "kUSBSerialNumberString" = "7EF958DA5055344A322E3120FF010B15"
  |   "bDeviceClass" = 0
  |   "bDeviceSubClass" = 0
  |   "iSerialNumber" = 3
  |   "Built-In" = No
  |   "kUSBFailedRemoteWake" = Yes
  |   "IOServiceDEXTEntitlements" = (("com.apple.developer.driverkit.transport.usb"))
  |   "iProduct" = 2
  |   "USB Serial Number" = "7EF958DA5055344A322E3120FF010B15"
  |   "USB Vendor Name" = "Seeed"
  |   "USBSpeed" = 1
  |   "IOPowerManagement" = {"PowerOverrideOn"=Yes,"CapabilityFlags"=32768,"MaxPowerState"=2,"DevicePowerState"=2,"DriverPowerState"=0,"ChildrenPowerState"=1,"CurrentPowerState"=2}
  |   "bNumConfigurations" = 1
  |   "kUSBProductString" = "Seeed XIAO M0"
  |   "IOServiceLegacyMatchingRegistryID" = 4295144283
  |   "kUSBVendorString" = "Seeed"
  |   "USB Product Name" = "Seeed XIAO M0"
  |   "iManufacturer" = 1
  |   "idVendor" = 10374
  |   "Device Speed" = 1
  |   "kUSBCurrentConfiguration" = 1
  |   "idProduct" = 32815
  |   "bcdDevice" = 260
  |   "UsbDeviceSignature" = <86282f80040137454639353844413530353533343441333232453331323046463031304231350000000202000a0000>
  |   "sessionID" = 242710715877150
  |   "USB Address" = 10
  |   "non-removable" = "no"
  |   "IOCFPlugInTypes" = {"9dc7b780-9ec0-11d4-a54f-000a27052861"="IOUSBHostFamily.kext/Contents/PlugIns/IOUSBLib.bundle"}
  |   "IOClassNameOverride" = "IOUSBDevice"
  |   "USBPortType" = 0
  |   "bDeviceProtocol" = 0
  |   "locationID" = 339738624
  |   "kUSBAddress" = 10
  |   "bcdUSB" = 512
  |   "IOGeneralInterest" = "IOCommand is not serializable"
  |   "bMaxPacketSize0" = 64
  | }
  | 
  +-o AppleUSBHostLegacyClient  <class AppleUSBHostLegacyClient, id 0x10002b35c, !registered, !matched, active, busy 0, retain 8>
  |   {
  |     "IOPowerManagement" = {"DevicePowerState"=0,"CurrentPowerState"=1,"CapabilityFlags"=65536,"MaxPowerState"=2,"DriverPowerState"=1}
  |   }
  |   
  +-o AppleUSBCDCCompositeDevice  <class AppleUSBCDCCompositeDevice, id 0x10002b35f, !registered, !matched, active, busy 0, retain 4>
  |   {
  |     "IOClass" = "AppleUSBCDCCompositeDevice"
  |     "CFBundleIdentifier" = "com.apple.driver.usb.cdc"
  |     "IOProviderClass" = "IOUSBHostDevice"
  |     "kUSBPreferredConfiguration" = 1
  |     "IOProbeScore" = 60000
  |     "IOMatchedAtBoot" = Yes
  |     "IOMatchCategory" = "IODefaultMatchCategory"
  |     "bDeviceSubClass" = 0
  |     "bDeviceProtocol" = 0
  |     "CFBundleIdentifierKernel" = "com.apple.driver.usb.cdc"
  |     "IOPrimaryDriverTerminateOptions" = Yes
  |     "bDeviceClass" = 0
  |   }
  |   
  +-o IOUSBHostInterface@0  <class IOUSBHostInterface, id 0x10002b362, registered, matched, active, busy 0 (8 ms), retain 11>
  | | {
  | |   "USBSpeed" = 1
  | |   "iInterface" = 0
  | |   "IOServiceLegacyMatchingRegistryID" = 4295144292
  | |   "bInterfaceProtocol" = 0
  | |   "bAlternateSetting" = 0
  | |   "idProduct" = 32815
  | |   "bcdDevice" = 260
  | |   "USB Product Name" = "Seeed XIAO M0"
  | |   "locationID" = 339738624
  | |   "bInterfaceClass" = 2
  | |   "bInterfaceSubClass" = 2
  | |   "IOCFPlugInTypes" = {"2d9786c6-9ef3-11d4-ad51-000a27052861"="IOUSBHostFamily.kext/Contents/PlugIns/IOUSBLib.bundle"}
  | |   "UsbExclusiveOwner" = "AppleUSBACMControl"
  | |   "USBPortType" = 0
  | |   "bConfigurationValue" = 1
  | |   "bInterfaceNumber" = 0
  | |   "USB Vendor Name" = "Seeed"
  | |   "IOServiceDEXTEntitlements" = (("com.apple.developer.driverkit.transport.usb"))
  | |   "idVendor" = 10374
  | |   "bNumEndpoints" = 1
  | |   "USB Serial Number" = "7EF958DA5055344A322E3120FF010B15"
  | |   "IOGeneralInterest" = "IOCommand is not serializable"
  | |   "IOClassNameOverride" = "IOUSBInterface"
  | | }
  | | 
  | +-o AppleUSBACMControl  <class AppleUSBACMControl, id 0x10002b366, registered, matched, active, busy 0 (1 ms), retain 7>
  |     {
  |       "IOProbeScore" = 60000
  |       "CFBundleIdentifier" = "com.apple.driver.usb.cdc.acm"
  |       "IOMatchDefer" = Yes
  |       "IOClass" = "AppleUSBACMControl"
  |       "IOProviderClass" = "IOUSBHostInterface"
  |       "bInterfaceClass" = 2
  |       "CFBundleIdentifierKernel" = "com.apple.driver.usb.cdc.acm"
  |       "IOMatchCategory" = "IODefaultMatchCategory"
  |       "bInterfaceSubClass" = 2
  |       "InterruptPipeReturn" = 0
  |       "bInterfaceProtocol" = 0
  |     }
  |     
  +-o IOUSBHostInterface@1  <class IOUSBHostInterface, id 0x10002b363, registered, matched, active, busy 0 (227 ms), retain 9>
  | | {
  | |   "USBSpeed" = 1
  | |   "iInterface" = 0
  | |   "IOServiceLegacyMatchingRegistryID" = 4295144293
  | |   "bInterfaceProtocol" = 0
  | |   "bAlternateSetting" = 0
  | |   "idProduct" = 32815
  | |   "bcdDevice" = 260
  | |   "USB Product Name" = "Seeed XIAO M0"
  | |   "locationID" = 339738624
  | |   "Product Name" = "Seeed XIAO M0"
  | |   "bInterfaceClass" = 10
  | |   "bInterfaceSubClass" = 0
  | |   "IOCFPlugInTypes" = {"2d9786c6-9ef3-11d4-ad51-000a27052861"="IOUSBHostFamily.kext/Contents/PlugIns/IOUSBLib.bundle"}
  | |   "USBPortType" = 0
  | |   "bConfigurationValue" = 1
  | |   "bInterfaceNumber" = 1
  | |   "USB Vendor Name" = "Seeed"
  | |   "IOServiceDEXTEntitlements" = (("com.apple.developer.driverkit.transport.usb"))
  | |   "idVendor" = 10374
  | |   "IODEXTMatchCount" = 1
  | |   "bNumEndpoints" = 2
  | |   "USB Serial Number" = "7EF958DA5055344A322E3120FF010B15"
  | |   "IOGeneralInterest" = "IOCommand is not serializable"
  | |   "IOClassNameOverride" = "IOUSBInterface"
  | | }
  | | 
  | +-o AppleUSBACMData  <class AppleUSBACMData, id 0x10002b369, registered, matched, active, busy 0 (2 ms), retain 7>
  |   | {
  |   |   "IOClass" = "AppleUSBACMData"
  |   |   "CFBundleIdentifier" = "com.apple.driver.usb.cdc.acm"
  |   |   "IOProviderClass" = "IOUSBHostInterface"
  |   |   "IOTTYBaseName" = "usbmodem"
  |   |   "idProduct" = 32815
  |   |   "IOProbeScore" = 49998
  |   |   "bInterfaceSubClass" = 0
  |   |   "IOMatchCategory" = "IODefaultMatchCategory"
  |   |   "IOMatchDefer" = Yes
  |   |   "idVendor" = 10374
  |   |   "CFBundleIdentifierKernel" = "com.apple.driver.usb.cdc.acm"
  |   |   "IOTTYSuffix" = "14401"
  |   |   "bInterfaceClass" = 10
  |   | }
  |   | 
  |   +-o IOSerialBSDClient  <class IOSerialBSDClient, id 0x10002b36f, registered, matched, active, busy 0 (1 ms), retain 5>
  |       {
  |         "IOClass" = "IOSerialBSDClient"
  |         "CFBundleIdentifier" = "com.apple.iokit.IOSerialFamily"
  |         "IOProviderClass" = "IOSerialStreamSync"
  |         "IOTTYBaseName" = "usbmodem"
  |         "IOSerialBSDClientType" = "IOSerialStream"
  |         "IOProbeScore" = 1000
  |         "IOResourceMatch" = "IOBSD"
  |         "IOMatchedAtBoot" = Yes
  |         "IOMatchCategory" = "IODefaultMatchCategory"
  |         "IOTTYDevice" = "usbmodem14401"
  |         "IOCalloutDevice" = "/dev/cu.usbmodem14401"
  |         "IODialinDevice" = "/dev/tty.usbmodem14401"
  |         "IOGeneralInterest" = "IOCommand is not serializable"
  |         "IOPersonalityPublisher" = "com.apple.iokit.IOSerialFamily"
  |         "CFBundleIdentifierKernel" = "com.apple.iokit.IOSerialFamily"
  |         "IOTTYSuffix" = "14401"
  |       }
  |       
  +-o Google Chrome  <class AppleUSBHostDeviceUserClient, id 0x10002b36d, !registered, !matched, active, busy 0, retain 7>
      {
        "IOUserClientCreator" = "pid 13262, Google Chrome"
        "IOUserClientDefaultLocking" = Yes
      }
      

2020年モデルの13インチMacBookPro(4ポートThunderbolt3)上で確認した結果です。

実行結果の一部を切り取っています。

-cオプションを有効にしていますが、これでもIOUSBHostDeviceの結果は、3000行近く、900KBほどのテキスト情報です。

IOCalloutDeviceキーまたは、IODialinDeviceキーに、/dev/〜のポート名が格納されていることがわかります。

これらのキーを探して、上位ツリーからプロダクト名等を取得すると、ボード名表示までできそうです。

ちなみに、locationIDが一致しない?と思われるでしょうが、デフォルトで10進数表示になっているからです。

-xオプションの指定で16進数表示になるため、基本的には-c, -l, -r, -xオプションは有効にして使用するものになると思われますが、XML出力では16進数で出力はされないようです。

 

情報:

 

Mac OS X : ioreg の出力を xmllint でフィルタする方法

Mac OS Xに入っているコマンドラインツールでXMLをパースできないか調べると、grep, sed, awkで頑張っている例や、perlに渡してしまって文字列処理するような例が見つかります。

ioregの出力は-aオプションでXML出力が可能で、XML構造になっているため、きちんと情報を取り出すにはXMLをパースするのが一番都合が良いです。

ioreg -alrx -c IOUSBHostDevice

をパースする方法を考えてみましょう。

Mac OS Xにはxmllintが入っているため、XPathで指定してみることにします。

% /usr/sbin/ioreg -alrx -c IOUSBHostDevice | /usr/bin/xmllint --xpath "//dict[key[text()='IOCalloutDevice']]" -
<dict>
								<key>CFBundleIdentifier</key>
								<string>com.apple.iokit.IOSerialFamily</string>
								<key>CFBundleIdentifierKernel</key>
								<string>com.apple.iokit.IOSerialFamily</string>
								<key>IOCalloutDevice</key>
								<string>/dev/cu.usbmodem14301</string>
								<key>IOClass</key>
								<string>IOSerialBSDClient</string>
								<key>IODialinDevice</key>
								<string>/dev/tty.usbmodem14301</string>
								<key>IOGeneralInterest</key>
								<string>IOCommand is not serializable</string>
								<key>IOMatchCategory</key>
								<string>IODefaultMatchCategory</string>
								<key>IOMatchedAtBoot</key>
								<true/>
								<key>IOObjectClass</key>
								<string>IOSerialBSDClient</string>
								<key>IOObjectRetainCount</key>
								<integer>5</integer>
								<key>IOPersonalityPublisher</key>
								<string>com.apple.iokit.IOSerialFamily</string>
								<key>IOProbeScore</key>
								<integer>1000</integer>
								<key>IOProviderClass</key>
								<string>IOSerialStreamSync</string>
								<key>IORegistryEntryID</key>
								<integer>4295143958</integer>
								<key>IORegistryEntryName</key>
								<string>IOSerialBSDClient</string>
								<key>IOResourceMatch</key>
								<string>IOBSD</string>
								<key>IOSerialBSDClientType</key>
								<string>IOSerialStream</string>
								<key>IOServiceBusyState</key>
								<integer>0</integer>
								<key>IOServiceBusyTime</key>
								<integer>1005816</integer>
								<key>IOServiceState</key>
								<integer>30</integer>
								<key>IOTTYBaseName</key>
								<string>usbmodem</string>
								<key>IOTTYDevice</key>
								<string>usbmodem14301</string>
								<key>IOTTYSuffix</key>
								<string>14301</string>
							</dict>
<dict>
								<key>CFBundleIdentifier</key>
								<string>com.apple.iokit.IOSerialFamily</string>
								<key>CFBundleIdentifierKernel</key>
								<string>com.apple.iokit.IOSerialFamily</string>
								<key>IOCalloutDevice</key>
								<string>/dev/cu.usbmodem14401</string>
								<key>IOClass</key>
								<string>IOSerialBSDClient</string>
								<key>IODialinDevice</key>
								<string>/dev/tty.usbmodem14401</string>
								<key>IOGeneralInterest</key>
								<string>IOCommand is not serializable</string>
								<key>IOMatchCategory</key>
								<string>IODefaultMatchCategory</string>
								<key>IOMatchedAtBoot</key>
								<true/>
								<key>IOObjectClass</key>
								<string>IOSerialBSDClient</string>
								<key>IOObjectRetainCount</key>
								<integer>5</integer>
								<key>IOPersonalityPublisher</key>
								<string>com.apple.iokit.IOSerialFamily</string>
								<key>IOProbeScore</key>
								<integer>1000</integer>
								<key>IOProviderClass</key>
								<string>IOSerialStreamSync</string>
								<key>IORegistryEntryID</key>
								<integer>4295144303</integer>
								<key>IORegistryEntryName</key>
								<string>IOSerialBSDClient</string>
								<key>IOResourceMatch</key>
								<string>IOBSD</string>
								<key>IOSerialBSDClientType</key>
								<string>IOSerialStream</string>
								<key>IOServiceBusyState</key>
								<integer>0</integer>
								<key>IOServiceBusyTime</key>
								<integer>1479657</integer>
								<key>IOServiceState</key>
								<integer>30</integer>
								<key>IOTTYBaseName</key>
								<string>usbmodem</string>
								<key>IOTTYDevice</key>
								<string>usbmodem14401</string>
								<key>IOTTYSuffix</key>
								<string>14401</string>
							</dict>

情報取得できますね。

では、シリアルポート名からプロダクト名を引いてみましょう。

% /usr/sbin/ioreg -alrx -c IOUSBHostDevice | /usr/bin/xmllint --xpath "//dict[string[text()='/dev/cu.usbmodem14301']]/../../../../key[text()='Product Name']/following::string[position()=1]/text()" -
Arduino MKRZero
% /usr/sbin/ioreg -alrx -c IOUSBHostDevice | /usr/bin/xmllint --xpath "//dict[string[text()='/dev/cu.usbmodem14401']]/../../../../key[text()='Product Name']/following::string[position()=1]/text()" -
Seeed XIAO M0
% /usr/sbin/ioreg -alrx -c IOUSBHostDevice | /usr/bin/xmllint --xpath "//dict[string[text()='/dev/cu.NO_DEVICE']]/../../../../key[text()='Product Name']/following::string[position()=1]/text()" -
XPath set is empty

簡単ですね。

「/dev/cu.usbmodem14301」と一致する<dict>内の<string>を検索し、そのノードの4階層上にある<key>Product Name</key>を探し、なおかつその次の要素となる<string>からテキストを抜き出しています。

一致がない場合は「XPath set is empty」という応答(標準エラー出力)になります。

 

PureBasicで実装する例です。

Structure SerialDevice
  Port.s
  hasName.b
  Name.s
EndStructure

Global Dim SerialPortList.SerialDevice(0)

cmdFind = RunProgram("/usr/bin/find", "/dev -name cu.*", "", #PB_Program_Open | #PB_Program_Read)

i.l = 0
If cmdFind
  While ProgramRunning(cmdFind)
    If AvailableProgramOutput(cmdFind)
      ReDim SerialPortList(i)
      SerialPortList(i) \ Port = ReadProgramString(cmdFind)
      i = i + 1
    EndIf
  Wend
  CloseProgram(cmdFind)
EndIf

For i.l = 0 To ArraySize(SerialPortList())
  cmdXPath.s = "//dict[string[text()='\''" + SerialPortList(i) \ Port + "'\'']]/../../../../key[text()='\''Product Name'\'']/following::string[position()=1]/text()"
  cmdPortName.s = "/usr/sbin/ioreg -alrx -c IOUSBHostDevice | /usr/bin/xmllint --xpath '" + cmdXPath + "' -"
  cmdIoreg = RunProgram("/bin/sh", "-c " + Chr(34) + cmdPortName + Chr(34), "", #PB_Program_Open | #PB_Program_Read)
  If cmdIoreg
    While ProgramRunning(cmdIoreg)
      If AvailableProgramOutput(cmdIoreg)
        SerialPortList(i) \ hasName = 1
        SerialPortList(i) \ Name = ReadProgramString(cmdIoreg)
      EndIf
    Wend
    CloseProgram(cmdIoreg)
  EndIf
Next

For i.l = 0 To ArraySize(SerialPortList())
  If SerialPortList(i) \ hasName
    Debug SerialPortList(i) \ Port + " @ " + SerialPortList(i) \ Name
  Else
    Debug SerialPortList(i) \ Port
  EndIf
Next

RunProgramの引数で#PB_Program_Connectを指定するとパイプをつなげることができるようなのですが、いまいち動作させることができませんでした。

RunProgramをioregとxmllintで2行書くのですが、間にDelay()を入れると動くなど、タイミングの問題か書き方の問題なのか、まだ調査できていません。

とりあえずは、/bin/shにすべて渡してしまうことにして、RunProgram1行で書く例です。

シリアルポートをリストアップした後、それぞれのシリアルポートに対してコマンドを実行しているため、ポート数が多くなると遅くなると思われます。

ioregの内容をバッファしたほうが良いかと思いますが、コマンドの実行結果の行数が増えると極端に重くなってしまったため、このような形での実装となりました。

他に良い実装の方法があればご連絡ください!

 

Linux上でシリアルポートの一覧を出す方法