void SendTelemetry()
        {
            if (client == null)
            {
                return;
            }

            JSONObject json = new JSONObject(JSONObject.Type.OBJECT);

            json.AddField("msg_type", "telemetry");

            json.AddField("steering_angle", car.GetSteering() / steer_to_angle);
            json.AddField("throttle", car.GetThrottle());
            json.AddField("speed", car.GetVelocity().magnitude);
            json.AddField("image", Convert.ToBase64String(camSensor.GetImageBytes()));

            if (camSensorB != null && camSensorB.gameObject.activeInHierarchy)
            {
                json.AddField("imageb", Convert.ToBase64String(camSensorB.GetImageBytes()));
            }

            if (lidar != null && lidar.gameObject.activeInHierarchy)
            {
                json.AddField("lidar", lidar.GetOutputAsJson());
            }

            if (odom.Length > 0)
            {
                JSONObject odom_arr = JSONObject.Create();

                foreach (Odometry o in odom)
                {
                    odom_arr.Add(o.GetOutputAsJson());
                }

                json.AddField("odom", odom_arr);
            }

            json.AddField("hit", car.GetLastCollision());
            car.ClearLastCollision();
            json.AddField("time", Time.timeSinceLevelLoad);

            Vector3 accel = car.GetAccel();

            json.AddField("accel_x", accel.x);
            json.AddField("accel_y", accel.y);
            json.AddField("accel_z", accel.z);

            Quaternion gyro = car.GetGyro();

            json.AddField("gyro_x", gyro.x);
            json.AddField("gyro_y", gyro.y);
            json.AddField("gyro_z", gyro.z);
            json.AddField("gyro_w", gyro.w);


            // not intended to use in races, just to train
            if (extendedTelemetry)
            {
                Transform tm = car.GetTransform();
                json.AddField("pos_x", tm.position.x);
                json.AddField("pos_y", tm.position.y);
                json.AddField("pos_z", tm.position.z);

                Vector3 velocity = car.GetVelocity();
                json.AddField("vel_x", velocity.x);
                json.AddField("vel_y", velocity.y);
                json.AddField("vel_z", velocity.z);

                if (pm != null)
                {
                    float cte = 0.0f;
                    if (pm.path.GetCrossTrackErr(tm.position, ref cte))
                    {
                        json.AddField("cte", cte);
                    }
                    else
                    {
                        pm.path.ResetActiveSpan();
                        json.AddField("cte", 0.0f);
                    }
                    json.AddField("activeNode", pm.path.iActiveSpan);
                    json.AddField("totalNodes", pm.path.nodes.Count);
                }


                if (pm.path.nodes.Count > 10)
                {
                    NavMeshHit hit       = new NavMeshHit();
                    Vector3    position  = carObj.transform.position;
                    bool       onNavMesh = NavMesh.SamplePosition(position, out hit, 5, NavMesh.AllAreas);
                    json.AddField("on_road", onNavMesh ? 1 : 0);
                    if (onNavMesh)
                    {
                        Vector3 target               = pm.path.nodes[(pm.path.iActiveSpan + pm.path.nodes.Count + pm.path.nodes.Count / 3) % (pm.path.nodes.Count)].pos;
                        double  currentDistance      = pm.path.getDistance(position, target);
                        double  distanceToLastTarget = pm.path.getDistance(position, this.lastTarget);
                        if (this.lastTarget.x == 0 || Math.Abs(currentDistance) < 0.001 || Math.Abs(distanceToLastTarget) < 0.001 || Math.Abs(this.lastDistance) < 0.001)
                        {
                            this.lastDistance = currentDistance;
                        }
                        json.AddField("progress_on_shortest_path", (float)(this.lastDistance - distanceToLastTarget));
                        this.lastDistance = currentDistance;
                        this.lastTarget   = target;
                    }
                    else
                    {
                        this.lastTarget   = new Vector3(0, 0, 0);
                        this.lastDistance = 0.0;
                        json.AddField("progress_on_shortest_path", 0.0f);
                    }
                }
            }
            client.SendMsg(json);
        }
        void SendTelemetry()
        {
            if (client == null)
            {
                return;
            }

            JSONObject json = new JSONObject(JSONObject.Type.OBJECT);

            json.AddField("msg_type", "telemetry");

            json.AddField("steering_angle", car.GetSteering() / steer_to_angle);
            json.AddField("throttle", car.GetThrottle());

            json.AddField("image", Convert.ToBase64String(camSensor.GetImageBytes()));

            if (camSensorB != null && camSensorB.gameObject.activeInHierarchy)
            {
                json.AddField("imageb", Convert.ToBase64String(camSensorB.GetImageBytes()));
            }

            if (lidar != null && lidar.gameObject.activeInHierarchy)
            {
                json.AddField("lidar", lidar.GetOutputAsJson());
            }

            foreach (Odometry o in odom)
            {
                json.AddField(o.Label, o.GetNumberRotations());
            }


            json.AddField("hit", car.GetLastCollision());
            car.ClearLastCollision();
            json.AddField("time", Time.timeSinceLevelLoad);

            Vector3 velocity = car.GetVelocity() / 8.0f;

            json.AddField("speed", velocity.magnitude);

            Vector3 accel = car.GetAccel() / 8.0f;

            json.AddField("accel_x", accel.x);
            json.AddField("accel_y", accel.y);
            json.AddField("accel_z", accel.z);

            Vector3 gyro = car.GetGyro();

            json.AddField("gyro_x", gyro.x);
            json.AddField("gyro_y", gyro.y);
            json.AddField("gyro_z", gyro.z);

            Transform tm          = car.GetTransform();
            Vector3   eulerAngles = tm.rotation.eulerAngles;

            json.AddField("pitch", eulerAngles.x);
            json.AddField("yaw", eulerAngles.y);
            json.AddField("roll", eulerAngles.z);

            if (pm != null)
            {
                float cte = 0.0f;
                pm.carPath.GetCrossTrackErr(tm.position, ref iActiveSpan, ref cte); // get distance to closest node
                if (GlobalState.extendedTelemetry)
                {
                    json.AddField("cte", cte);
                }

                json.AddField("activeNode", iActiveSpan);
                json.AddField("totalNodes", pm.carPath.nodes.Count);
            }

            // not intended to use in races, just to train
            if (GlobalState.extendedTelemetry)
            {
                Vector3 pos = tm.position / 8.0f;
                json.AddField("pos_x", pos.x);
                json.AddField("pos_y", pos.y);
                json.AddField("pos_z", pos.z);

                json.AddField("vel_x", velocity.x);
                json.AddField("vel_y", velocity.y);
                json.AddField("vel_z", velocity.z);
            }
            client.SendMsg(json);
        }