From 9748cb1e3ed689b815270297060e02a61f6725b6 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Sun, 22 Mar 2015 01:27:25 +1100 Subject: [PATCH] HAL_SITL: use a synthetic clock when possible this decouples wall clock time from simulation time if the FDM supports it --- .../AP_HAL_AVR_SITL/AP_HAL_AVR_SITL_Main.h | 1 - libraries/AP_HAL_AVR_SITL/SITL_State.cpp | 269 +++++++++--------- libraries/AP_HAL_AVR_SITL/SITL_State.h | 121 ++++---- libraries/AP_HAL_AVR_SITL/Scheduler.cpp | 62 +++- libraries/AP_HAL_AVR_SITL/Scheduler.h | 5 +- libraries/AP_HAL_AVR_SITL/sitl_gps.cpp | 3 - 6 files changed, 253 insertions(+), 208 deletions(-) diff --git a/libraries/AP_HAL_AVR_SITL/AP_HAL_AVR_SITL_Main.h b/libraries/AP_HAL_AVR_SITL/AP_HAL_AVR_SITL_Main.h index 2a2495bcdf..8f517e3fad 100644 --- a/libraries/AP_HAL_AVR_SITL/AP_HAL_AVR_SITL_Main.h +++ b/libraries/AP_HAL_AVR_SITL/AP_HAL_AVR_SITL_Main.h @@ -10,7 +10,6 @@ hal.scheduler->system_initialized(); \ for(;;) { \ loop(); \ - AVR_SITL::SITL_State::loop_hook(); \ } \ return 0;\ }\ diff --git a/libraries/AP_HAL_AVR_SITL/SITL_State.cpp b/libraries/AP_HAL_AVR_SITL/SITL_State.cpp index 43e58db1b7..c7b18623ca 100644 --- a/libraries/AP_HAL_AVR_SITL/SITL_State.cpp +++ b/libraries/AP_HAL_AVR_SITL/SITL_State.cpp @@ -20,6 +20,9 @@ #include #include +#include + +typedef void *(*pthread_startroutine_t)(void *); #ifdef __CYGWIN__ #include @@ -49,37 +52,11 @@ extern const AP_HAL::HAL& hal; using namespace AVR_SITL; -enum SITL_State::vehicle_type SITL_State::_vehicle; -uint16_t SITL_State::_framerate; -uint16_t SITL_State::_base_port = 5760; -uint16_t SITL_State::_rcout_port = 5502; -uint16_t SITL_State::_simin_port = 5501; -struct sockaddr_in SITL_State::_rcout_addr; -pid_t SITL_State::_parent_pid; -uint32_t SITL_State::_update_count; -bool SITL_State::_motors_on; -uint16_t SITL_State::sonar_pin_value; -uint16_t SITL_State::airspeed_pin_value; -uint16_t SITL_State::voltage_pin_value; -uint16_t SITL_State::current_pin_value; -float SITL_State::_current; - -AP_Baro *SITL_State::_barometer; -AP_InertialSensor *SITL_State::_ins; -SITLScheduler *SITL_State::_scheduler; -Compass *SITL_State::_compass; -OpticalFlow *SITL_State::_optical_flow; -AP_Terrain *SITL_State::_terrain; - -int SITL_State::_sitl_fd; -SITL *SITL_State::_sitl; -uint16_t SITL_State::pwm_output[11]; -uint16_t SITL_State::last_pwm_output[11]; -uint16_t SITL_State::pwm_input[8]; -bool SITL_State::new_rc_input; +// this allows loop_hook to be called +SITL_State *g_state; // catch floating point exceptions -void SITL_State::_sig_fpe(int signum) +static void _sig_fpe(int signum) { fprintf(stderr, "ERROR: Floating point exception - aborting\n"); abort(); @@ -106,7 +83,12 @@ void SITL_State::_parse_command_line(int argc, char * const argv[]) setvbuf(stdout, (char *)0, _IONBF, 0); setvbuf(stderr, (char *)0, _IONBF, 0); - while ((opt = getopt(argc, argv, "swhr:H:CI:P:")) != -1) { + _synthetic_clock_mode = false; + _base_port = 5760; + _rcout_port = 5502; + _simin_port = 5501; + + while ((opt = getopt(argc, argv, "swhr:H:CI:P:S")) != -1) { switch (opt) { case 'w': AP_Param::erase_all(); @@ -131,6 +113,9 @@ void SITL_State::_parse_command_line(int argc, char * const argv[]) case 'P': _set_param_default(optarg); break; + case 'S': + _synthetic_clock_mode = true; + break; default: _usage(); exit(1); @@ -205,7 +190,6 @@ void SITL_State::_sitl_setup(void) _rcout_addr.sin_port = htons(_rcout_port); inet_pton(AF_INET, "127.0.0.1", &_rcout_addr.sin_addr); - _setup_timer(); #ifndef HIL_MODE _setup_fdm(); #endif @@ -228,6 +212,28 @@ void SITL_State::_sitl_setup(void) _update_gps(0, 0, 0, 0, 0, 0, false); #endif } + + if (_synthetic_clock_mode) { + // start with non-zero clock + hal.scheduler->stop_clock(100); + } + + // setup a pipe used to trigger loop to stop sleeping + pipe(_fdm_pipe); + AVR_SITL::SITLUARTDriver::_set_nonblocking(_fdm_pipe[0]); + AVR_SITL::SITLUARTDriver::_set_nonblocking(_fdm_pipe[1]); + + g_state = this; + + /* + setup thread that receives input from the FDM + */ + pthread_attr_t thread_attr; + pthread_attr_init(&thread_attr); + + pthread_create(&_fdm_thread_ctx, &thread_attr, + (pthread_startroutine_t)&AVR_SITL::SITL_State::_fdm_thread, this); + } @@ -270,96 +276,89 @@ void SITL_State::_setup_fdm(void) /* - timer called at 1kHz + thread for FDM input */ -void SITL_State::_timer_handler(int signum) +void SITL_State::_fdm_thread(void) { - static uint32_t last_update_count; - static uint32_t last_pwm_input; + uint32_t last_update_count = 0; + uint32_t last_pwm_input = 0; - static bool in_timer; + while (true) { + fd_set fds; + struct timeval tv; - if (in_timer || _scheduler->interrupts_are_blocked() || _sitl == NULL){ - return; - } + if (next_stop_clock != 0) { + hal.scheduler->stop_clock(next_stop_clock); + next_stop_clock = 0; + } - _scheduler->sitl_begin_atomic(); - in_timer = true; + tv.tv_sec = 1; + tv.tv_usec = 0; -#ifndef __CYGWIN__ - /* make sure we die if our parent dies */ - if (kill(_parent_pid, 0) != 0) { - exit(1); - } -#else - - static uint16_t count = 0; - static uint32_t last_report; - - count++; - if (hal.scheduler->millis() - last_report > 1000) { - fprintf(stdout, "TH %u cps\n", count); - // print_trace(); - count = 0; - last_report = hal.scheduler->millis(); - } -#endif + FD_ZERO(&fds); + FD_SET(_sitl_fd, &fds); + if (select(_sitl_fd+1, &fds, NULL, NULL, &tv) != 1) { + // internal error + _simulator_output(true); + continue; + } - // simulate RC input at 50Hz - if (hal.scheduler->millis() - last_pwm_input >= 20 && _sitl->rc_fail == 0) { - last_pwm_input = hal.scheduler->millis(); - new_rc_input = true; - } + /* check for packet from flight sim */ + _fdm_input(); -#ifndef HIL_MODE - /* check for packet from flight sim */ - _fdm_input(); + /* make sure we die if our parent dies */ + if (kill(_parent_pid, 0) != 0) { + exit(1); + } - // send RC output to flight sim - _simulator_output(); -#endif + if (_scheduler->interrupts_are_blocked() || _sitl == NULL) { + continue; + } - if (_update_count == 0 && _sitl != NULL) { -#ifndef HIL_MODE - _update_gps(0, 0, 0, 0, 0, 0, false); - _update_barometer(0); -#endif - _scheduler->timer_event(); + // simulate RC input at 50Hz + if (hal.scheduler->millis() - last_pwm_input >= 20 && _sitl->rc_fail == 0) { + last_pwm_input = hal.scheduler->millis(); + new_rc_input = true; + } + + _scheduler->sitl_begin_atomic(); + + if (_update_count == 0 && _sitl != NULL) { + _update_gps(0, 0, 0, 0, 0, 0, false); + _update_barometer(0); + _scheduler->timer_event(); + _scheduler->sitl_end_atomic(); + continue; + } + + if (_update_count == last_update_count) { + _scheduler->timer_event(); + _scheduler->sitl_end_atomic(); + continue; + } + last_update_count = _update_count; + + if (_sitl != NULL) { + _update_gps(_sitl->state.latitude, _sitl->state.longitude, + _sitl->state.altitude, + _sitl->state.speedN, _sitl->state.speedE, _sitl->state.speedD, + !_sitl->gps_disable); + _update_ins(_sitl->state.rollDeg, _sitl->state.pitchDeg, _sitl->state.yawDeg, + _sitl->state.rollRate, _sitl->state.pitchRate, _sitl->state.yawRate, + _sitl->state.xAccel, _sitl->state.yAccel, _sitl->state.zAccel, + _sitl->state.airspeed, _sitl->state.altitude); + _update_barometer(_sitl->state.altitude); + _update_compass(_sitl->state.rollDeg, _sitl->state.pitchDeg, _sitl->state.yawDeg); + _update_flow(); + } + + // trigger all APM timers. + _scheduler->timer_event(); _scheduler->sitl_end_atomic(); - in_timer = false; - return; - } - if (_update_count == last_update_count) { - _scheduler->timer_event(); - _scheduler->sitl_end_atomic(); - in_timer = false; - return; - } - last_update_count = _update_count; - - if (_sitl != NULL) { -#ifndef HIL_MODE - _update_gps(_sitl->state.latitude, _sitl->state.longitude, - _sitl->state.altitude, - _sitl->state.speedN, _sitl->state.speedE, _sitl->state.speedD, - !_sitl->gps_disable); - _update_ins(_sitl->state.rollDeg, _sitl->state.pitchDeg, _sitl->state.yawDeg, - _sitl->state.rollRate, _sitl->state.pitchRate, _sitl->state.yawRate, - _sitl->state.xAccel, _sitl->state.yAccel, _sitl->state.zAccel, - _sitl->state.airspeed, _sitl->state.altitude); - _update_barometer(_sitl->state.altitude); - _update_compass(_sitl->state.rollDeg, _sitl->state.pitchDeg, _sitl->state.yawDeg); - _update_flow(); -#endif + char b = 0; + write(_fdm_pipe[1], &b, 1); } - - // trigger all APM timers. We do this last as it can re-enable - // interrupts, which can lead to recursion - _scheduler->timer_event(); - - _scheduler->sitl_end_atomic(); - in_timer = false; } #ifndef HIL_MODE @@ -373,12 +372,25 @@ void SITL_State::_fdm_input(void) uint16_t pwm[8]; }; union { + struct { + uint64_t timestamp; + struct sitl_fdm fg_pkt; + } fg_pkt_timestamped; struct sitl_fdm fg_pkt; struct pwm_packet pwm_pkt; } d; + bool got_fg_input = false; + next_stop_clock = 0; size = recv(_sitl_fd, &d, sizeof(d), MSG_DONTWAIT); switch (size) { + case 148: + /* a fg packate with a timestamp */ + next_stop_clock = d.fg_pkt_timestamped.timestamp; + memmove(&d.fg_pkt, &d.fg_pkt_timestamped.fg_pkt, sizeof(d.fg_pkt)); + _synthetic_clock_mode = true; + // fall through + case 140: static uint32_t last_report; static uint32_t count; @@ -388,6 +400,8 @@ void SITL_State::_fdm_input(void) return; } + got_fg_input = true; + if (d.fg_pkt.latitude == 0 || d.fg_pkt.longitude == 0 || d.fg_pkt.altitude <= 0) { @@ -428,6 +442,10 @@ void SITL_State::_fdm_input(void) } } + if (got_fg_input) { + // send RC output to flight sim + _simulator_output(_synthetic_clock_mode); + } } #endif @@ -460,7 +478,7 @@ void SITL_State::_apply_servo_filter(float deltat) /* send RC outputs to simulator */ -void SITL_State::_simulator_output(void) +void SITL_State::_simulator_output(bool synthetic_clock_mode) { static uint32_t last_update_usec; struct { @@ -495,7 +513,7 @@ void SITL_State::_simulator_output(void) // output at chosen framerate uint32_t now = hal.scheduler->micros(); - if (last_update_usec != 0 && now - last_update_usec < 1000000/_framerate) { + if (!synthetic_clock_mode && last_update_usec != 0 && now - last_update_usec < 1000000/_framerate) { return; } float deltat = (now - last_update_usec) * 1.0e-6f; @@ -577,28 +595,6 @@ void SITL_State::_simulator_output(void) sendto(_sitl_fd, (void*)&control, sizeof(control), MSG_DONTWAIT, (const sockaddr *)&_rcout_addr, sizeof(_rcout_addr)); } - -/* - setup a timer used to prod the ISRs - */ -void SITL_State::_setup_timer(void) -{ - struct itimerval it; - struct sigaction act; - - act.sa_handler = _timer_handler; - act.sa_flags = SA_RESTART|SA_NODEFER; - sigemptyset(&act.sa_mask); - sigaddset(&act.sa_mask, SIGALRM); - sigaction(SIGALRM, &act, NULL); - - it.it_interval.tv_sec = 0; - it.it_interval.tv_usec = 1000; // 1KHz - it.it_value = it.it_interval; - - setitimer(ITIMER_REAL, &it, NULL); -} - // generate a random float between -1 and 1 float SITL_State::_rand_float(void) { @@ -661,11 +657,20 @@ void SITL_State::loop_hook(void) FD_SET(fd, &fds); max_fd = max(fd, max_fd); } + + FD_SET(_fdm_pipe[0], &fds); + max_fd = max(_fdm_pipe[0], max_fd); + tv.tv_sec = 0; tv.tv_usec = 100; fflush(stdout); fflush(stderr); select(max_fd+1, &fds, NULL, NULL, &tv); + + if (FD_ISSET(_fdm_pipe[0], &fds)) { + char b; + read(_fdm_pipe[0], &b, 1); + } } diff --git a/libraries/AP_HAL_AVR_SITL/SITL_State.h b/libraries/AP_HAL_AVR_SITL/SITL_State.h index 816c4ca2c7..1885fdf6a7 100644 --- a/libraries/AP_HAL_AVR_SITL/SITL_State.h +++ b/libraries/AP_HAL_AVR_SITL/SITL_State.h @@ -39,18 +39,18 @@ public: int gps_pipe(void); int gps2_pipe(void); ssize_t gps_read(int fd, void *buf, size_t count); - static uint16_t pwm_output[11]; - static uint16_t last_pwm_output[11]; - static uint16_t pwm_input[8]; - static bool new_rc_input; - static void loop_hook(void); + uint16_t pwm_output[11]; + uint16_t last_pwm_output[11]; + uint16_t pwm_input[8]; + bool new_rc_input; + void loop_hook(void); uint16_t base_port(void) const { return _base_port; } // simulated airspeed, sonar and battery monitor - static uint16_t sonar_pin_value; // pin 0 - static uint16_t airspeed_pin_value; // pin 1 - static uint16_t voltage_pin_value; // pin 13 - static uint16_t current_pin_value; // pin 12 + uint16_t sonar_pin_value; // pin 0 + uint16_t airspeed_pin_value; // pin 1 + uint16_t voltage_pin_value; // pin 13 + uint16_t current_pin_value; // pin 12 private: void _parse_command_line(int argc, char * const argv[]); @@ -61,12 +61,10 @@ private: void _setup_timer(void); void _setup_adc(void); - // these methods are static as they are called - // from the timer - static float height_agl(void); - static void _update_barometer(float height); - static void _update_compass(float rollDeg, float pitchDeg, float yawDeg); - static void _update_flow(void); + float height_agl(void); + void _update_barometer(float height); + void _update_compass(float rollDeg, float pitchDeg, float yawDeg); + void _update_flow(void); struct gps_data { double latitude; @@ -79,64 +77,67 @@ private: }; #define MAX_GPS_DELAY 100 - static gps_data _gps_data[MAX_GPS_DELAY]; + gps_data _gps_data[MAX_GPS_DELAY]; - static bool _gps_has_basestation_position; - static gps_data _gps_basestation_data; - static void _gps_write(const uint8_t *p, uint16_t size); - static void _gps_send_ubx(uint8_t msgid, uint8_t *buf, uint16_t size); - static void _update_gps_ubx(const struct gps_data *d); - static void _update_gps_mtk(const struct gps_data *d); - static void _update_gps_mtk16(const struct gps_data *d); - static void _update_gps_mtk19(const struct gps_data *d); - static uint16_t _gps_nmea_checksum(const char *s); - static void _gps_nmea_printf(const char *fmt, ...); - static void _update_gps_nmea(const struct gps_data *d); - static void _sbp_send_message(uint16_t msg_type, uint16_t sender_id, uint8_t len, uint8_t *payload); - static void _update_gps_sbp(const struct gps_data *d, bool sim_rtk); + bool _gps_has_basestation_position; + gps_data _gps_basestation_data; + void _gps_write(const uint8_t *p, uint16_t size); + void _gps_send_ubx(uint8_t msgid, uint8_t *buf, uint16_t size); + void _update_gps_ubx(const struct gps_data *d); + void _update_gps_mtk(const struct gps_data *d); + void _update_gps_mtk16(const struct gps_data *d); + void _update_gps_mtk19(const struct gps_data *d); + uint16_t _gps_nmea_checksum(const char *s); + void _gps_nmea_printf(const char *fmt, ...); + void _update_gps_nmea(const struct gps_data *d); + void _sbp_send_message(uint16_t msg_type, uint16_t sender_id, uint8_t len, uint8_t *payload); + void _update_gps_sbp(const struct gps_data *d, bool sim_rtk); - static void _update_gps(double latitude, double longitude, float altitude, + void _update_gps(double latitude, double longitude, float altitude, double speedN, double speedE, double speedD, bool have_lock); - static void _update_ins(float roll, float pitch, float yaw, // Relative to earth + void _update_ins(float roll, float pitch, float yaw, // Relative to earth double rollRate, double pitchRate,double yawRate, // Local to plane double xAccel, double yAccel, double zAccel, // Local to plane float airspeed, float altitude); - static void _fdm_input(void); - static void _simulator_output(void); - static void _apply_servo_filter(float deltat); - static uint16_t _airspeed_sensor(float airspeed); - static uint16_t _ground_sonar(); - static float _gyro_drift(void); - static float _rand_float(void); - static Vector3f _rand_vec3f(void); + void _fdm_input(void); + void _simulator_output(bool synthetic_clock_mode); + void _apply_servo_filter(float deltat); + uint16_t _airspeed_sensor(float airspeed); + uint16_t _ground_sonar(); + float _gyro_drift(void); + float _rand_float(void); + Vector3f _rand_vec3f(void); - // signal handlers - static void _sig_fpe(int signum); - static void _timer_handler(int signum); + pthread_t _fdm_thread_ctx; + void _fdm_thread(void); + int _fdm_pipe[2]; + uint64_t next_stop_clock; // internal state - static enum vehicle_type _vehicle; - static uint16_t _framerate; - static uint16_t _base_port; + enum vehicle_type _vehicle; + uint16_t _framerate; + uint16_t _base_port; float _initial_height; - static struct sockaddr_in _rcout_addr; - static pid_t _parent_pid; - static uint32_t _update_count; - static bool _motors_on; + struct sockaddr_in _rcout_addr; + pid_t _parent_pid; + uint32_t _update_count; + bool _motors_on; - static AP_Baro *_barometer; - static AP_InertialSensor *_ins; - static SITLScheduler *_scheduler; - static Compass *_compass; - static OpticalFlow *_optical_flow; - static AP_Terrain *_terrain; + AP_Baro *_barometer; + AP_InertialSensor *_ins; + SITLScheduler *_scheduler; + Compass *_compass; + OpticalFlow *_optical_flow; + AP_Terrain *_terrain; - static int _sitl_fd; - static SITL *_sitl; - static uint16_t _rcout_port; - static uint16_t _simin_port; - static float _current; + int _sitl_fd; + SITL *_sitl; + uint16_t _rcout_port; + uint16_t _simin_port; + float _current; + + bool _synthetic_clock_mode; }; #endif // CONFIG_HAL_BOARD == HAL_BOARD_AVR_SITL diff --git a/libraries/AP_HAL_AVR_SITL/Scheduler.cpp b/libraries/AP_HAL_AVR_SITL/Scheduler.cpp index 762e28d1cf..0ca273d1e6 100644 --- a/libraries/AP_HAL_AVR_SITL/Scheduler.cpp +++ b/libraries/AP_HAL_AVR_SITL/Scheduler.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #ifdef __CYGWIN__ #include @@ -44,9 +46,16 @@ double SITLScheduler::_cyg_freq = 0; long SITLScheduler::_cyg_start = 0; #endif +static void sigcont_handler(int) +{ +} + SITLScheduler::SITLScheduler(SITL_State *sitlState) : - _sitlState(sitlState) -{} + _sitlState(sitlState), + stopped_clock_usec(0) +{ + signal(SIGCONT, sigcont_handler); +} void SITLScheduler::init(void *unused) { @@ -88,6 +97,9 @@ uint64_t SITLScheduler::_micros64() uint64_t SITLScheduler::micros64() { + if (stopped_clock_usec) { + return stopped_clock_usec; + } return _micros64(); } @@ -98,6 +110,9 @@ uint32_t SITLScheduler::micros() uint64_t SITLScheduler::millis64() { + if (stopped_clock_usec) { + return stopped_clock_usec/1000; + } #ifdef __CYGWIN__ // 1000 ms in a second return (uint64_t)(_cyg_sec() * 1000); @@ -116,24 +131,29 @@ uint32_t SITLScheduler::millis() return millis64() & 0xFFFFFFFF; } +extern AVR_SITL::SITL_State *g_state; + + void SITLScheduler::delay_microseconds(uint16_t usec) { uint64_t start = micros64(); - while (micros64() - start < usec) { - usleep(usec - (micros64() - start)); + uint64_t dtime; + while ((dtime=(micros64() - start) < usec)) { + if (stopped_clock_usec) { + wait_time_usec = start + usec; + pause(); + wait_time_usec = 0; + } else { + usleep(usec - dtime); + } } } void SITLScheduler::delay(uint16_t ms) { - uint64_t start = micros64(); - while (ms > 0) { - while ((micros64() - start) >= 1000) { - ms--; - if (ms == 0) break; - start += 1000; - } + delay_microseconds(1000); + ms--; if (_min_delay_cb_ms <= ms) { if (_delay_cb) { _delay_cb(); @@ -294,4 +314,24 @@ void SITLScheduler::panic(const prog_char_t *errormsg) { for(;;); } +/* + set simulation timestamp + */ +void SITLScheduler::stop_clock(uint64_t time_usec) +{ + stopped_clock_usec = time_usec; + /* + we want to ensure the main thread + */ + while (wait_time_usec == 0) { + pthread_yield(); + } + kill(0, SIGCONT); + while (wait_time_usec != 0 && wait_time_usec <= time_usec) { + kill(0, SIGCONT); + pthread_yield(); + } + _run_io_procs(false); +} + #endif diff --git a/libraries/AP_HAL_AVR_SITL/Scheduler.h b/libraries/AP_HAL_AVR_SITL/Scheduler.h index 0894f7c992..6c40565393 100644 --- a/libraries/AP_HAL_AVR_SITL/Scheduler.h +++ b/libraries/AP_HAL_AVR_SITL/Scheduler.h @@ -73,8 +73,11 @@ private: static double _cyg_sec(); #endif - bool _initialized; + void stop_clock(uint64_t time_usec); + bool _initialized; + volatile uint64_t stopped_clock_usec; + volatile uint64_t wait_time_usec; }; #endif #endif // __AP_HAL_SITL_SCHEDULER_H__ diff --git a/libraries/AP_HAL_AVR_SITL/sitl_gps.cpp b/libraries/AP_HAL_AVR_SITL/sitl_gps.cpp index 6b6de9b790..f23db10ec0 100644 --- a/libraries/AP_HAL_AVR_SITL/sitl_gps.cpp +++ b/libraries/AP_HAL_AVR_SITL/sitl_gps.cpp @@ -32,9 +32,6 @@ extern const AP_HAL::HAL& hal; static uint8_t next_gps_index; static uint8_t gps_delay; -SITL_State::gps_data SITL_State::_gps_data[MAX_GPS_DELAY]; -bool SITL_State::_gps_has_basestation_position = false; -SITL_State::gps_data SITL_State::_gps_basestation_data; // state of GPS emulation static struct gps_state {