Skip to content
This repository has been archived by the owner on May 3, 2021. It is now read-only.

PSMoveService Programmer Notes

Guido Sanchez edited this page Mar 29, 2017 · 3 revisions

Overview

PSMoveService is an application that runs on the computer connected to the PSMove controller and the camera (e.g., PSEye). This application handles management of the devices, and it serves data from the devices via UDP to any registered clients. It also accepts commands from registered clients via TCP (e.g., pair controller, change controller LED colour, rumble).

PSMoveService

The only thing in the EntryPoint's main is the instantiation of a PSMoveService app object and a call to app.exec(). app.exec() handles the command-line arguments and then instantiates and "launches" a PSMoveServiceImpl object. "Launching" happens through PSMoveServiceImpl's '()' operator, which starts with a call to startup(), then in the application loop it calls update(), and finally shutdown(). Let's look at each step from initialization to shutdown.

Initialize PSMoveServiceImpl object.

PSMoveServiceImpl has relevant member variables (initialized in this order):

* `DeviceManager` `m_device_manager()`
* `ServerRequestHandler` `m_request_handler(&m_device_manager)`
* `ServerNetworkManager` `m_network_manager(&m_io_service, PSMOVE_SERVER_PORT, &m_request_handler)`

m_device_manager via DeviceManager::DeviceManager()

Relevant DeviceManager member variables, instantiated by constructor:

* `ControllerManager m_controller_manager`
* `TrackerManager m_tracker_manager`
* `HMDManager m_hmd_manager`

Each of these device-type managers is a child of DeviceTypeManager. Their constructors allocate an array of Server<device>ViewPtrs in m_devices. See the descriptions of ServerControllerView, ServerTrackerView, and ServerHMDView below.

m_request_handler via ServerRequestHandler::ServerRequestHandler(DeviceManager *m_device_manager)

ServerRequestHandler uses the PImpl idiom. ServerRequestHandlerImpl::ServerRequestHandlerImpl retains a reference to the device manager and initializes a t_connection_state_map m_connection_state_map. The m_connection_state_map maintains a list of connection states, one for each registered client. The connection state RequestConnectionState includes which device streams the client is tracking, and the stream info for those streams. TODO: TrackerStreamInfo and HMDStreamInfo

m_network_manager via ServerNetworkManager::ServerNetworkManager(boost::asio::io_service *io_service, unsigned port, ServerRequestHandler *request_handler)

ServerNetworkManager also uses the PImpl idiom and has singleton access. ServerNetworkManagerImpl::ServerNetworkManagerImpl([...]) maintains a reference to the request_handler, m_io_service, tcp and udp connection information, and a t_client_connection_map named m_connections. m_connections maps ints to an instance of ClientConnection, one for each connected client. ClientConnection maintains TCP and UDP connection state to a single client, handles async socket callbacks on the connection, and routes requests through the request handler. TODO: ClientConnection needs tracker and hmd data_frames.

PSMoveServiceImpl::startup()

* `m_network_manager.startup()`
* `m_device_manager.startup()`
* `m_request_handler.startup()`

m_network_manager.startup()

ServerNetworkManagerImpl::start_connection_accept() ClientConnection::create then add the id-connection mapping to m_connections. Then use m_tcp_acceptor to async get new tcp client with handler ServerNetworkManagerImpl::handle_tcp_accept. Call start_udp_receive_connection_id() which uses m_udp_socket to start an async read with handler ServerNetworkManagerImpl::handle_udp_read_connection_id.

ServerNetworkManagerImpl::handle_tcp_accept(ClientConnectionPtr connection, const boost::system::error_code& error) should always be called before handle_udp_read_connection_id because the client should always make a tcp connection before a udp connection. This tells the tcp connection->start() and start_connection_accept() again to accept another client.

ServerNetworkManagerImpl::handle_udp_read_connection_id(...) binds the remote endpoint to the UDP connection map. Then this sends an async result to the remote endpoint with handler ServerNetworkManagerImpl::handle_udp_write_connection_result, which eventually calls start_udp_receive_connection_id() to look for the next UDP client.

m_device_manager.startup()

DeviceManager::startup() calls each specific manager's .startup(). This should initialize any hardware communication services required. TODO: Implement TrackerManager::startup and HMDManager::startup() if needed.

ServerRequestHandler m_request_handler.startup()

This doesn't appear to do much.

PSMoveServiceImpl::update() in application loop:

* `m_request_handler.update()`
* `m_device_manager.update()`
* `m_network_manager.update()`

m_request_handler.update()

Update any async requests still waiting to complete. Iterate through m_connection_state_map. If the connection state has a pending bluetooth request then update that. TODO: As pending bluetooth requests are unique to the controller, we may need to add functionality here for tracker and HMD.

m_device_manager.update()

DeviceManager::update() calls each specific manager's .poll(), .updateStateAndPredict(), and .publish() in a specific order. These methods are described in more detail in the sections below about the Device<Type>Managers.

m_network_manager.update()

Process incoming/outgoing networking requests. ServerNetworkManagerImpl::poll() calls start_udp_queued_data_frame_write() (see below) and m_io_service.poll() to handle the callbacks. This continues as long as has_queued_controller_data_frames_ready_to_start(). TODO: Keep polling as long as there are ANY data frames ready to start, not just controller.

start_udp_queued_data_frame_write() iterates through m_connections, calling start_udp_write_queued_controller_data_frame() on each one. TODO: start ALL device data frames.

ServerNetworkManagerImpl::start_udp_write_queued_controller_data_frame() checks m_pending_dataframes (currently device-specific) and if not empty, pulls out a ControllerDataFramePtr, packs it, and sends it to async udp write with callback &ClientConnection::handle_udp_write_controller_data_frame_complete. This just pops the frame from the queue if completed.

The ControllerDataFramePtr gets added to the queue below (see add_controller_data_frame_to_write_queue(...)). ControllerDataFramePtr is a std::shared_ptr of PSMoveProtocol::ControllerDataFrame, which is created by protobuf. See the wiki page about PSMoveProtocol.

PSMoveServiceImpl::shutdown()

This is not very interesting. Skip documentation for now.

DeviceTypeManagers

During the application loop we call m_device_manager.update(). We glossed over this above but we will go into more detail here. This function has the following contents.

    m_controller_manager.poll(); // Update controller counts and poll button/IMU state
    m_tracker_manager.poll(); // Update tracker count and poll video frames
    m_hmd_manager.poll(); // Update HMD count and poll position/orientation state

    m_tracker_manager.updateStateAndPredict(); // Get controller colors and update tracking blob positions/predictions
    m_controller_manager.updateStateAndPredict(); // Compute pose/prediction of tracking blob+IMU state
    m_hmd_manager.updateStateAndPredict(); // Get the pose + prediction for HMD

    m_controller_manager.publish(); // publish controller state to any listening clients  (common case)
    m_tracker_manager.publish(); // publish tracker state to any listening clients (probably only used by ConfigTool)
    m_hmd_manager.publish(); // publish hmd state to any listening clients (probably only used by ConfigTool)

Each m__manager is an instance of the respective DeviceManager, which is a child of DeviceTypeManager.

DeviceTypeManager::poll()

Each m__manager.poll() is handled by the parent class method.

This calls poll_devices() and update_connected_devices() if sufficient time has elapsed (poll_interval and reconnect_interval). poll_devices() simply iterates through each device in m_devices and calls its poll(). Each m_device is a pointer to a ServerDeviceView (see below). update_connected_devices() adds/removes devices to the m_devices list; this is device-type-specific.

DeviceTypeManager::updateStateAndPredict()

This will derive a state from the data and update a model that allows prediction. This is just a wrapper around the (ServerDeviceViewPtr) device->updateStateAndPredict() which is purely virtual and implemented by the device-specific views.

update_connected_devices()
  1. Allocate a DeviceEnumerator (specific enumerator allocated by derived DeviceTypeManager)
  2. Process device shuffling or attachments
    • See if any devices shuffled order OR if any new devices were attached.
    • Migrate open devices to a new temp list in the order that they appear in the device enumerator.
  3. Process device removal
    • Close any remaining open controllers not listed in the device enumerator.
    • Copy over any closed controllers to the temp.
  4. Copy the temp controller list back over top the original list.
    • send_device_list_changed_notification() is device_type-agnostic.
  5. Free the device enumerator
  6. Send notification to connected clients if the device list changed

DeviceTypeManager::publish()

This simply iterates through each device in m_devices and calls its publish().

Let's look at each derived DeviceTypeManager.

ControllerManager

Derived DeviceTypeManager that manages all connected controller types. Creates a ControllerDeviceEnumerator when scanning for connected controllers. Specific controller views can be accessed from this manager. Currently the only supported controller type is the PSMoveController.

TrackerManager

Derived DeviceTypeManager that manages all connected camera types. Creates a TrackerDeviceEnumerator when scanning for connected camera. Specific tracker views can be accessed from this manager. Controller connections can't be updated while a controller pairing request is running. Currently the only supported tracker type is the PS3EyeTracker.

HMDManager

Derived DeviceTypeManager that manages all connected HMD types. Creates a HMDDeviceEnumerator when scanning for connected HMDs. Specific tracker views can be accessed from this manager. Currently the only supported tracker type is the DK2.

ServerDeviceView

Next we will look at the device poll() and publish() methods of of the devices which are called during DeviceTypeManager::poll_devices() and DeviceTypeManager::publish(), respectively. These are called on the upcast form of the device so we call the generic ServerDeviceView parent class methods.

ServerDeviceView::poll()

This gets the (upcast) *IDeviceInterface with getDevice() then calls its ->poll() method. If polling was successful, then we mark the device as having an unpublished state. IDeviceInterface::poll() is virtual and must be implemented by each specific Interface (see below for details of the devices).

ServerDeviceView::publish()

A wrapper around ServerDeviceView::publish_device_data_frame(). This is device specific and is implemented by the child class. It will tell the instance of the ServerRequestHandler to publish_<type>_data_frame with a type-specific callback (e.g., &ServerControllerView::generate_controller_data_frame_for_stream) that is called for each listening connection.

Let's look at the device-specific implementations, including updateStateAndPredict() called during DeviceTypeManager::updateStateAndPredict().

Devices

Controller

ServerControllerView::updateStateAndPredict()

Currently does nothing. TODO: Update state space model.

ServerControllerView::publish_device_data_frame()

ServerRequestHandler::get_instance()->publish_controller_data_frame(...). For each connection id,state , get a new ControllerDataFramePtr and send it with the ServerControllerView through the callback to build the frame, then ServerNetworkManager::get_instance()->send_controller_data_frame(connection_id, data_frame) to send it across the network.

Callback: ServerControllerView::generate_controller_data_frame_for_stream calls generate_psmove_data_frame_for_stream (or alternative for navi). This does all the work of actually filling out the data_frame to be transmitted with the current state information pulled from the hardware. This makes heavy use of PSMoveProtocol.

ServerNetworkManagerImpl::send_controller_data_frame(connection_id, data_frame) finds the right connection, then connection->add_controller_data_frame_to_write_queue(data_frame) which simply calls m_pending_dataframes.push_back(data_frame), and finally start_udp_queued_data_frame_write() which we mentioned above.

PSMoveController::poll()

This does all the work in communicating with the hardware and copying information to a state.

Tracker

ServerTrackerView::updateStateAndPredict()

Currently does nothing.

ServerTrackerView::publish_device_data_frame()

TODO: Implement ServerRequestHandler::get_instance()->publish_tracker_data_frame(...). This will call ServerNetworkManager::get_instance()->send_tracker_data_frame(connection_id, data_frame); TODO: TrackerDataFramePtr.

TODO: Implement ServerNetworkManager::send_tracker_data_frame. Adds the frame to the write queue and starts the UDP write. Does this have to be device-type-specific? What about just overloading using the data_frame argument type?

TODO: Implement ServerTrackerView::generate_tracker_data_frame_for_stream

PSMoveTracker::poll()

TODO: Implement PSMoveTracker in PSMoveTracker.cpp, including poll().

HMD

ServerHMDView::updateStateAndPredict()

Currently does nothing.

ServerHMDView::publish_device_data_frame()

TODO: Implement ServerRequestHandler::get_instance()->publish_tracker_data_frame(...). This will call ServerNetworkManager::get_instance()->send_hmd_data_frame(connection_id, data_frame);

TODO: Implement ServerNetworkManager::send_hmd_data_frame.

TODO: Implement ServerTrackerView::generate_hmd_data_frame_for_stream

PSMoveHMD::poll()

TODO: Implement PSMoveHMD in PSMoveHMD.cpp, including poll().

Device Enumerators

The DeviceEnumerator is a simple iterator that walks over all connected devices of a specified device class that match a USB VendorID and ProductID. DeviceManagers that derive from DeviceTypeMangager allocate enumerators derived from DeviceEnumerator.

ControllerDeviceEnumerator

Iterates over connected HID controllers of the following types using HIDAPI:

  • VID:0x054c/PID:0x03d4 (Vendor:Sony, Product:PSMove)
  • VID:0x054c/PID:0x042f (Vendor:Sony, Product:PSNavi)

TrackerDeviceEnumerator

Iterates over connected cameras of the following types using LIBUSB:

  • VID:0x1415/PID:0x2000 (Vendor:Sony, Product:PS3EYE)

HMDDeviceEnumerator

Iterates over connected HMDs of the following types using HIDAPI:

  • VID:0x2833/PID:0x0021 (Vendor:Oculus, Product:DK2)