Sending a USB Device out the USB DEVICE Port

When running on a recent 6.12+ kernel Raspberry Pi 4 or 5, the VirtualHere Device Client can send the USB Signal out the USB-C (DEVICE) port. 

  This is really useful for KVM/IP type systems or industrial control systems, where the USB device can be redirected to a target that does not need any VirtualHere Client software installed for the remote USB device to be accessible.

Most full speed and high speed devices should redirect out the USB-C port successfully. These include USB disks, fingerprint readers, Security Tokens, Serial Adapters, Wifi dongles, Bluetooth dongles, Speakers, Microphone, Headset etc.
 

Instructions:

Run the Virtualhere Device Client (vhuitarm64g GUI ,or vhclientarm64g Console ) on the Raspberry Pi 4 or Pi5 machine and plug the Pi into the target machine that you want to remotely access a USB device. The VirtualHere Device Client works like a normal VirtualHere client, so Auto-Use etc work the same way.

 On the Pi, it will show a GUI with the available USB devices on the network. Right click on a USB device you want to use and select Use

The USB-C port on the Pi will now become the remote USB Device.

Limitations:

  • High bandwidth Isochronous Devices like Webcams will not work
  • Plug USB 3 devices into into a USB 2 ports on the VirtualHere Server first. The Pi USB-C DEVICE port only supports High Speed USB 2, and Full Speed USB 1.1 output, not Super-speed USB or Low-speed 1.0 USB.
  • Make sure the VirtualHere Server machine and VirtualHere Device Client Pi are using a good WiFi connection or preferably Ethernet because the data transmission is slower than if regular VirtualHere software was on both sides of the connection.
  • Since the Pi only has one USB-C port - only one device can be used at a time. Stop using the device before trying to use another.
  • Some complicated USB devices with more than 7 endpoints will not work
  • If the server is in trial mode, the client is limited to 5 minutes usage per device.

Any questions/comments/bugs please post on this forum or email mail [at] virtualhere.com (mail[at]virtualhere[dot]com)

 

#2

thank you for this feature

I was waiting for it for a long time (at least since 2021) I thought it needed an FPGA to work

will the a pi zero 2 w work as well ? (yes the wifi is 2.4Ghz Only) but the microUSB is otg compatible, so theoretically it should also work. and I can add a GPIO 100mbps ethernet nic

and how is the Pi4 getting powered ? from the host, or should we add a little otg power splitter to power the Pi and connect the OTG port to the laptop ?

again, thanks a lot for this feature.

#3

Yes it will work. I don't have one to test (I was testing on a pi4/5) but as long as you write the latest 64-bit RaspberryOS on it, then run the VirtualHere Device Client it will work. WiFi has more latency so it will be slower but it depends what you need to pass through? What USB device do you want to pass-through?

All Pis except 5 can be powered directly from the USB-C port only. Whereas i had trouble with the Pi5, it needed more power than just the USB-C port on my laptop could provide, so it kept cutting out. I suppose you can tweak the kernel to turn off most things e.g HDMI/bluetooth subsystems and maybe get it to boot reliably.
 

If you are running VirtualHere Server 4.8.0 or later and you run the VirtuaHere Client 5.9.0 or later on any machine you can remotely redirect a USB device out the pi port. 

I haven't documented this fully yet as of now. But basically you can right click on the Device and select Send To and that will show the pi02w or whatever available Pi Device Clients are on the network that can do OTG. Thereore you dont actually need VNC or screen access to the pi to "use" the device. You can trigger the "Use" remotely. Furthermore you can Stop using remotely by simply selecting the device in any VirtuaHere client and selecting Disconnect from User. The pi3/4/5 does need to run either the Virtualhere GUI Device Client or the Virtualhere Console Device Client first e.g as a background daemon.

(To emulate an actual USB Hub would require an FPGA, however i found the limitations of just one device on the pi is pretty useful anyway, and its not as slow as i was expecting)

#4

I needed this setup mainly to pass-through a Yubikey Dongle for Fido2 authentication (it need a person to touch the device to activate the authentication)

the Pi zero 2 w can be fully powered by the micro usb otg port for this purpose, and the fact that we can remotly plug a USB without a VNC is even best suited for the z2w it runs better without x11 and they are cheap we can plug multiple ones on a Hub and plug multiple devices.

I will try it once i am back home.

again, thank you for this feature, it helps a LOT.

#6

I tested with my YubiKey 5C NFC FIPS .

 If the VirtualHere Device Client is running on your pi02w and is connected to the VirtualHere server, then you will should see the Send To option (below) from any other client. 

I have the Yubikey plugged into a Pi5 VirtualHere Server, i right click and Send To the VirtualHere Device Client running on a Pi4 plugged in via the USB-C port into my laptop. I then test using the demo.yubikey.com on my laptop and it works with my fingerprint etc. I reach across to the server and press my fingerprint where the physical Yubikey is and it works.

When you have finished using the Yubikey remotely you can right click on it and select Disconnect from User (of course, you can also just use the local client on the pi02w and stop using there if you want)

 

#7

First of all, congratulations on this feature — I don’t know of any other that has this, and it’s something that’s really been missing.
I tried using this new function on two Raspberry Pis — a Pi 5 and a Pi Zero 2 W — both running the latest version of Raspberry Pi OS, but unfortunately without success.
On a clean installation, I do the usual SSH + VNC setup and then download the binary vhuitarm64g. After that, I run the terminal command to change permissions (chmod +x vhuitarm64g).
Finally, I run sudo ./vhuitarm64g, but it just shows the following text and does nothing else:
Reading package lists... Building dependency tree... Reading state information... raspberrypi-kernel-headers is already the newest version (1:1.20230405-1). Summary:  Upgrading: 0, Installing: 0, Removing: 0, Not Upgrading: 2 

What could I be doing wrong?
Uname -a: Linux usbtest-001 6.12.57-v8+ #1920 SMP PREEMPT Tue Nov  4 10:15:36 GMT 2025 aarch64 GNU/Linux
 
I remember to put -l to log file and have this:
2025-11-06 15:12:13 ERROR :Directory '/tmp/' couldn't be created (error 17: File exists)
2025-11-06 15:12:13 ERROR :Failed to make temp dir when compiling driver!
2025-11-06 15:12:13 ERROR :Error preloading client driver, 

#9

After downloading this new binary, the app no longer gives an error.
However, the GUI doesn’t open (which isn’t a problem for me).
On the Pi 5, after launching the app from the console, I was able to add the server via commands, and it works perfectly — even with CodeMeter dongles.
On the Pi Zero 2 W, I still haven’t managed to get it working, and this time, even with the -l option, I can’t get it to save a log to analyze the issue.

I run "sudo strace -f -o vhdebug.txt ./vhclientarm64g -n" and I think is stuck on infiniti loop
"1616 dup3(10, 1, 0 <unfinished ...> 1608 close(5 <unfinished ...> 1616 <... dup3 resumed>) = 1 1608 <... close resumed>) = 0 1616 dup3(12, 2, 0 <unfinished ...> 1608 close(10 <unfinished ...> 1616 <... dup3 resumed>) = 2 1608 <... close resumed>) = 0 1616 close(5 <unfinished ...> 1608 close(12 <unfinished ...> 1616 <... close resumed>) = 0 1608 <... close resumed>) = 0 1616 close(7 <unfinished ...> 1608 rt_sigaction(SIGCHLD, {sa_handler=0x595604, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x73fd9c}, <unfinished ...> 1616 <... close resumed>) = 0 1608 <... rt_sigaction resumed>NULL, 8) = 0 1616 close(9 <unfinished ...> 1608 wait4(1616, <unfinished ...> 1616 <... close resumed>) = 0 1608 <... wait4 resumed>0x7ffc2a3364, WNOHANG, NULL) = 0 1616 close(10 <unfinished ...> 1608 pipe2( <unfinished ...> 1616 <... close resumed>) = 0 1608 <... pipe2 resumed>[5, 10], 0) = 0 1616 close(11 <unfinished ...> 1608 fcntl(5, F_GETFL <unfinished ...> 1616 <... close resumed>) = 0 1608 <... fcntl resumed>) = 0 (flags O_RDONLY) 1616 close(12 <unfinished ...> 1608 fcntl(5, F_SETFL, O_RDONLY|O_NONBLOCK|O_LARGEFILE <unfinished ...> 1616 <... close resumed>) = 0 1608 <... fcntl resumed>) = 0 1616 close(3) = 0 1608 epoll_ctl(8, EPOLL_CTL_ADD, 5, {events=EPOLLIN, data=0x7f96c7be30} <unfinished ...> 1616 close(4 <unfinished ...> 1608 <... epoll_ctl resumed>) = 0 1616 <... close resumed>) = 0 1608 epoll_ctl(8, EPOLL_CTL_ADD, 9, {events=EPOLLIN, data=0x7f96c928d0} <unfinished ...> 1616 close(5 <unfinished ...> 1608 <... epoll_ctl resumed>) = 0 1616 <... close resumed>) = -1 EBADF (Bad file descriptor) 1608 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0 <unfinished ...> 1616 close(6 <unfinished ...> 1608 <... mmap resumed>) = 0x7f96c78000 1616 <... close resumed>) = 0 1608 munmap(0x7f96c78000, 4096 <unfinished ...> 1616 close(7) = -1 EBADF (Bad file descriptor) 1608 <... munmap resumed>) = 0 1616 close(8 <unfinished ...> 1608 epoll_ctl(8, EPOLL_CTL_ADD, 11, {events=EPOLLIN, data=0x7f96c7bef0} <unfinished ...> 1616 <... close resumed>) = 0 1608 <... epoll_ctl resumed>) = 0 1616 close(9 <unfinished ...> 1608 epoll_pwait(8, <unfinished ...> 1616 <... close resumed>) = -1 EBADF (Bad file descriptor) 1608 <... epoll_pwait resumed>[], 1, 0, NULL, 8) = 0 1616 close(10 <unfinished ...> 1608 epoll_pwait(8, <unfinished ...> 1616 <... close resumed>) = -1 EBADF (Bad file descriptor)"
 

#10

What i think i need to do is integrate the gadget client with cloudhub because it requires patching and compiling a kernel module which is clumsy for end users. I can precompile and install this inside cloudhub and so it will work immediately.


At the moment, the client will automatically download/patch and compile a kernel module but as you can see it takes time (it hasn't jammed its just downloading and compiling / patching the kernel module).

I will add the device client to all cloudhub builds over the next few days and let you know when its ready.

 

#11

I spent some time over the last few days investigating this and its best to use either a Pi4 or a Pi5 only. Not a Pi02w or a Pi0w, and not cloudHub (yet).

 Initially the Pi will require internet access so that the driver can be automatically compiled and installed. 

The virtualhere gadget client will do this automatically when it first starts (so it might take a while to run initially). After it runs for the first time, the Pi no longer requires internet access.

Also using Raspberry Pi OS is a good idea with all the latest updates (sudo apt update;sudo apt upgrade -y;sync;reboot)

#12

I understand.
I’d like to use the Pi Zero 2 W because of its size and the ability to connect via Wi-Fi, Bluetooth, or USB cable, but I realize that in terms of hardware it’s somewhat limited.
A few months ago, I tried using USB gadget mode to “forward” all traffic to the USB port — from the virtual port to the physical OTG port — but unfortunately, I didn’t have much success.
There was a lot of latency, issues with the endpoints, and of course, I don’t have the same level of knowledge as you, so I eventually reached a point where I couldn’t make further progress.
Even so, the Pi 5 is working well now, and that’s already an amazing achievement.
Thank you very much.

#13

Actually the Pi02w probably does work. 

After you boot the pi02w and it can connect to the internet, then run the vhclientarm64g and wait quite a while, it should automatically download and compile the driver. This only has to be done once.

I couldnt get it to work with openwrt which is what i use for cloudhub so i gave up but with raspberrypi os its probably ok.

 

#14

I asked the virtual assistant for help analyzing the log, and it suggested checking the build architecture, since the Pi 5 uses a Cortex-A76 (ARMv8.2-A architecture) and the Pi Zero 2 W is the same as the Pi 3, with a Cortex-A53 (ARMv8.0-A architecture).

#15

Its not important, what is - is that both use the dwc2 USB IP(Intellectual Property) Block in their cpus and this is what the virtualhere client binds to

#16

I think I managed to:

  • Edit cmdline.txt (sudo nano /boot/firmware/cmdline.txt) and add modules-load=dwc2 (...rootwait modules-load=dwc2 quiet...)
  • Edit config.txt (sudo nano /boot/firmware/config.txt) and at the end, under [ALL], add dtoverlay=dwc2,dr_mode=peripheral

That makes it work.
This change enables dwc2, which, as far as I understand, is disabled on the Pi Zero 2 W, and in turn activates the raw_gadget module.

It's working but unstable, log:
2025-11-12 15:23:21 INFO  :Log Started
2025-11-12 15:23:21 INFO  :VirtualHere Client 5.9.6 starting (Compiled: Nov  4 2025 18:55:54)
2025-11-12 15:23:21 INFO  :Client OS is Linux 6.12.47+rpt-rpi-v8 aarch64
2025-11-12 15:23:21 INFO  :Using config at /home/suporte/.vhui
2025-11-12 15:23:21 INFO  :Administrator mode
2025-11-12 15:23:21 ERROR :Error preloading client driver, 
2025-11-12 15:23:24 INFO  :Log Started
2025-11-12 15:23:24 INFO  :VirtualHere Client 5.9.6 starting (Compiled: Nov  4 2025 18:55:54)
2025-11-12 15:23:24 INFO  :Client OS is Linux 6.12.47+rpt-rpi-v8 aarch64
2025-11-12 15:23:24 INFO  :Using config at /root/.vhui
2025-11-12 15:23:24 INFO  :Administrator mode
2025-11-12 15:23:24 INFO  :IPC available at /tmp/vhclient
2025-11-12 15:23:24 INFO  :Auto-find (Bonjour) on
2025-11-12 15:23:24 INFO  :Auto-find (Bonjour SSL) on
2025-11-12 15:23:55 ERROR :Error 108 reading ep 0x01, Cannot send after socket shutdown
2025-11-12 15:23:55 INFO  :Device has been (internally reset/disconnected)
2025-11-12 15:23:55 INFO  :Device has been (internally reset/disconnected)
2025-11-12 15:23:56 INFO  :BI IN ACK FAILED on ep 0x81 result = -108
2025-11-12 15:23:56 ERROR :Error 108 reading ep 0x01, Cannot send after socket shutdown
2025-11-12 15:23:56 INFO  :Device has been (internally reset/disconnected)
2025-11-12 15:23:56 INFO  :Device has been (internally reset/disconnected)
2025-11-12 15:23:56 ERROR :Error 108 reading ep 0x01, Cannot send after socket shutdown
2025-11-12 15:23:56 INFO  :Device has been (internally reset/disconnected)
2025-11-12 15:23:56 ERROR :Error 108 reading ep 0x01, Cannot send after socket shutdown
2025-11-12 15:23:56 INFO  :BI IN ACK FAILED on ep 0x81 result = -108
2025-11-12 15:23:56 ERROR :Error 108 reading ep 0x01, Cannot send after socket shutdown
2025-11-12 15:23:56 INFO  :Device has been (internally reset/disconnected)
2025-11-12 15:23:56 INFO  :BI IN ACK FAILED on ep 0x81 result = -108
2025-11-12 15:23:56 ERROR :Error 11 reading ep 0x01, Resource temporarily unavailable
2025-11-12 15:26:36 ERROR :Error 108 reading ep 0x01, Cannot send after socket shutdown
2025-11-12 15:26:36 INFO  :Device has been (internally reset/disconnected)
2025-11-12 15:26:36 INFO  :Device has been (internally reset/disconnected)
2025-11-12 15:26:36 ERROR :Error 108 reading ep 0x01, Cannot send after socket shutdown
2025-11-12 15:26:36 INFO  :Device has been (internally reset/disconnected)
2025-11-12 15:26:37 ERROR :Error 108 reading ep 0x01, Cannot send after socket shutdown
2025-11-12 15:26:37 INFO  :Device has been (internally reset/disconnected)
2025-11-12 15:26:37 ERROR :Error 108 reading ep 0x01, Cannot send after socket shutdown
2025-11-12 15:26:37 INFO  :Device has been (internally reset/disconnected)
2025-11-12 15:26:37 INFO  :Device has been (internally reset/disconnected)
2025-11-12 15:26:37 ERROR :Error 108 reading ep 0x01, Cannot send after socket shutdown
2025-11-12 15:26:37 INFO  :BI IN ACK FAILED on ep 0x81 result = -108
2025-11-12 15:26:37 ERROR :Error 11 reading ep 0x01, Resource temporarily unavailable
2025-11-12 15:28:18 ERROR :Error 108 reading ep 0x01, Cannot send after socket shutdown
2025-11-12 15:28:18 INFO  :Device has been (internally reset/disconnected)
2025-11-12 15:28:18 INFO  :Device has been (internally reset/disconnected)
2025-11-12 15:28:19 ERROR :Error 108 reading ep 0x01, Cannot send after socket shutdown
2025-11-12 15:28:19 INFO  :Device has been (internally reset/disconnected)
2025-11-12 15:28:19 ERROR :Error 108 reading ep 0x01, Cannot send after socket shutdown
2025-11-12 15:28:19 INFO  :Device has been (internally reset/disconnected)
2025-11-12 15:28:19 ERROR :Error 108 reading ep 0x01, Cannot send after socket shutdown
2025-11-12 15:28:19 INFO  :Device has been (internally reset/disconnected)
2025-11-12 15:28:20 INFO  :Device has been (internally reset/disconnected)
2025-11-12 15:28:20 ERROR :Error 108 reading ep 0x01, Cannot send after socket shutdown
2025-11-12 15:28:20 INFO  :BI IN ACK FAILED on ep 0x81 result = -108
2025-11-12 15:28:20 ERROR :Error 11 reading ep 0x01, Resource temporarily unavailable

#17

Its better to dynamically add the overlay using dtoverlay at runtime rather than changing config.txt

 i could not get the pi02w working reliably either. I think its best to stick with the pi4 or 5 for this.

#18

So let me make sure that I understand how this works:

  1. I run a Pi 4 (Let's call it Pi 4 Server for the purpose of this discussion) at home with the Virtual Here Client (I bought a license).
  2. I run TailScale on that Pi 4 Server.
  3. I take another Pi 4 at work (let's call it Pi 4 Client for the purpose of this discussion), also running Tailscale. if I connect the Pi 4 Client to work's WiFi network, and I run the tailscale and the Client, I should see the USB dongle from Home.
  4. I shut down the Pi 4 Client and instead of using the USB-C power supply, I plug it into my Laptop. I assume the laptop (or at least the USB-C docking station) can provide enough power to the Pi 4 Client.
  5. I open the Client, right click on the Dongle and send that to my USB-C port (to my Laptop)?
  6. This way, I do not need to run TailScale or Virtual Client on the laptop at all?

If this is how it works, this is amazing. If I misunderstood something, please advise.

Thanks a lot!

 

#19

Yes that is correct. The USB signal comes out of the USB-C port of the pi4 (or pi5). Just like the device was really connected. There is no need for any software on the laptop e.g virtualhere client.

The only thing you need to be mindful of is if the network connection is slow then the usb device will operate slowly.  Far too slow for e.g passing through a webcam. However a microphone can pass through ok but you need a good network connection for this. Note the real microphone/speaker passes through, not some emulation like all other KVMIP systems out there. Security dongles, licensing dongles etc should pass through ok. You can only pass through one device at a time (get another pi for each device) because the pi only has one USB-C port on the board.

I'm still formulating how best to make this easier for users but currently works like this.

  1. Run virtualhere server like you normally do on a pi or whatever
  2. Install raspbian on a separate pi4 (or 5)
  3. Run the virtualhere device client on that pi. That will automatically compile and install the correct driver (your pi does need internet access for this stage)
  4. Somehow get that to connect to the virtualhere server. Eg using tailscale
  5. Use the device in the virtualhere client. Either manually by typing ./vhclientarm64g -t "USE,..." or by right clicking on the device on some other client on the network and selecting Send To and specify that pi4 like in the screenshot above
  6. The laptop will then notice a "usb" device being connected which appears as the same device that is remotely on the virtualhere server.

    Let me know how you go setting this up.  IM thinking of creating a firmware image just for the pi for this (sort of like cloudhub) so its not such a pain to setup

 

#20

Excellent. IT will not allow me to install Tailscale, they started to put more restrictions. As of now, I can still run Virtual Here Client, so when I am home, I can use the dongle but not when I am at work.

I cannot try this today but I should be able to try it in the next few days.

Thanks a lot, much appreciated. This is huge.

#21

Any chance this could work via the USB-A port? They do make USB-A to USB-A cables.

#22

Impossible, you can use a USB-C to USBA a cable however. The USBC port on the pi to a USBA a cable is fine. 

#23

As per the title, is there any feasibility for webcams to work with this feature in the future? I understand if the overheads are too much. This feature I would be willing to pay an additional premium for, I am already a server license owner.

#24

Its  probably not feasible because the usb webcam stream cannot be transmitted fast enough.

#25

This took almost 10 hours of troubleshooting, scouring various forums, and extensive use of ChatGPT. Use my input at your own discretion.

The problem that vhclientarm64 has with the Pi Zero 2w is that RAW_GADGET_MODE is not inherently available via the kernel. Here are the steps I had to follow in order to successfully install and use the virtual here client on the Pi Zero 2w in gadget mode.

PREPARING THE Pi OS ENVIRONMENT

  1. I used Raspberry Pi Imager to install Raspberry Pi OS Lite (64-bit) on an SD card. 
    Customization:
    - Entered a hostname
    - Pre-filled my Wi-Fi network credentials
    - Enabled SSH for remote access with password authentication. *The login you enter for the username and password will be your credentials.
  2. Update the /boot/firmware/config.txt file. (Can be modified directly on the SD card from your machine)
    This can be added to the end of the file under the [ALL] section.
    dtoverlay=dwc2,dr_mode=peripheral 
  3. Update /boot/firmware/cmdline.txt file. (Can be modified directly on the SD card from your machine)
    modules-load=dwc2 i.e. (...rootwait modules-load=dwc2 quiet...)
  4. Power on the Raspberry Pi Zero 2W through the OTG capable USB port with your SD card.
  5. SSH into your Pi.
    ssh username@pi-hostname

INSTALLING DEPENDENCIES

  1. Install any available updates.
    sudo apt update
    sudo apt upgrade -y
    sync
    sudo reboot
  2. Install build tools + kernel headers
    sudo apt install -y git build-essential bc bison flex libssl-dev linux-headers-$(uname -r)
  3. Build + install the raw_gadget kernel module
    cd ~
    git clone https://github.com/xairy/raw-gadget.git
    cd raw-gadget/raw_gadget
    make
  4. Install the model into the kernel module tree
    KVER="$(uname -r)"
    sudo install -D -m 0644 ./raw_gadget.ko "/lib/modules/${KVER}/kernel/drivers/usb/gadget/raw_gadget.ko"
    sudo depmod -a "${KVER}"
  5. Load the raw_gadget module
    sudo modprobe raw_gadget
  6. Configure the raw_gadget module to load automatically
    echo raw_gadget | sudo tee /etc/modules-load.d/raw_gadget.conf

VERIFY INSTALLATION

  1. Verify headers - You should see it pointing into /usr/src/linux-headers-...
    ls -l /lib/modules/$(uname -r)/build
  2. Confirm dwc2 is loaded.
    lsmod | grep dwc2
    dmesg | grep -i dwc2 | tail -n 20
  3. Confirm raw_gadget is loaded.
    lsmod | grep raw_gadget
    dmesg | grep -i 'raw-gadget\|raw_gadget' | tail -n 30

Installing the Virtual Here Gadget Mode Console Client

  1. wget https://www.virtualhere.com/sites/default/files/usbclient/vhclientarm64g
  2. chmod +x ./vhclientarm64g
  3. sudo mv ./vhclientarm64g /usr/sbin
  4. Create the virtual here service.
    sudo tee /etc/systemd/system/virtualhereclient.service >/dev/null <<'EOF'

    # Contents of /etc/systemd/system/virtualhereclient.service
    [Unit]
    Description=VirtualHere Client (Gadget Mode)
    Wants=network-online.target
    After=network-online.target
    [Service]
    # Type=forking # To run the service in the background 
    Type=simple
    # Ensure USB gadget stack + raw gadget module are present before starting VH
    ExecStartPre=/sbin/modprobe dwc2
    ExecStartPre=/sbin/modprobe libcomposite
    ExecStartPre=/sbin/modprobe raw_gadget
    # Run VH in daemon mode and log to syslog/journal
    # Include option -n to use in background mode if you have license
    ExecStart=/usr/sbin/vhclientarm64g -l OSEventLog
    Restart=on-failure
    RestartSec=2
    KillSignal=SIGINT
    TimeoutStopSec=5
    [Install]
    WantedBy=multi-user.target

    Save and Exit
    ctrl + x
     
  5. Reload systems, enable, and start the virtual here client service.
    sudo systemctl daemon-reload
    sudo systemctl enable virtualhereclient
    sudo systemctl restart virtualhereclient

Verify that the Virtual Here Client is Running in Gadget Mode

  1. ps aux | grep -v grep | grep vhclientarm64g
    Output should look like this:
    root        1022  0.0  4.2  19112 17920 ?        Ssl  Feb23   0:01 /usr/sbin/vhclientarm64g
  2. sudo journalctl -u virtualhereclient -e --no-pager
    dmesg | grep -E 'dwc2|raw-gadget|raw_gadget' | tail -n 80
    Output:
    Feb 23 06:52:47 sfgc-pi-cam systemd[1]: Starting virtualhereclient.service - VirtualHere Client...
    Feb 23 06:52:47 sfgc-pi-cam systemd[1]: Started virtualhereclient.service - VirtualHere Client.
    Feb 23 06:52:48 sfgc-pi-cam vhclientarm64g[1022]: VirtualHere Client: VirtualHere Client 5.9.6 starting (Compiled: Jan 22 2026 11:58:30)
    Feb 23 06:52:48 sfgc-pi-cam vhclientarm64g[1022]: VirtualHere Client: Client OS is Linux 6.12.62+rpt-rpi-v8 aarch64
    Feb 23 06:52:48 sfgc-pi-cam vhclientarm64g[1022]: VirtualHere Client: Using config at /root/.vhui
    Feb 23 06:52:48 sfgc-pi-cam vhclientarm64g[1022]: VirtualHere Client: Administrator mode
    Feb 23 06:52:48 sfgc-pi-cam vhclientarm64g[1022]: VirtualHere Client: IPC available at /tmp/vhclient
    Feb 23 06:52:48 sfgc-pi-cam vhclientarm64g[1022]: VirtualHere Client: Auto-find (Bonjour) on
    Feb 23 06:52:48 sfgc-pi-cam vhclientarm64g[1022]: VirtualHere Client: Auto-find (Bonjour SSL) on
    [    0.000000] Kernel command line: coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_headphones=0 cgroup_disable=memory snd_bcm2835.enable_hdmi=1 snd_bcm2835.enable_hdmi=0  smsc95xx.macaddr=B8:27:EB:B4:F1:97 vc_mem.mem_base=0x1ec00000 vc_mem.mem_size=0x20000000  console=ttyS0,115200 console=tty1 root=PARTUUID=4509a990-02 rootfstype=ext4 fsck.repair=yes rootwait modules-load=dwc2 cfg80211.ieee80211_regdom=US
    [    0.000000] Unknown kernel command line parameters "modules-load=dwc2", will be passed to user space.
    [    1.725610] dwc2 3f980000.usb: supply vusb_d not found, using dummy regulator
    [    1.726925] dwc2 3f980000.usb: supply vusb_a not found, using dummy regulator
    [    1.830026] dwc2 3f980000.usb: EPs: 8, dedicated fifos, 4080 entries in SPRAM
    [    3.280190]     modules-load=dwc2
    [    9.555897] raw_gadget: loading out-of-tree module taints kernel.
    [   33.879531] dwc2 3f980000.usb: bound driver raw-gadget.0

Using the Virtual Here Client in Gadget Mode
See full documentation here.

  1. From the Pi Zero 2w via SSH
    /usr/sbin/vhclientarm64g -t "LIST"
    Expected output:
    VirtualHere Client IPC, below are the available devices:
    (Value in brackets = address, * = Auto-Use)

    hostname
     - Device 1 (112)

    Auto-Find currently on
    Auto-Use All currently off
    Reverse Lookup currently off
    Reverse SSL Lookup currently off
    VirtualHere Client not running as a service
  2. Select a device from a virtual here server.
    /usr/sbin/vhclientarm64g -t "USE,hostname.112"
    or for auto-use
    /usr/sbin/vhclientarm64g - t "AUTO USE DEVICE,<address>"
    Expected output:
    OK

    If you get an API timeout, your client is running in the background mode. You will need to either update the service to run in simple mode or purchase a virtual here server license. 
  3. (Alternative) From any Virtual Here Client GUI.
    Right click on any virtual here device on your network. From the drop down, press "Send to". Your Pi Zero 2w hostname will appear in the list. 
     

Helpful Sources:

https://www.isticktoit.net/?p=1383

https://jon.sprig.gs/blog/post/2243

https://pip.raspberrypi.com/categories/685-app-notes-guides-whitepapers/documents/RP-009276-WP/Using-OTG-mode-on-Raspberry-Pi-SBCs.pdf

https://github.com/thagrol/Guides/blob/main/ethernetgadget.pdf

 

 

 

#26

OK thanks for the chatGPT reference.