How to Power Off Basestations remotely?



Didn’t have much time to work on this last night but I managed to get the basestation ID programmatically. Annoyingly Pimax doesn’t expose them via OpenVR (they report them as something like PVR_lighthouse_0). However the data is available in lighthousedb.json (in a Pimax-specific path) or via the USB HID protocol for communicating with headsets.


I have published my solution on GitHub ( It comes with a short explanation of how the power management works and a Python script, which implements the control loop.

At the moment it does not have an automatic search for BT MAC address (it should be easy to add). Since I am running it currently on my RPi, there is no way I could integrate data from SteamVR, but as a working example how it could be done I guess it should suffice.

Just received my 8K

i bought 2$ powerbars at the dollar store. Flip the switch and power is off.
Smart switches cost around 25$ and have a few seconds delay, which is the same as walking to your unit and unplugging it.


Just buy the old clapper. Lol


nah, to easy. also it would generate costs for all users, while a software solution might come for free depending on the license agreement.


“I bought powerbars at the two dollar store.”

Fixed it :stuck_out_tongue_winking_eye:


I did a little more playing with sending different values to a lighthouse (mode B) last night and discovered a bit more of the protocol. Here is a state diagram:


0x1200 disables power management (always on)
0x1201 enables power management and sets the timeout to 60 seconds
0x1202 wakes the lighthouse from standby with a custom timeout (and is ignored in any other state)

For some reason I can’t use 0x1202 as a ping, the current state of the timeout countdown remains unchanged. Instead I need to ping with 0x1201 (which resets the remaining countdown timer to 60 seconds).

Also interestingly I can’t use the BaseStation ID in any message (doing so returns a 0x8112 error response). Only 0xFFFFFFFF as the BaseStation ID is accepted (0x0012 success response).

I wonder if it’s because I’m only using a single base station and/or because this unit has never been power-managed via a Vive Linkbox so doesn’t have some config set.


I have not tried different “header” values from 0x1202 before, so I am looking at it now, and here are my observations with default ID (0xffffffff):

  • 0x1200 - wakes up the LH and lets it running indefinitely.

    Writing char-cs to 0x35 : 12000028ffffffff000000000000000000000000 -> {‘rsp’: [‘wr’]}
    Reading char-cs from 0x35 -> 0012000000000000000000000000000000000000

  • 0x1201 - wakes up the LH and activates “default” timeout 60 sec. Even if I tried to set a different one it behaves (and reports) as if the timeout is 60 sec.
    This command can also be used to ping the LH and keep it running as long as it arrives before the 60 sec deadline.
    This can be also used to turn off the LH if it was started with 0x1200 as it resets the timeout to 60 sec.

    Writing char-cs to 0x35 : 12010028ffffffff000000000000000000000000 -> {‘rsp’: [‘wr’]}
    Reading char-cs from 0x35 -> 0012003c00000000000000000000000000000000

  • 0x1202 - seems to be the “admin” mode, but needs the real ID of the lighthouse. If only default ID is supplied it starts the LH, but cannot keep it running. It does not reset the timeout counter, so once the original value, given at the wake-up command, expires, the LH goes into stand-by again. It can be woken-up again though and it honors the timeout value given.

    Writing char-cs to 0x35 : 12020028ffffffff000000000000000000000000 -> {‘rsp’: [‘wr’]}
    Reading char-cs from 0x35 -> 0012002800000000000000000000000000000000

What I found out also that all three types of commands acknowledges in a way the mode of operation by reporting the currently used timeout. In the examples above I used 40 seconds in the write command and followed it by read command.

0x1200 reports 0x0000 timeout which is consistent with the observation -> running indefinitely
0x1201 reports 0x003c hardcoded value of 60 sec. even when I supplied 40.
0x1202 honors the value given with the write command, but does not allow extending the timeout by subsequent repetition of the same command. Once the timeout expires the LH goes into stand-by again.

Then there is possibility to change the timeout value with 0x1202 command using the real LH ID and also pinging the LH with this custom timeout value.

  • 0x1202 with the correct LH ID, can wake-up the LH, set the custom timeout value, and ping the LH with the custom timeout value, to keep it running.

    Writing char-cs to 0x35 : 12020028<lh__id>000000000000000000000000 -> {‘rsp’: [‘wr’]}
    Reading char-cs from 0x35 -> 0012002800000000000000000000000000000000

Are you encoding it in little-endian into the command? (

You state diagram looks good to me, except the transitions related to 0x1202 command (I assume you describe the case with default ID) as per my observation above.

For me 0x1202 with default ID does not transition it from On (Temp) to On (Temp) as it actually does nothing. It does not keep the LH running in the same way 0x1201 does.
On the other hand 0x1202 with correct ID works as 0x1201, but allows custom timeout.
So I guess you probably should split those two cases as 0x1202 “default” and 0x1202 “authenticated”.

On the side note, I believe there is no “permanent configuration” stored on the LH. Once the LH is power cycled it basically starts in the mode “On (Perm)” until it is told to do otherwise. You might want to add power on state transition to your state diagram too.


Here is my take on the state diagram:


Thanks! Yep, endianess of the base station ID was my issue with 0x1202.

Looks like your findings align with mine, so think we’ve cracked this! :+1:


Actually, if it was not for you, I would not probably try the other commands or the states, because I already had a working setup (even it may sound a bit convoluted - running remote script on a Rapberry Pi - but it does exactly what I need and it is just one click on my desktop).

So your post was a good motivation for me to somehow complete the topic for which I am thankful. I am currently updating my GitHub repo with the additional info on the commands. If you could later review it, I would be happy.

I guess next step would be small window app and considering its nature I can imagine it could be just the minimalistic:

  • run the app = start the lighthouses
  • stop the app = stop the lighthouses

But I was looking into BT stack on Windows and do not feel very encouraged to go into that :slight_smile:


Thanks muchly!

I have nearly finished a Windows app that does just that (plus another mode to only keep the LHs awake if you have a headset connected). I’ve just got to get round an issue I’m having with some odd intermittent AccessDenied errors when getting the GATT characteristic (seems as if something in the Windows BT stack gets an exclusive lock on it - think it’s the discovery process as accessing the characteristic from the discovery thread always works).

Nearly there!


i thought of writing it in .net core 3.0 C# and as a WPF App.

and use the crossplattform BlueTooth LowEnergie Library to communicate with any accessable bt device:

But it should be also possible to write it with .net framework 4.5 + WPF without any crossplattform compatiblity. Can’t imagine someone wants to start/stop those lighthouses via his mobile phone or on a linux pc… if i am honest to myself. Or is there someone who liked to do it?


Might be useful though


Last time I wrote an application with GUI using Win32 API it was 20 years ago. Back then MFC was cutting edge and WTL was hardcore :slight_smile:. After that I was more low level guy. So when I was thinking about doing it on Windows today, I searched for some BT libs and did not find any (free). Then I looked at Win32 BT API (using sockets) and realized I was too old for this.

So I searched for some frameworks and found some BT LE examples in Qt, which I built and they did not work - literally. They built, run but did not detect any BT device around without any error.
Then I noticed the BT stack in WinRT, but since I am C++ guy and have not written really anything worthy in C# I started to consider the idea about using C++/WinRT (C++17 standard FTW!) with this runtime. This time, I built the project, and it worked fine (apart from some strange glitches when running service discovery), I found my lighthouses, could query them, felt like a good start.

And then I saw new “XAML” UI design and realized I am probably too old for this too :slight_smile:. So my last hope was a console app in C++/WinRT which would use the BT stack but would not need any UI - no one will probably use it :smiley:.

So I am quite curious how yours will turn out.


Do you know from where the error comes? When I was running my script for prolonged time, I noticed that from time to time there was an error reported when trying to connect to the lighthouse.

Usually the second attempt 2 seconds later resolved the issue. Once I saw like 2 or 3 failed attempts in a row then again it connected. I guess it could be some interference on the BT signal which gets the comm corrupted beyond repair. (I am running two BT thermometers at my place and even that I query them once in two minutes they log quite a lot comm drop outs.)

In the script I use I have 5 repetitions before the script gives up, and so far it was sufficient.


Hey Risa,

i am writing right now the previous mentioned system tray app for controlling the lighthouses. While using your map of possible statuses, i am little bit curious about this particular path:


Do you mean with that, that to keep the Lighthouses permanently on, we still have to ping them? Or did you just listed it to show that 0x1200 doesn’t change the state of the LH in case it’s already in permanent mode?

My best regards,


I guess I am used to write “useless” commands as the transition from one state to itself to stress out that the command actually does not change the state machine. But here I preferred to omit “useless” commands on the On (Timeout) state to avoid confusion with the commands which actually reset the timer.

So you are right, the state diagram is not consistent and I removed the transition (

In short, it did mean nothing :wink:

On the side note, I have looked at the BT lib you mentioned previously and it seemed to be only working for UWP and only allowing advertising and scanning, but no characteristic writing. Did I get it wrong, or are you using different lib?


thanks for clarifying,

I’ve started to write a .net core WPF app and managed to create a system tray icon & window as intended. But using WPF in combination with .net core (3.x / preview) comes at a price. I couldn’t find so far a method to use anything from any Bluetooth device at all.

Right now I am stalling the approach of using WPF and switching to UWP where at least I am sure that there are Bluetooth LE interfaces available. This comes with the cost that i have to use the new ‘notification hub’ from Windows 10 :confused:, but better that as nothing.

About, as far as I see it, it delivers all of its functions to all supported Platforms: Windows (UWP), Android and iOS. And the function I at least plan to use would be the following one, to be found in IBleGattServerConnection.cs.

/// <summary>
/// Write the given <paramref name=“data” /> to this characteristic’s value, returning the bytes that were written (in
/// almost all cases
/// this returns a byte array identical to the <paramref name=“data” /> passed as an argument).
/// </summary>
/// <exception cref=“GattServerConnectionLostException”>
/// If the connection has been lost before the request could be
/// completed.
/// </exception>
/// <exception cref=“GattException”>If there was an error performing the request.</exception>

Task<Byte[]> WriteCharacteristicValue( Guid service, Guid characteristic, Byte[] data );

P.s.: If everything fails, i still have the namespace: available in UWP Apps too.


What discouraged me was exactly this small note on the project page:

Note: Currently UWP only supports listening for broadcasts/advertisements, not connecting to devices.

So I gave up, after trying to implement the “sample” in console UWP app.
I would say that your best bet (if you are already commited to UWP) is “BluetoothLE” example from

I even made a desperate attempt at rewriting it to C++/WinRT console app, but figured out that it definitely does not come along. Using hstring instead of string and IIterable instead of vector in just the very first line of code I was trying to port was “too much” of Windows.