/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have Weboreceived a copy of the GNU General Public License along with this program. If not, see . */ /* simulator connector for webots simulator */ #include "SIM_Webots.h" #include #include #include #include #include #include #include #include #include #include "pthread.h" #include extern const AP_HAL::HAL& hal; using namespace SITL; static const struct { const char *name; float value; bool save; } sim_defaults[] = { { "AHRS_EKF_TYPE", 10 }, { "INS_GYR_CAL", 0 }, { "RC1_MIN", 1000, true }, { "RC1_MAX", 2000, true }, { "RC2_MIN", 1000, true }, { "RC2_MAX", 2000, true }, { "RC3_MIN", 1000, true }, { "RC3_MAX", 2000, true }, { "RC4_MIN", 1000, true }, { "RC4_MAX", 2000, true }, { "RC2_REVERSED", 1 }, // interlink has reversed rc2 { "SERVO1_MIN", 1000 }, { "SERVO1_MAX", 2000 }, { "SERVO2_MIN", 1000 }, { "SERVO2_MAX", 2000 }, { "SERVO3_MIN", 1000 }, { "SERVO3_MAX", 2000 }, { "SERVO4_MIN", 1000 }, { "SERVO4_MAX", 2000 }, { "SERVO5_MIN", 1000 }, { "SERVO5_MAX", 2000 }, { "SERVO6_MIN", 1000 }, { "SERVO6_MAX", 2000 }, { "SERVO6_MIN", 1000 }, { "SERVO6_MAX", 2000 }, { "INS_ACC2OFFS_X", 0.001 }, { "INS_ACC2OFFS_Y", 0.001 }, { "INS_ACC2OFFS_Z", 0.001 }, { "INS_ACC2SCAL_X", 1.001 }, { "INS_ACC2SCAL_Y", 1.001 }, { "INS_ACC2SCAL_Z", 1.001 }, { "INS_ACCOFFS_X", 0.001 }, { "INS_ACCOFFS_Y", 0.001 }, { "INS_ACCOFFS_Z", 0.001 }, { "INS_ACCSCAL_X", 1.001 }, { "INS_ACCSCAL_Y", 1.001 }, { "INS_ACCSCAL_Z", 1.001 }, }; Webots::Webots(const char *frame_str) : Aircraft(frame_str) { use_time_sync = true; rate_hz = 4000; char *saveptr = nullptr; char *s = strdup(frame_str); char *frame_option = strtok_r(s, ":", &saveptr); char *args1 = strtok_r(nullptr, ":", &saveptr); char *args2 = strtok_r(nullptr, ":", &saveptr); /* allow setting of IP, sensors port and control port format morse:IPADDRESS:SENSORS_PORT:CONTROL_PORT */ if (args1) { webots_ip = args1; } if (args2) { webots_sensors_port = atoi(args2); } if (strstr(frame_option, "-rover")) { output_type = OUTPUT_ROVER; } else if (strstr(frame_option, "-quad")) { output_type = OUTPUT_QUAD; } else if (strstr(frame_option, "-pwm")) { output_type = OUTPUT_PWM; } else { // default to rover output_type = OUTPUT_ROVER; } for (uint8_t i=0; iconfigured()) { p->save(); } } } printf("Started Webots with %s:%u type %u\n", webots_ip, webots_sensors_port, (unsigned)output_type); } /* very simple JSON parser for sensor data called with pointer to one row of sensor data, nul terminated This parser does not do any syntax checking, and is not at all general purpose {"timestamp": 1563474924.817575, "vehicle.imu": {"timestamp": 1563474924.8009083, "angular_velocity": [2.319516170246061e-06, -3.5830129263558774e-07, 7.009341995711793e-09], "linear_acceleration": [0.005075275432318449, 0.22471635043621063, 9.80748176574707], "magnetic_field": [23088.65625, 3875.89453125, -53204.51171875]}, "vehicle.gps": {"timestamp": 1563474924.8009083, "x": 5.386466364143416e-05, "y": -0.0010969983413815498, "z": 0.03717954829335213}, "vehicle.velocity": {"timestamp": 1563474924.8009083, "linear_velocity": [4.818238585890811e-10, 2.1333558919423012e-08, 9.310780910709582e-07], "angular_velocity": [2.319516170246061e-06, -3.5830129263558774e-07, 7.009341995711793e-09], "world_linear_velocity": [5.551115123125783e-17, 0.0, 9.313225746154785e-07]}, "vehicle.pose": {"timestamp": 1563474924.8009083, "x": 5.386466364143416e-05, "y": -0.0010969983413815498, "z": 0.03717954829335213, "yaw": 7.137723878258839e-05, "pitch": -0.0005173543468117714, "roll": 0.022908739745616913}} */ bool Webots::parse_sensors(const char *json) { //printf("%s\n", json); ///* for (uint16_t i=0; ix, &v->y, &v->z) != 3) { printf("Failed to parse Vector3f for %s %s/%s\n",p, key.section, key.key); //printf("Failed to parse Vector3f for %s/%s\n", key.section, key.key); return false; } else { //printf("GOT %s/%s [%f, %f, %f]\n", key.section, key.key, v->x, v->y, v->z); } break; } case DATA_VECTOR3F_ARRAY: { // example: [[0.0, 0.0, 0.0], [-8.97607135772705, -8.976069450378418, -8.642673492431641e-07]] if (*p++ != '[') { return false; } uint16_t n = 0; struct vector3f_array *v = (struct vector3f_array *)key.ptr; while (true) { if (n >= v->length) { Vector3f *d = (Vector3f *)realloc(v->data, sizeof(Vector3f)*(n+1)); if (d == nullptr) { return false; } v->data = d; v->length = n+1; } if (sscanf(p, "[%f, %f, %f]", &v->data[n].x, &v->data[n].y, &v->data[n].z) != 3) { //printf("Failed to parse Vector3f for %s %s/%s[%u]\n",p, key.section, key.key, n); //printf("Failed to parse Vector3f for %s/%s[%u]\n", key.section, key.key, n); return false; } else { //printf("GOT %s/%s [%f, %f, %f]\n", key.section, key.key, v->data[n].x, v->data[n].y, v->data[n].z); } n++; p = strchr(p,']'); if (!p) { return false; } p++; if (p[0] != ',') { break; } if (p[1] != ' ') { return false; } p += 2; } if (p[0] != ']') { return false; } v->length = n; break; } case DATA_FLOAT_ARRAY: { // example: [18.0, 12.694079399108887] if (*p++ != '[') { return false; } uint16_t n = 0; struct float_array *v = (struct float_array *)key.ptr; while (true) { if (n >= v->length) { float *d = (float *)realloc(v->data, sizeof(float)*(n+1)); if (d == nullptr) { return false; } v->data = d; v->length = n+1; } v->data[n] = atof(p); n++; p = strchr(p,','); if (!p) { break; } p++; } v->length = n; break; } } } // */ socket_frame_counter++; return true; } /* connect to the required sockets */ bool Webots::connect_sockets(void) { if (!sim_sock) { sim_sock = new SocketAPM(false); if (!sim_sock) { AP_HAL::panic("Out of memory for sensors socket"); } if (!sim_sock->connect(webots_ip, webots_sensors_port)) { usleep(100000); if (connect_counter++ == 20) { printf("Waiting to connect to sensors control on %s:%u\n", webots_ip, webots_sensors_port); connect_counter = 0; } delete sim_sock; sim_sock = nullptr; return false; } sim_sock->reuseaddress(); printf("Sensors connected\n"); } return true; } /* get any new data from the sensors socket */ bool Webots::sensors_receive(void) { ssize_t ret = sim_sock->recv(&sensor_buffer[sensor_buffer_len], sizeof(sensor_buffer)-sensor_buffer_len, 0); if (ret <= 0) { no_data_counter++; if (no_data_counter == 1000) { no_data_counter = 0; delete sim_sock; sim_sock = nullptr; } return false; } no_data_counter = 0; // convert '\n' into nul while (uint8_t *p = (uint8_t *)memchr(&sensor_buffer[sensor_buffer_len], '\n', ret)) { *p = 0; } sensor_buffer_len += ret; const uint8_t *p2 = (const uint8_t *)memrchr(sensor_buffer, 0, sensor_buffer_len); if (p2 == nullptr || p2 == sensor_buffer) { return false; } const uint8_t *p1 = (const uint8_t *)memrchr(sensor_buffer, 0, p2 - sensor_buffer); if (p1 == nullptr) { return false; } bool parse_ok = parse_sensors((const char *)(p1+1)); memmove(sensor_buffer, p2, sensor_buffer_len - (p2 - sensor_buffer)); sensor_buffer_len = sensor_buffer_len - (p2 - sensor_buffer); return parse_ok; } /* output control command assuming skid-steering rover */ void Webots::output_rover(const struct sitl_input &input) { const float motor1 = 2*((input.servos[0]-1000)/1000.0f - 0.5f); const float motor2 = 2*((input.servos[2]-1000)/1000.0f - 0.5f); // construct a JSON packet for v and w char buf[60]; snprintf(buf, sizeof(buf)-1, "{\"rover\": [%f, %f]}\n", motor1, motor2); //printf("rover motors m1: %f m2: %f\n", steer_angle, speed_ms); buf[sizeof(buf)-1] = 0; sim_sock->send(buf, strlen(buf)); } /* output control command assuming a 4 channel quad */ void Webots::output_quad(const struct sitl_input &input) { const float max_thrust = 1.0; float motors[4]; for (uint8_t i=0; i<4; i++) { //return a filtered servo input as a value from 0 to 1 //servo is assumed to be 1000 to 2000 motors[i] = constrain_float(((input.servos[i]-1000)/1000.0f) * max_thrust, 0, max_thrust); } const float &m_right = motors[0]; const float &m_left = motors[1]; const float &m_front = motors[2]; const float &m_back = motors[3]; // quad format in Webots is: // m1: front // m2: right // m3: back // m4: left // construct a JSON packet for motors char buf[60]; snprintf(buf, sizeof(buf)-1, "{\"engines\": [%f, %f, %f, %f]}\n", m_front, m_right, m_back, m_left); buf[sizeof(buf)-1] = 0; sim_sock->send(buf, strlen(buf)); } /* output all 16 channels as PWM values. This allows for general control of a robot */ void Webots::output_pwm(const struct sitl_input &input) { char buf[200]; snprintf(buf, sizeof(buf)-1, "{\"pwm\": [%u, %uf, %u, %u, %u, %uf, %u, %u, %u, %uf, %u, %u, %u, %uf, %u, %u]}\n", input.servos[0], input.servos[1], input.servos[2], input.servos[3], input.servos[4], input.servos[5], input.servos[6], input.servos[7], input.servos[8], input.servos[9], input.servos[10], input.servos[11], input.servos[12], input.servos[13], input.servos[14], input.servos[15]); buf[sizeof(buf)-1] = 0; sim_sock->send(buf, strlen(buf)); } void Webots::output (const struct sitl_input &input) { switch (output_type) { case OUTPUT_ROVER: output_rover(input); break; case OUTPUT_QUAD: output_quad(input); break; case OUTPUT_PWM: output_pwm(input); break; } } /* update the Webots simulation by one time step */ void Webots::update(const struct sitl_input &input) { if (!connect_sockets()) { return; } last_state = state; const bool valid = sensors_receive(); if (valid) { // update average frame time used for extrapolation double dt = constrain_float(state.timestamp - last_state.timestamp, 0.001, 1.0/50); if (average_frame_time_s < 1.0e-6) { //if average is too small take the current dt as a good start. average_frame_time_s = dt; } // this is complementry filter for updating average. average_frame_time_s = average_frame_time_s * 0.98 + dt * 0.02; } // again measure dt as dt_s but with no constraints.... double dt_s = state.timestamp - last_state.timestamp; if (dt_s < 0 || dt_s > 1) { // cope with restarting while connected initial_time_s = time_now_us * 1.0e-6f; return; } if (dt_s < 0.00001f) { float delta_time = 0.001; // don't go past the next expected frame if (delta_time + extrapolated_s > average_frame_time_s) { delta_time = average_frame_time_s - extrapolated_s; } // check if extrapolation_s duration is longer than average_frame_time_s if (delta_time <= 0) { // dont extrapolate anymore untill there is valid data with long-enough dt_s usleep(1000); return; } // extrapolated_s duration is safe. keep on extrapolation. time_now_us += delta_time * 1.0e6; extrapolate_sensors(delta_time); update_position(); //update body magnetic field from position and rotation update_mag_field_bf(); usleep(delta_time * 1.0e6); extrapolated_s += delta_time; //output(input); report_FPS(); return; } if (valid) { // reset extrapolation duration. extrapolated_s = 0; if (initial_time_s <= 0) { dt_s = 0.001f; initial_time_s = state.timestamp - dt_s; } // convert from state variables to ardupilot conventions dcm.from_euler(state.pose.roll, state.pose.pitch, -state.pose.yaw); gyro = Vector3f(state.imu.angular_velocity[0] , state.imu.angular_velocity[1] , -state.imu.angular_velocity[2] ); accel_body = Vector3f(+state.imu.linear_acceleration[0], +state.imu.linear_acceleration[1], -state.imu.linear_acceleration[2]); velocity_ef = Vector3f(+state.velocity.world_linear_velocity[0], +state.velocity.world_linear_velocity[1], -state.velocity.world_linear_velocity[2]); position = Vector3f(state.gps.x, state.gps.y, -state.gps.z); // limit to 16G to match pixhawk1 float a_limit = GRAVITY_MSS*16; accel_body.x = constrain_float(accel_body.x, -a_limit, a_limit); accel_body.y = constrain_float(accel_body.y, -a_limit, a_limit); accel_body.z = constrain_float(accel_body.z, -a_limit, a_limit); // fill in laser scanner results, if available scanner.points = state.scanner.points; scanner.ranges = state.scanner.ranges; update_position(); uint64_t new_time_us = (state.timestamp - initial_time_s)*1.0e6; if (new_time_us < time_now_us) { uint64_t dt_us = time_now_us - new_time_us; if (dt_us > 500000) { // time going backwards time_now_us = new_time_us; } } else { // update SITL time with webots time. time_now_us = new_time_us; } time_advance(); // update magnetic field update_mag_field_bf(); output(input); report_FPS(); } } /* report frame rates */ void Webots::report_FPS(void) { if (frame_counter++ % 1000 == 0) { if (!is_zero(last_frame_count_s)) { uint64_t frames = socket_frame_counter - last_socket_frame_counter; last_socket_frame_counter = socket_frame_counter; double dt = state.timestamp - last_frame_count_s; printf("%.2f/%.2f FPS avg=%.2f\n", frames / dt, 1000 / dt, 1.0/average_frame_time_s); } else { printf("Initial position %f %f %f\n", position.x, position.y, position.z); } last_frame_count_s = state.timestamp; } }