SDK Tutorial

A quick start guide for the Flyby SDK.

Download & Setup

The latest SDK release can be found here, and an example project can be found here.

The simplest way to get starting is by copying the example project structure and CMake file.

For this example, we will assume we have a payload that is composed of a single camera on a gimbal, and the camera produces one video stream.

Getting Started

The main entry point into the drone ecosystem is through the Drone object. This object is how our new payload register itself with the drone and make itself available to the controller.

To create a new Drone object, we use the DroneFactory::create_drone() method. This returns a new shared_ptr to the Drone object.

auto drone = DroneFactory::create_drone();

Now that we have access to the drone, the next step is creating the Payload object.

Payload

Every payload has to inherit from the flyby::Payload object. Our new payload class should call the following base constructor:

Payload(const std::string& name, const std::string& version);

The Payload object will contain all of our payload components, is solely responsible for creating these components. Payloads are composed of streams and components such as gimbals.

class SimplePayload : public Payload {
public:
	SimplePayload() : Payload { "Simple Payload", "1.0.0" } {
		// Construct payload components
	}

	~SimplePayload() override = default;
};

We can now register our payload object with the drone.

auto payload = std::make_shared<SimplePayload>();

drone->register_payload(payload);

Since our payload does not contain any components, we have no way of interacting with it through the controller. Let's fix that.

Components

Every payload component should inherit from its corresponding class. For example, cameras should inherit from Camera, and gimbals should inherit from Gimbal. The main responsibility of these objects is to connect the controller to the hardware, and we do so by overriding functionality that the hardware can provide. For example, a camera that can take pictures should override the take_picture function, and that function should take a picture on the corresponding camera.

Gimbal

All gimbals should inherit from the Gimbal object. Gimbals are controlled through the controller, which sends roll, pitch, and yaw values for the gimbal. The controller displays the current gimbal pitch, and so the gimbal class should override the get_pitch function. In order for the controller to control the gimbal's movements, it should also override the set_roll_pitch_yaw_speed function.

class SimpleGimbal : public Gimbal {
public:
	SimpleGimbal() : Gimbal() {}

	~SimpleGimbal() override = default;

	void set_roll_pitch_yaw_speed(double roll, double pitch, double yaw) override {
		// Set the speed on the gimbal
	}

    	double get_pitch() const override {
		// Return the current pitch of the gimbal
    	}
};

We can now revisit the SimplePayload constructor to include our new gimbal.

SimplePayload() : Payload { "Simple Payload", "1.0.0" } {
        auto m_gimbal = std::make_shared<SimpleGimbal>();
        register_gimbal(m_gimbal);
		
        // Construct payload components
}

Camera & Stream

Streams are composed of cameras, so let us focus on the camera object first. Similar to gimbals, cameras should inherit from the Camera object and implement the functionality that they provide.

Suppose our camera can take videos, but does not have any picture functionality or zoom functionality. For video controls, we will set m_video = true, and set the other two to false. Since our camera is mounted on our gimbal, we will set m_gimbal = true so that, while viewing this camera's stream, we will be able to control the gimbal through the controller. These settings correspond to the UI visibility on the controller.

class SimpleCamera : public Camera {
public:
	SimpleCamera() : Camera("Simple Camera") {
		m_picture = false;
		m_video = true;
		m_zoom = false;
		m_gimbal = true;
	}

	~SimpleCamera() override = default;

	void start_video_recording() override {
		m_is_recording = true; // Only set if the camera started recording
		// Initiate video recording
	}

	void stop_video_recording() override {
		m_is_recording = false; // Only set if the camera stopped recording
		// Stop video recording
	}
};

This camera object tells the controller that this camera is mounted on a gimbal and can take videos, and when the user presses the video buttons the controller they trigger the overriden functions that are present above.

With our camera object implemented, we can now focus on the stream. Streams are responsible for creating and owning their own camera objects, and starting some streaming method. For the former, we create our camera object in the constructor and register it with the stream.

class SimpleStream : public Stream {
public:
	explicit SimpleStream() : Stream("Simple Stream") {
		auto m_camera = std::make_shared<SimpleCamera>();

		register_camera(m_camera);
	}

	~SimpleStream() override = default;
	
	// Other functionality
}

With our stream object configured and our cameras set up, all that is left is to start an actual stream. The stream object will get a new endpoint to publish a stream to, and is responsible to specify when the stream is alive. We do this by overriding the start_stream function.

void start_stream(
    unsigned int endpoint_idx,
    std::function<void(std::string)> set_alive_callback
) override {
	// Publish a stream to rtsp://0.0.0.0:8554/<endpoint_idx>
	// Can use any library, such as gstreamer or ffmpeg
	
	set_alive_callback(get_uuid()); // Call when the stream is alive
}

Lastly, we update the payload constructor to register this stream as follows:

SimplePayload() : Payload { "Simple Payload", "1.0.0" } {
        auto m_gimbal = std::make_shared<SimpleGimbal>();
        register_gimbal(m_gimbal);
		
	auto m_stream = std::mak_shared<SimpleStream>();
	register_stream(m_stream);
}

Finally, we have created and registered all of the relevant payload components. All that is left to do is to start the payload server.

int main(int argc, char** argv) {
	auto drone = DroneFactory::create_drone();

	auto payload = std::make_shared<SimplePayload>();

	drone->register_payload(payload);

	drone->start_server();
}

Last updated