/* 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 received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include #include #include #include #if HAVE_FILESYSTEM_SUPPORT && defined(HAL_BOARD_TERRAIN_DIRECTORY) #define AP_TERRAIN_AVAILABLE 1 #else #define AP_TERRAIN_AVAILABLE 0 #endif #if AP_TERRAIN_AVAILABLE #include #include #define TERRAIN_DEBUG 0 // MAVLink sends 4x4 grids #define TERRAIN_GRID_MAVLINK_SIZE 4 // a 2k grid_block on disk contains 8x7 of the mavlink grids. Each // grid block overlaps by one with its neighbour. This ensures that // the altitude at any point can be calculated from a single grid // block #define TERRAIN_GRID_BLOCK_MUL_X 7 #define TERRAIN_GRID_BLOCK_MUL_Y 8 // this is the spacing between 32x28 grid blocks, in grid_spacing units #define TERRAIN_GRID_BLOCK_SPACING_X ((TERRAIN_GRID_BLOCK_MUL_X-1)*TERRAIN_GRID_MAVLINK_SIZE) #define TERRAIN_GRID_BLOCK_SPACING_Y ((TERRAIN_GRID_BLOCK_MUL_Y-1)*TERRAIN_GRID_MAVLINK_SIZE) // giving a total grid size of a disk grid_block of 32x28 #define TERRAIN_GRID_BLOCK_SIZE_X (TERRAIN_GRID_MAVLINK_SIZE*TERRAIN_GRID_BLOCK_MUL_X) #define TERRAIN_GRID_BLOCK_SIZE_Y (TERRAIN_GRID_MAVLINK_SIZE*TERRAIN_GRID_BLOCK_MUL_Y) // number of grid_blocks in the LRU memory cache #define TERRAIN_GRID_BLOCK_CACHE_SIZE 12 // format of grid on disk #define TERRAIN_GRID_FORMAT_VERSION 1 // we allow for a 2cm discrepancy in the grid corners. This is to // account for different rounding in terrain DAT file generators using // different programming languages #define TERRAIN_LATLON_EQUAL(v1, v2) (labs((v1)-(v2)) <= 2) #if TERRAIN_DEBUG #include #define ASSERT_RANGE(v,minv,maxv) assert((v)<=(maxv)&&(v)>=(minv)) #else #define ASSERT_RANGE(v,minv,maxv) #endif /* Data conventions in this library: array[x][y]: x is increasing north, y is increasing east array[x]: low order bits increase east first bitmap: low order bits increase east first file: first entries increase east, then north */ class AP_Terrain { public: AP_Terrain(const AP_Mission &_mission); /* Do not allow copies */ AP_Terrain(const AP_Terrain &other) = delete; AP_Terrain &operator=(const AP_Terrain&) = delete; static AP_Terrain *get_singleton(void) { return singleton; } enum TerrainStatus { TerrainStatusDisabled = 0, // not enabled TerrainStatusUnhealthy = 1, // no terrain data for current location TerrainStatusOK = 2 // terrain data available }; static const struct AP_Param::GroupInfo var_info[]; // update terrain state. Should be called at 1Hz or more void update(void); bool enabled() const { return enable; } // return status enum for health reporting enum TerrainStatus status(void) const { return system_status; } // send any pending terrain request message bool send_cache_request(mavlink_channel_t chan); void send_request(mavlink_channel_t chan); // handle terrain data and reports from GCS void send_terrain_report(mavlink_channel_t chan, const Location &loc, bool extrapolate); void handle_data(mavlink_channel_t chan, const mavlink_message_t &msg); void handle_terrain_check(mavlink_channel_t chan, const mavlink_message_t &msg); void handle_terrain_data(const mavlink_message_t &msg); /* find the terrain height in meters above sea level for a location return false if not available if corrected is true then terrain alt is adjusted so that the terrain altitude matches the home altitude at the home location (i.e. we assume home is at the terrain altitude) */ bool height_amsl(const Location &loc, float &height, bool corrected); /* find difference between home terrain height and the terrain height at the current location in meters. A positive result means the terrain is higher than home. return false is terrain at the current location or at home location is not available If extrapolate is true then allow return of an extrapolated terrain altitude based on the last available data */ bool height_terrain_difference_home(float &terrain_difference, bool extrapolate = false); /* return estimated equivalent relative-to-home altitude in meters of a given height above the terrain at the current location This function allows existing height controllers which work on barometric altitude (relative to home) to be used with terrain based target altitude, by translating the "above terrain" altitude into an equivalent barometric relative height. return false if terrain data is not available either at the given location or at the home location. If extrapolate is true then allow return of an extrapolated terrain altitude based on the last available data */ bool height_relative_home_equivalent(float terrain_altitude, float &relative_altitude, bool extrapolate = false); /* return current height above terrain at current AHRS position. If extrapolate is true then extrapolate from most recently available terrain data if terrain data is not available for the current location. Return true if height is available, otherwise false. */ bool height_above_terrain(float &terrain_altitude, bool extrapolate = false); /* calculate lookahead rise in terrain. This returns extra altitude needed to clear upcoming terrain in meters */ float lookahead(float bearing, float distance, float climb_ratio); /* log terrain status to AP_Logger */ void log_terrain_data(); /* get some statistics for TERRAIN_REPORT */ void get_statistics(uint16_t &pending, uint16_t &loaded) const; /* returns true if initialisation failed because out-of-memory */ bool init_failed() const { return memory_alloc_failed; } private: // allocate the terrain subsystem data bool allocate(void); /* a grid block is a structure in a local file containing height information. Each grid block is 2048 in size, to keep file IO to block oriented SD cards efficient */ struct PACKED grid_block { // bitmap of 4x4 grids filled in from GCS (56 bits are used) uint64_t bitmap; // south west corner of block in degrees*10^7 int32_t lat; int32_t lon; // crc of whole block, taken with crc=0 uint16_t crc; // format version number uint16_t version; // grid spacing in meters uint16_t spacing; // heights in meters over a 32*28 grid int16_t height[TERRAIN_GRID_BLOCK_SIZE_X][TERRAIN_GRID_BLOCK_SIZE_Y]; // indices info 32x28 grids for this degree reference uint16_t grid_idx_x; uint16_t grid_idx_y; // rounded latitude/longitude in degrees. int16_t lon_degrees; int8_t lat_degrees; }; /* grid_block for disk IO, aligned on 2048 byte boundaries */ union grid_io_block { struct grid_block block; uint8_t buffer[2048]; }; enum GridCacheState { GRID_CACHE_INVALID=0, // when first initialised GRID_CACHE_DISKWAIT=1, // when waiting for disk read GRID_CACHE_VALID=2, // when at least partially valid GRID_CACHE_DIRTY=3 // when updates have been made, and // disk write needed }; /* a grid_block plus some meta data used for requesting new blocks */ struct grid_cache { struct grid_block grid; volatile enum GridCacheState state; // the last time access was requested to this block, used for LRU uint32_t last_access_ms; }; /* grid_info is a broken down representation of a Location, giving the index terms for finding the right grid */ struct grid_info { // rounded latitude/longitude in degrees. int8_t lat_degrees; int16_t lon_degrees; // lat and lon of SW corner of this 32*28 grid, *10^7 degrees int32_t grid_lat; int32_t grid_lon; // indices info 32x28 grids for this degree reference uint16_t grid_idx_x; uint16_t grid_idx_y; // indexes into 32x28 grid uint8_t idx_x; // north (0..27) uint8_t idx_y; // east (0..31) // fraction within the grid square float frac_x; // north (0..1) float frac_y; // east (0..1) // file offset of this grid uint32_t file_offset; }; // given a location, fill a grid_info structure void calculate_grid_info(const Location &loc, struct grid_info &info) const; /* find a grid structure given a grid_info */ struct grid_cache &find_grid_cache(const struct grid_info &info); /* calculate bit number in grid_block bitmap. This corresponds to a bit representing a 4x4 mavlink transmitted block */ uint8_t grid_bitnum(uint8_t idx_x, uint8_t idx_y); /* given a grid_info check that a given idx_x/idx_y is available (set in the bitmap) */ bool check_bitmap(const struct grid_block &grid, uint8_t idx_x, uint8_t idx_y); /* request any missing 4x4 grids from a block */ bool request_missing(mavlink_channel_t chan, struct grid_cache &gcache); bool request_missing(mavlink_channel_t chan, const struct grid_info &info); /* look for blocks that need to be read/written to disk */ void schedule_disk_io(void); /* get some statistics for TERRAIN_REPORT */ uint8_t bitcount64(uint64_t b) const; /* disk IO functions */ int16_t find_io_idx(enum GridCacheState state); uint16_t get_block_crc(struct grid_block &block); void check_disk_read(void); void check_disk_write(void); void io_timer(void); void open_file(void); void seek_offset(void); uint32_t east_blocks(struct grid_block &block) const; void write_block(void); void read_block(void); /* check for missing mission terrain data */ void update_mission_data(void); /* check for missing rally data */ void update_rally_data(void); // parameters AP_Int8 enable; AP_Int16 grid_spacing; // meters between grid points AP_Int16 options; // option bits enum class Options { DisableDownload = (1U<<0), }; // reference to AP_Mission, so we can ask preload terrain data for // all waypoints const AP_Mission &mission; // cache of grids in memory, LRU uint8_t cache_size = 0; struct grid_cache *cache = nullptr; // a grid_cache block waiting for disk IO enum DiskIoState { DiskIoIdle = 0, DiskIoWaitWrite = 1, DiskIoWaitRead = 2, DiskIoDoneRead = 3, DiskIoDoneWrite = 4 }; volatile enum DiskIoState disk_io_state; union grid_io_block disk_block; // last time we asked for more grids uint32_t last_request_time_ms[MAVLINK_COMM_NUM_BUFFERS]; static const uint64_t bitmap_mask = (((uint64_t)1U)<<(TERRAIN_GRID_BLOCK_MUL_X*TERRAIN_GRID_BLOCK_MUL_Y)) - 1; // open file handle on degree file int fd; // has the timer been setup? bool timer_setup; // degrees lat and lon of file int8_t file_lat_degrees; int16_t file_lon_degrees; // do we have an IO failure volatile bool io_failure; uint32_t last_retry_ms; // have we created the terrain directory? bool directory_created; // cache the home altitude, as it is needed so often float home_height; Location home_loc; // cache the last terrain height (AMSL) of the AHRS current // location. This is used for extrapolation when terrain data is // temporarily unavailable bool have_current_loc_height; float last_current_loc_height; // next mission command to check uint16_t next_mission_index; // next mission position to check uint8_t next_mission_pos; // last time the mission changed uint32_t last_mission_change_ms; // grid spacing during mission check uint16_t last_mission_spacing; // next rally command to check uint16_t next_rally_index; // last time the rally points changed uint32_t last_rally_change_ms; // grid spacing during rally check uint16_t last_rally_spacing; char *file_path = nullptr; // status enum TerrainStatus system_status = TerrainStatusDisabled; // memory allocation status bool memory_alloc_failed; static AP_Terrain *singleton; }; namespace AP { AP_Terrain *terrain(); }; #endif // AP_TERRAIN_AVAILABLE