Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions include/app.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "isobus/isobus/isobus_speed_distance_messages.hpp"
#include "isobus/isobus/nmea2000_message_interface.hpp"

#include "gnss_receiver.hpp"
#include "settings.hpp"
#include "task_controller.hpp"
#include "udp_connections.hpp"
Expand All @@ -38,6 +39,7 @@ class Application
std::shared_ptr<isobus::InternalControlFunction> tecuCF = nullptr;
std::unique_ptr<isobus::SpeedMessagesInterface> speedMessagesInterface;
std::unique_ptr<isobus::NMEA2000MessageInterface> nmea2000MessageInterface;
std::unique_ptr<GnssReceiver> gnssReceiver;
std::uint8_t nmea2000SequenceIdentifier = 0;
std::uint32_t lastJ1939SpeedTransmit = 0;
std::int32_t lastSpeedValue = 0;
Expand Down
108 changes: 108 additions & 0 deletions include/gnss_receiver.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* @author Qoder
* @brief GNSS receiver that parses J1939 PGNs from a John Deere SF3000 and generates $PANDA NMEA sentences
* @version 0.2
* @date 2025-03-14
*/

#pragma once

#include <cstdint>
#include <fstream>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>

#include "isobus/hardware_integration/can_hardware_interface.hpp"
#include "isobus/isobus/can_message_frame.hpp"
#include "isobus/utility/event_dispatcher.hpp"

#include "udp_connections.hpp"

class GnssReceiver
{
public:
GnssReceiver();
~GnssReceiver();

/// @brief Register the CAN frame listener. Must be called after CANHardwareInterface::start().
/// @param reverseEngineerMode If true, log all SF3000 frames to CSV for offline analysis
void initialize(bool reverseEngineerMode = false);

/// @brief Build and send a $PANDA sentence if position data is available. Throttled to 10 Hz.
/// @param udp The UDP connections to send the sentence on
void send_panda_if_ready(std::shared_ptr<UdpConnections> udp);

private:
struct GnssData
{
double latitude_deg = 0.0;
double longitude_deg = 0.0;
double altitude_m = 0.0;
double heading_deg = 0.0;
double speed_kmh = 0.0;

std::uint8_t hour = 0;
std::uint8_t minute = 0;
std::uint16_t minute_ms = 0;

bool has_position = false;
bool has_time = false;
bool has_altitude = false;
bool has_heading = false;
bool has_speed = false;
};

/// @brief Per-PGN tracking for reverse engineering
struct PgnTracker
{
std::uint32_t count = 0;
std::uint8_t lastPayload[8] = {};
std::uint8_t lastLength = 0;
std::uint32_t firstSeenMs = 0;
std::uint32_t lastSeenMs = 0;
};

void on_can_frame(const isobus::CANMessageFrame &frame);

// Standard J1939 parsers (kept for buses that broadcast them)
void parse_position_standard(const isobus::CANMessageFrame &frame);
void parse_time_date_standard(const isobus::CANMessageFrame &frame);
void parse_altitude_standard(const isobus::CANMessageFrame &frame);
void parse_speed_standard(const isobus::CANMessageFrame &frame);

// Confirmed/candidate parsers for proprietary Deere PGNs
void parse_heading_FE48(const isobus::CANMessageFrame &frame);
void parse_candidate_FE45(const isobus::CANMessageFrame &frame);
void parse_candidate_FE43(const isobus::CANMessageFrame &frame);
void parse_candidate_FE12(const isobus::CANMessageFrame &frame);
void parse_candidate_FE13(const isobus::CANMessageFrame &frame);
void parse_candidate_FFFA(const isobus::CANMessageFrame &frame);
void parse_candidate_FFFB(const isobus::CANMessageFrame &frame);
void parse_candidate_ACFF(const isobus::CANMessageFrame &frame);
void parse_candidate_FE0A(const isobus::CANMessageFrame &frame);
void parse_candidate_F022(const isobus::CANMessageFrame &frame);
void parse_candidate_FFFF(const isobus::CANMessageFrame &frame);

void log_frame_raw(std::uint32_t pgn, std::uint8_t sa, const isobus::CANMessageFrame &frame);
void log_frame_csv(std::uint32_t pgn, std::uint8_t sa, const isobus::CANMessageFrame &frame);

std::string build_gga(const GnssData &snapshot) const;

static constexpr std::uint8_t SF3000_SOURCE_ADDRESS = 0x9A;

GnssData data;
std::mutex dataMutex;
isobus::EventCallbackHandle canFrameHandle = 0;
std::uint32_t lastPandaSend = 0;
std::uint32_t lastPandaLog = 0;
std::uint32_t lastWaitingLog = 0;
std::uint32_t lastPgnSummaryLog = 0;
bool pandaSending = false;
bool reMode = false;

std::unordered_map<std::uint32_t, PgnTracker> pgnTrackers;
std::mutex trackerMutex;
std::ofstream csvFile;
};
8 changes: 8 additions & 0 deletions include/udp_connections.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include <boost/asio.hpp>
#include <span>
#include <string_view>
#include "settings.hpp"

using boost::asio::ip::udp;
Expand Down Expand Up @@ -69,6 +70,13 @@ class UdpConnections
*/
bool send(std::uint8_t src, std::uint8_t pgn, std::span<std::uint8_t> data);

/**
* @brief Send raw text to AOG (no binary framing)
* @param text The raw text to send (e.g. an NMEA sentence)
* @return True if the text was sent successfully, false otherwise
*/
bool send_raw(std::string_view text);

private:
static const std::size_t MAX_PACKET_SIZE = 512; // Mostly arbitrary, but should be large enough to hold any packet
static const std::uint16_t PACKET_START = 0x8081; // Start of packet
Expand Down
6 changes: 6 additions & 0 deletions src/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ bool Application::initialize()
udpConnections->set_packet_handler(packetHandler);
udpConnections->open();

gnssReceiver = std::make_unique<GnssReceiver>();
gnssReceiver->initialize(true); // RE mode: log all SF3000 frames to CSV

std::cout << "UDP connections opened." << std::endl;

return true;
Expand All @@ -236,6 +239,9 @@ bool Application::update()
udpConnections->handle_address_detection();
udpConnections->handle_incoming_packets();

if (gnssReceiver)
gnssReceiver->send_panda_if_ready(udpConnections);

tcServer->request_measurement_commands();
tcServer->update();
if (speedMessagesInterface)
Expand Down
Loading
Loading