diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index c64599ce6..bac3899bb 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -246,6 +246,7 @@ void PowerFSM_setup() { bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0); bool isTrackerOrSensor = config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || + config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER || config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR; bool hasPower = isPowered(); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 891b7a61f..add1b1296 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -325,6 +325,17 @@ void NodeDB::installRoleDefaults(meshtastic_Config_DeviceConfig_Role role) (meshtastic_Config_PositionConfig_PositionFlags_ALTITUDE | meshtastic_Config_PositionConfig_PositionFlags_SPEED | meshtastic_Config_PositionConfig_PositionFlags_HEADING | meshtastic_Config_PositionConfig_PositionFlags_DOP); moduleConfig.telemetry.device_update_interval = ONE_DAY; + } else if (role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) { + config.device.node_info_broadcast_secs = ONE_DAY; + config.position.position_broadcast_smart_enabled = true; + config.position.position_broadcast_secs = 3 * 60; // Every 3 minutes + config.position.broadcast_smart_minimum_distance = 20; + config.position.broadcast_smart_minimum_interval_secs = 15; + // Remove Altitude MSL from flags since CoTs use HAE (height above ellipsoid) + config.position.position_flags = + (meshtastic_Config_PositionConfig_PositionFlags_ALTITUDE | meshtastic_Config_PositionConfig_PositionFlags_SPEED | + meshtastic_Config_PositionConfig_PositionFlags_HEADING | meshtastic_Config_PositionConfig_PositionFlags_DOP); + moduleConfig.telemetry.device_update_interval = ONE_DAY; } else if (role == meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) { config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_LOCAL_ONLY; config.device.node_info_broadcast_secs = UINT32_MAX; diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 5e808b6b6..e82362bc6 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -8,9 +8,15 @@ #include "airtime.h" #include "configuration.h" #include "gps/GeoCoord.h" +#include "main.h" +#include "meshtastic/atak.pb.h" #include "sleep.h" #include "target_specific.h" +extern "C" { +#include "mesh/compression/unishox2.h" +} + PositionModule *positionModule; PositionModule::PositionModule() @@ -18,11 +24,14 @@ PositionModule::PositionModule() concurrency::OSThread("PositionModule") { isPromiscuous = true; // We always want to update our nodedb, even if we are sniffing on others - if (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER) + if (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER && + config.device.role != meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) setIntervalFromNow(60 * 1000); // Power saving trackers should clear their position on startup to avoid waking up and sending a stale position - if (config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER && config.power.is_power_saving) { + if ((config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || + config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) && + config.power.is_power_saving) { clearPosition(); } } @@ -157,9 +166,47 @@ meshtastic_MeshPacket *PositionModule::allocReply() LOG_INFO("Position reply: time=%i, latI=%i, lonI=-%i\n", p.time, p.latitude_i, p.longitude_i); + // TAK Tracker devices should send their position in a TAK packet over the ATAK port + if (config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) + return allocAtakPli(); + return allocDataProtobuf(p); } +meshtastic_MeshPacket *PositionModule::allocAtakPli() +{ + LOG_INFO("Sending TAK PLI packet\n"); + meshtastic_MeshPacket *mp = allocDataPacket(); + mp->decoded.portnum = meshtastic_PortNum_ATAK_PLUGIN; + + meshtastic_TAKPacket takPacket = {.is_compressed = true, + .has_contact = true, + .contact = {0}, + .has_group = true, + .group = {meshtastic_MemberRole_TeamMember, meshtastic_Team_Cyan}, + .has_status = true, + .status = + { + .battery = powerStatus->getBatteryChargePercent(), + }, + .which_payload_variant = meshtastic_TAKPacket_pli_tag, + {.pli = { + .latitude_i = localPosition.latitude_i, + .longitude_i = localPosition.longitude_i, + .altitude = localPosition.altitude_hae > 0 ? localPosition.altitude_hae : 0, + .speed = localPosition.ground_speed, + .course = static_cast(localPosition.ground_track), + }}}; + + auto length = unishox2_compress_simple(owner.long_name, strlen(owner.long_name), takPacket.contact.device_callsign); + LOG_DEBUG("Uncompressed device_callsign '%s' - %d bytes\n", owner.long_name, strlen(owner.long_name)); + LOG_DEBUG("Compressed device_callsign '%s' - %d bytes\n", takPacket.contact.device_callsign, length); + length = unishox2_compress_simple(owner.long_name, strlen(owner.long_name), takPacket.contact.callsign); + mp->decoded.payload.size = + pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), &meshtastic_TAKPacket_msg, &takPacket); + return mp; +} + void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t channel) { // cancel any not yet sent (now stale) position packets @@ -174,7 +221,8 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha p->to = dest; p->decoded.want_response = config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER ? false : wantReplies; - if (config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER) + if (config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || + config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) p->priority = meshtastic_MeshPacket_Priority_RELIABLE; else p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; @@ -185,7 +233,9 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha service.sendToMesh(p, RX_SRC_LOCAL, true); - if (config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER && config.power.is_power_saving) { + if ((config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || + config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) && + config.power.is_power_saving) { LOG_DEBUG("Starting next execution in 5 seconds and then going to sleep.\n"); sleepOnNextExecution = true; setIntervalFromNow(5000); @@ -212,7 +262,8 @@ int32_t PositionModule::runOnce() uint32_t intervalMs = getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs); uint32_t msSinceLastSend = now - lastGpsSend; // Only send packets if the channel util. is less than 25% utilized or we're a tracker with less than 40% utilized. - if (!airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER)) { + if (!airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER && + config.device.role != meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)) { return RUNONCE_INTERVAL; } diff --git a/src/modules/PositionModule.h b/src/modules/PositionModule.h index 983fcdf8f..bd7a9def4 100644 --- a/src/modules/PositionModule.h +++ b/src/modules/PositionModule.h @@ -49,6 +49,7 @@ class PositionModule : public ProtobufModule, private concu private: struct SmartPosition getDistanceTraveledSinceLastSend(meshtastic_PositionLite currentPosition); + meshtastic_MeshPacket *allocAtakPli(); /** Only used in power saving trackers for now */ void clearPosition(); diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index c1d3e93fb..63b014c46 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -185,6 +185,7 @@ void cpuDeepSleep(uint32_t msecToWake) // Don't enter this if we're sleeping portMAX_DELAY, since that's a shutdown event if (msecToWake != portMAX_DELAY && (config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || + config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER || config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR) && config.power.is_power_saving == true) { sd_power_mode_set(NRF_POWER_MODE_LOWPWR);