 }
 
 extension Data {
-    // Convert Data containing key=value pairs into a Dictionary<String:String>
+    /// Convert Data containing space-separated key=value pairs into a Dictionary - see String.kvToDict
     func kvToDict() -> [String: String]? {
         if let kvPairs = String.init(data: self, encoding: .utf8){
             return kvPairs.kvToDict()

import Foundation

extension String {
    func kvToDict() -> [String: String]? {
        let vals = self
            .split(separator: " ")

    }

    // Configuration
    /// Focal length, in mm
    var focalLength = 35.0
    /// Sensor width, in mm
    var sensorWidth = 36.0
    /// Sensor height, in mm
    var sensorHeight = 24.0
    /// Shutter speed, in seconds
    var shutter = 0.1
    /// Time to wait before shutter actuation, in seconds
    var preShutter = 0.0
    /// Time to wait after shutter actuation, in seconds
    var postShutter = 0.0
    /// Max time to wait for platform shake low enough to take the photo, in seconds
    var zeroMotionWait = 0.0
    /// If true, hold shutter down for the entire shutter period instead of a few ms
    var shutterLong = false
    /// Number of shots to take in each position
    var shotCount = 1
    /// Panorama horizontal field of view, in degrees 1-360°
    var panoHorizFOV = 180.0
    /// Panorama vertical field of view, in degrees 1-180°
    var panoVertFOV = 90.0
    /// How much should the photo edges overlap, 0.01 - 0.9 for 1% - 90%
    var overlap = 0.2
    /// Allow continuous horizontal rotation, set only if there are no external cables to wrap
    var infiniteRotation = false
    /// How many stops of Image Stabilization we can count on
    var stabilizationStops = 0.0
    /// Pano execution order, RowFirst does complete rows, ColumnFirst does complete columns
    var gridOrder: GridOrder = .RowFirst

    // State
    /// linear position of this shot (0..<rows*cols)
    var position = 0

    // Computed values
    /// Horizontal move to advance one cell, in degrees
    var horizCellMove = 0.0
    /// Vertical move to advance one cell, in degrees
    var vertCellMove = 0.0
    /// Computed number of rows needed to cover the horizontal pano FOV
    var rows = 0
    /// Computed number of columns needed to cover the vertical pano FOV
    var cols = 0

    // G-Code Interpreter Status and Configs
    /// Hardware platform configuration received from remote
    var platform: [String: String] = [:]

    /// Initialized G-Code Generator
    var program: AnyIterator<String>?

    // While harder on the eyes, this approach allows in-flight changing parameters like position, for example
    /**
     Generate the G-code program to execute the current pano step by step.
     - Returns: iterator over a list of dynamically generated strings containing the G-code program
     */
    var gCode: AnyIterator<String> {
        computeGrid()
        var commandBuffer = ["M17 M320 G1 G91 G92.1",
