update comments on all ifdefs
[olsrd.git] / lib / pud / src / receiver.c
index 9b2a4f3..86295af 100644 (file)
@@ -2,36 +2,23 @@
 
 /* Plugin includes */
 #include "pud.h"
-#include "gpsConversion.h"
+#include "state.h"
 #include "configuration.h"
-#include "dump.h"
-#include "timers.h"
-#include "posAvg.h"
+#include "gpsConversion.h"
 #include "networkInterfaces.h"
-#include "compiler.h"
+#include "timers.h"
+#include "uplinkGateway.h"
+#include "posFile.h"
 
 /* OLSRD includes */
 #include "net_olsr.h"
 
 /* System includes */
-#include <stddef.h>
 #include <nmea/parser.h>
-#include <nmea/info.h>
-#include <pthread.h>
-#include <nmea/info.h>
-#include <string.h>
 #include <nmea/gmath.h>
 #include <nmea/sentence.h>
-#include <math.h>
-#include <net/if.h>
-#include <assert.h>
-
-/* Debug includes */
-#if defined(PUD_DUMP_GPS_PACKETS_TX_OLSR) || \
-       defined(PUD_DUMP_GPS_PACKETS_TX_UPLINK) || \
-       defined(PUD_DUMP_AVERAGING)
-#include "olsr.h"
-#endif
+#include <nmea/context.h>
+#include <OlsrdPudWireFormat/wireFormat.h>
 
 /*
  * NMEA parser
@@ -44,40 +31,6 @@ static nmeaPARSER nmeaParser;
  * State
  */
 
-/** Type describing a tri-state boolean */
-typedef enum _TristateBoolean {
-       UNKNOWN = 0,
-       UNSET = 1,
-       SET = 2
-} TristateBoolean;
-
-#define TristateBooleanToString(s)     ((s == SET) ? "set" : \
-                                                                        (s == UNSET) ? "unset" : \
-                                                                        "unknown")
-
-/** Type describing movement state */
-typedef enum _MovementState {
-       STATIONARY = 0,
-       MOVING = 1
-} MovementState;
-
-#define MovementStateToString(s)       ((s == MOVING) ? "moving" : \
-                                                                        "stationary")
-
-/** Type describing state */
-typedef struct _StateType {
-       MovementState internalState; /**< the internal movement state */
-       MovementState externalState; /**< the externally visible movement state */
-       unsigned long long hysteresisCounter; /**< the hysteresis counter external state changes */
-} StateType;
-
-/** The state */
-static StateType state = {
-               .internalState = MOVING,
-               .externalState = MOVING,
-               .hysteresisCounter = 0
-};
-
 /** Type describing movement calculations */
 typedef struct _MovementType {
        TristateBoolean moving; /**< SET: we are moving */
@@ -108,26 +61,13 @@ static PositionAverageList positionAverageList;
  */
 
 typedef enum _TimedTxInterface {
-       OLSR = 1,
-       UPLINK = 2
+       TX_INTERFACE_OLSR = 1,
+       TX_INTERFACE_UPLINK = 2
 } TimedTxInterface;
 
-/** Structure of the latest GPS information that is transmitted */
-typedef struct _TransmitGpsInformation {
-       pthread_mutex_t mutex; /**< access mutex */
-       bool updated; /**< true when the information was updated */
-       PositionUpdateEntry txPosition; /**< The last transmitted position */
-} TransmitGpsInformation;
-
 /** The latest position information that is transmitted */
 static TransmitGpsInformation transmitGpsInformation;
 
-/** The last transmitted position.
- * The same as transmitGpsInformation.txPosition.
- * We keep this because then we can access the information without locking
- * mutexes. */
-static PositionUpdateEntry txPosition;
-
 /** The size of the buffer in which the OLSR and uplink messages are assembled */
 #define TX_BUFFER_SIZE_FOR_OLSR 1024
 
@@ -136,37 +76,22 @@ static PositionUpdateEntry txPosition;
  */
 
 /**
- This function is called every time before a message is sent on a specific
- interface. It can manipulate the outgoing message.
- Note that a change to the outgoing messages is carried over to the message
- that goes out on the next interface when the message is _not_ reset
- before it is sent out on the next interface.
-
- @param olsrMessage
- A pointer to the outgoing message
- @param ifn
- A pointer to the OLSR interface structure
+ Clear the MovementType
+ * @param result a pointer to the MovementType
  */
-static void nodeIdPreTransmitHook(union olsr_message *olsrMessage,
-               struct interface *ifn) {
-       /* set the MAC address in the message when needed */
-       if (unlikely(getNodeIdTypeNumber() == PUD_NODEIDTYPE_MAC)) {
-               TOLSRNetworkInterface * olsrIf = getOlsrNetworkInterface(ifn);
-               PudOlsrPositionUpdate * olsrGpsMessage =
-                               getOlsrMessagePayload(olsr_cnf->ip_version, olsrMessage);
-
-               if (likely(olsrIf != NULL)) {
-                       setPositionUpdateNodeId(olsrGpsMessage, &olsrIf->hwAddress[0],
-                                       PUD_NODEIDTYPE_MAC_BYTES, false);
-               } else {
-                       unsigned char buffer[PUD_NODEIDTYPE_MAC_BYTES] = { 0 };
-                       setPositionUpdateNodeId(olsrGpsMessage, &buffer[0],
-                                       PUD_NODEIDTYPE_MAC_BYTES, false);
-
-                       pudError(false, "Could not find OLSR interface %s, cleared its"
-                               " MAC address in the OLSR message\n", ifn->int_name);
-               }
-       }
+static void clearMovementType(MovementType * result) {
+       /* clear outputs */
+       result->moving = TRISTATE_BOOLEAN_UNKNOWN;
+       result->overThresholds = TRISTATE_BOOLEAN_UNKNOWN;
+       result->speedOverThreshold = TRISTATE_BOOLEAN_UNKNOWN;
+       result->hDistanceOverThreshold = TRISTATE_BOOLEAN_UNKNOWN;
+       result->vDistanceOverThreshold = TRISTATE_BOOLEAN_UNKNOWN;
+       result->outside = TRISTATE_BOOLEAN_UNKNOWN;
+       result->outsideHdop = TRISTATE_BOOLEAN_UNKNOWN;
+       result->outsideVdop = TRISTATE_BOOLEAN_UNKNOWN;
+       result->inside = TRISTATE_BOOLEAN_UNKNOWN;
+       result->insideHdop = TRISTATE_BOOLEAN_UNKNOWN;
+       result->insideVdop = TRISTATE_BOOLEAN_UNKNOWN;
 }
 
 /**
@@ -180,169 +105,155 @@ static void nodeIdPreTransmitHook(union olsr_message *olsrMessage,
  - false otherwise
  */
 static bool positionValid(PositionUpdateEntry * position) {
-       return (nmea_INFO_has_field(position->nmeaInfo.smask, FIX)
+       return (nmea_INFO_is_present(position->nmeaInfo.present, FIX)
                        && (position->nmeaInfo.fix != NMEA_FIX_BAD));
 }
 
-/** Send the transmit buffer out over all designated interfaces, called as a
- * timer callback and also immediately on an external state change.
+/**
+ Send the transmit buffer out over all designated interfaces, called as a
+ timer callback and also immediately on an external state change.
 
  @param interfaces
  a bitmap defining which interfaces to send over
  */
 static void txToAllOlsrInterfaces(TimedTxInterface interfaces) {
-       /** buffer used to fill in the OLSR and uplink messages */
+       /** txBuffer is used to concatenate the position update and cluster leader messages in */
        unsigned char txBuffer[TX_BUFFER_SIZE_FOR_OLSR];
-       UplinkMessage * message1 = (UplinkMessage *) &txBuffer[0];
+       unsigned int txBufferBytesUsed = 0;
+       #define txBufferBytesFree       (sizeof(txBuffer) - txBufferBytesUsed)
 
-       unsigned int txBufferSpaceTaken = 0;
-       #define txBufferSpaceFree       (sizeof(txBuffer) - txBufferSpaceTaken)
+       /*
+        * The first message in txBuffer is an OLSR position update.
+        *
+        * The position update is always present.
+        *
+        * The second message is the cluster leader message, but only when uplink
+        * was requested and correctly configured.
+        */
+
+       UplinkMessage * pu_uplink = (UplinkMessage *) &txBuffer[0];
+       union olsr_message * pu = &pu_uplink->msg.olsrMessage;
+       unsigned int pu_size = 0;
+       union olsr_ip_addr gateway;
+       MovementState externalState;
+       nmeaINFO nmeaInfo;
+
+       externalState = getExternalState();
+
+       /* only fixup timestamp when the position is valid _and_ when the position was not updated */
+       if (positionValid(&transmitGpsInformation.txPosition) && !transmitGpsInformation.positionUpdated) {
+               nmea_time_now(&transmitGpsInformation.txPosition.nmeaInfo.utc, &transmitGpsInformation.txPosition.nmeaInfo.present);
+       }
 
-       /* the first message in the buffer is an OLSR position update */
-       union olsr_message * olsrMessage =
-                       (union olsr_message *) &message1->msg.olsrMessage;
-       unsigned int aligned_size = 0;
+       nmeaInfo = transmitGpsInformation.txPosition.nmeaInfo;
+       transmitGpsInformation.positionUpdated = false;
+       gateway = transmitGpsInformation.txGateway;
 
        /* convert nmeaINFO to wireformat olsr message */
-       (void) pthread_mutex_lock(&transmitGpsInformation.mutex);
-       if (!transmitGpsInformation.updated
-                       && positionValid(&transmitGpsInformation.txPosition)) {
-               nmea_time_now(&transmitGpsInformation.txPosition.nmeaInfo.utc);
-       }
-
-       txBufferSpaceTaken += sizeof(UplinkHeader);
-       aligned_size = gpsToOlsr(&transmitGpsInformation.txPosition.nmeaInfo,
-                       olsrMessage, txBufferSpaceFree,
-                       ((state.externalState == MOVING) ? getUpdateIntervalMoving()
-                                               : getUpdateIntervalStationary()));
-       txBufferSpaceTaken += aligned_size;
-       transmitGpsInformation.updated = false;
-       (void) pthread_mutex_unlock(&transmitGpsInformation.mutex);
-
-       if (aligned_size > 0) {
-               /* push out to all OLSR interfaces */
-               if ((interfaces & OLSR) != 0) {
-                       int r;
-                       struct interface *ifn;
-                       for (ifn = ifnet; ifn; ifn = ifn->int_next) {
-                               nodeIdPreTransmitHook(olsrMessage, ifn);
-                               r = net_outbuffer_push(ifn, olsrMessage, aligned_size);
-                               if (r != (int) aligned_size) {
-                                       pudError(
-                                                       false,
-                                                       "Could not send to OLSR interface %s: %s"
-                                                                       " (aligned_size=%u, r=%d)",
-                                                       ifn->int_name,
-                                                       ((r == -1) ? "no buffer was found"
-                                                       : (r == 0) ? "there was not enough room in the buffer"
-                                                       : "unknown reason"), aligned_size, r);
-                               }
-#ifdef PUD_DUMP_GPS_PACKETS_TX_OLSR
-                               else {
-                                       olsr_printf(0, "%s: packet sent to OLSR interface %s (%d bytes)\n",
-                                                       PUD_PLUGIN_ABBR, ifn->int_name, aligned_size);
-                                       dump_packet((unsigned char *)olsrMessage, aligned_size);
-                               }
-#endif
-                       }
+       txBufferBytesUsed += sizeof(UplinkHeader); /* keep before txBufferSpaceFree usage */
+       pu_size = gpsToOlsr(&nmeaInfo, pu, txBufferBytesFree,
+                       ((externalState == MOVEMENT_STATE_STATIONARY) ? getUpdateIntervalStationary() : getUpdateIntervalMoving()));
+       txBufferBytesUsed += pu_size;
 
-                       /* loopback to tx interface when so configured */
-                       if (getUseLoopback()) {
-                               (void) packetReceivedFromOlsr(olsrMessage, NULL, NULL);
+       /*
+        * push out to all OLSR interfaces
+        */
+       if (((interfaces & TX_INTERFACE_OLSR) != 0) && (pu_size > 0)) {
+               int r;
+               struct interface *ifn;
+               for (ifn = ifnet; ifn; ifn = ifn->int_next) {
+                       /* force the pending buffer out if there's not enough space for our message */
+                       if ((int)pu_size > net_outbuffer_bytes_left(ifn)) {
+                         net_output(ifn);
                        }
+                       r = net_outbuffer_push(ifn, pu, pu_size);
+                       if (r != (int) pu_size) {
+                               pudError(
+                                               false,
+                                               "Could not send to OLSR interface %s: %s (size=%u, r=%d)",
+                                               ifn->int_name,
+                                               ((r == -1) ? "no buffer was found" :
+                                                       (r == 0) ? "there was not enough room in the buffer" : "unknown reason"), pu_size, r);
+                       }
+               }
+
+               /* loopback to tx interface when so configured */
+               if (getUseLoopback()) {
+                       (void) packetReceivedFromOlsr(pu, NULL, NULL);
                }
+       }
+
+       /* push out over uplink when an uplink is configured */
+       if (((interfaces & TX_INTERFACE_UPLINK) != 0) && isUplinkAddrSet()) {
+               int fd = getDownlinkSocketFd();
+               if (fd != -1) {
+                       union olsr_sockaddr * uplink_addr = getUplinkAddr();
+                       void * addr;
+                       socklen_t addrSize;
+
+                       UplinkMessage * cl_uplink = (UplinkMessage *) &txBuffer[txBufferBytesUsed];
+                       UplinkClusterLeader * cl = &cl_uplink->msg.clusterLeader;
+                       union olsr_ip_addr * cl_originator = getClusterLeaderOriginator(olsr_cnf->ip_version, cl);
+                       union olsr_ip_addr * cl_clusterLeader = getClusterLeaderClusterLeader(olsr_cnf->ip_version, cl);
+
+                       unsigned int cl_size =
+                                       sizeof(UplinkClusterLeader) - sizeof(cl->leader)
+                                                       + ((olsr_cnf->ip_version == AF_INET) ? sizeof(cl->leader.v4) :
+                                                                       sizeof(cl->leader.v6));
+
+                       unsigned long long uplinkUpdateInterval =
+                                       (externalState == MOVEMENT_STATE_STATIONARY) ? getUplinkUpdateIntervalStationary() : getUplinkUpdateIntervalMoving();
+
+                       if (uplink_addr->in.sa_family == AF_INET) {
+                               addr = &uplink_addr->in4;
+                               addrSize = sizeof(struct sockaddr_in);
+                       } else {
+                               addr = &uplink_addr->in6;
+                               addrSize = sizeof(struct sockaddr_in6);
+                       }
+
+                       /*
+                        * position update message (pu)
+                        */
 
-               /* push out over uplink when an uplink is configured */
-               if (((interfaces & UPLINK) != 0) && isUplinkAddrSet()) {
-                       int fd = getUplinkSocketFd();
-                       if (fd != -1) {
-                               /* FIXME until we have gateway selection we just send ourselves
-                                * as cluster leader */
-                               union olsr_ip_addr * gwAddr = &olsr_cnf->main_addr;
-
-                               UplinkMessage * message2 =
-                                               (UplinkMessage *) &txBuffer[aligned_size
-                                                               + sizeof(UplinkHeader)];
-                               UplinkClusterLeader * clusterLeaderMessage =
-                                               &message2->msg.clusterLeader;
-                               unsigned int message2Size;
-                               union olsr_ip_addr * clOriginator;
-                               union olsr_ip_addr * clClusterLeader;
-
-                               /*
-                                * position update message (message1)
-                                */
-
-                               union olsr_sockaddr * address = getUplinkAddr();
-                               PudOlsrPositionUpdate * olsrGpsMessage = getOlsrMessagePayload(
-                                               olsr_cnf->ip_version, olsrMessage);
-
-                               /* set header fields */
-                               setUplinkMessageType(&message1->header, POSITION);
-                               setUplinkMessageLength(&message1->header, aligned_size);
-                               setUplinkMessageIPv6(&message1->header,
-                                               (olsr_cnf->ip_version != AF_INET));
-                               setUplinkMessagePadding(&message1->header, 0);
+                       /* set header fields in position update uplink message and adjust
+                        * the validity time to the uplink validity time */
+                       if (pu_size > 0) {
+                               PudOlsrPositionUpdate * pu_gpsMessage = getOlsrMessagePayload(olsr_cnf->ip_version, pu);
+
+                               setUplinkMessageType(&pu_uplink->header, POSITION);
+                               setUplinkMessageLength(&pu_uplink->header, pu_size);
+                               setUplinkMessageIPv6(&pu_uplink->header, (olsr_cnf->ip_version != AF_INET));
+                               setUplinkMessagePadding(&pu_uplink->header, 0);
 
                                /* fixup validity time */
-                               setValidityTime(&olsrGpsMessage->validityTime,
-                                               (state.externalState == MOVING) ?
-                                               getUplinkUpdateIntervalMoving() :
-                                               getUplinkUpdateIntervalStationary());
-
-                               /*
-                                * cluster leader message (message2)
-                                */
-
-                               clOriginator = getClusterLeaderOriginator(olsr_cnf->ip_version,
-                                               clusterLeaderMessage);
-                               clClusterLeader = getClusterLeaderClusterLeader(
-                                               olsr_cnf->ip_version, clusterLeaderMessage);
-                               if (olsr_cnf->ip_version == AF_INET) {
-                                       message2Size = sizeof(clusterLeaderMessage->version)
-                                                       + sizeof(clusterLeaderMessage->validityTime)
-                                                       + sizeof(clusterLeaderMessage->leader.v4);
-                               } else {
-                                       message2Size = sizeof(clusterLeaderMessage->version)
-                                                       + sizeof(clusterLeaderMessage->validityTime)
-                                                       + sizeof(clusterLeaderMessage->leader.v6);
-                               }
-
-                               /* set header fields */
-                               setUplinkMessageType(&message2->header, CLUSTERLEADER);
-                               setUplinkMessageLength(&message2->header, message2Size);
-                               setUplinkMessageIPv6(&message2->header,
-                                               (olsr_cnf->ip_version != AF_INET));
-                               setUplinkMessagePadding(&message2->header, 0);
-                               txBufferSpaceTaken += sizeof(UplinkHeader);
-
-                               /* setup validity time */
-                               setClusterLeaderVersion(clusterLeaderMessage, PUD_WIRE_FORMAT_VERSION);
-                               setValidityTime(&clusterLeaderMessage->validityTime,
-                                               (state.externalState == MOVING) ?
-                                               getUplinkUpdateIntervalMoving() :
-                                               getUplinkUpdateIntervalStationary());
-
-                               memcpy(clOriginator, &olsr_cnf->main_addr, olsr_cnf->ipsize);
-                               memcpy(clClusterLeader, gwAddr, olsr_cnf->ipsize);
-
-                               txBufferSpaceTaken += message2Size;
-
-                               errno = 0;
-                               if (sendto(fd, &txBuffer, txBufferSpaceTaken, 0,
-                                       (struct sockaddr *) &address->in, sizeof(address->in)) < 0) {
-                                       pudError(true, "Could not send to uplink"
-                                                       " (aligned_size=%u)", txBufferSpaceTaken);
-                               }
-#ifdef PUD_DUMP_GPS_PACKETS_TX_UPLINK
-                               else {
-                                       olsr_printf(0, "%s: packet sent to uplink (%d bytes)\n",
-                                                       PUD_PLUGIN_ABBR, aligned_size);
-                                       dump_packet((unsigned char *)&txBuffer,
-                                                       (sizeof(txBuffer) -
-                                                        sizeof(txBuffer.msg)) + aligned_size);
-                               }
-#endif
+                               setValidityTime(&pu_gpsMessage->validityTime, uplinkUpdateInterval);
+                       }
+
+                       /*
+                        * cluster leader message (cl)
+                        */
+
+                       /* set cl_uplink header fields */
+                       setUplinkMessageType(&cl_uplink->header, CLUSTERLEADER);
+                       setUplinkMessageLength(&cl_uplink->header, cl_size);
+                       setUplinkMessageIPv6(&cl_uplink->header, (olsr_cnf->ip_version != AF_INET));
+                       setUplinkMessagePadding(&cl_uplink->header, 0);
+
+                       /* setup cl */
+                       setClusterLeaderVersion(cl, PUD_WIRE_FORMAT_VERSION);
+                       setValidityTime(&cl->validityTime, uplinkUpdateInterval);
+
+                       /* really need 2 memcpy's here because of olsr_cnf->ipsize */
+                       memcpy(cl_originator, &olsr_cnf->main_addr, olsr_cnf->ipsize);
+                       memcpy(cl_clusterLeader, &gateway, olsr_cnf->ipsize);
+
+                       txBufferBytesUsed += sizeof(UplinkHeader);
+                       txBufferBytesUsed += cl_size;
+
+                       errno = 0;
+                       if (sendto(fd, &txBuffer, txBufferBytesUsed, 0, addr, addrSize) < 0) {
+                               pudError(true, "Could not send to uplink (size=%u)", txBufferBytesUsed);
                        }
                }
        }
@@ -359,7 +270,7 @@ static void txToAllOlsrInterfaces(TimedTxInterface interfaces) {
  unused
  */
 static void pud_olsr_tx_timer_callback(void *context __attribute__ ((unused))) {
-       txToAllOlsrInterfaces(OLSR);
+       txToAllOlsrInterfaces(TX_INTERFACE_OLSR);
 }
 
 /**
@@ -369,12 +280,91 @@ static void pud_olsr_tx_timer_callback(void *context __attribute__ ((unused))) {
  unused
  */
 static void pud_uplink_timer_callback(void *context __attribute__ ((unused))) {
-       txToAllOlsrInterfaces(UPLINK);
+       txToAllOlsrInterfaces(TX_INTERFACE_UPLINK);
+}
+
+/**
+ Restart the OLSR tx timer
+ */
+static void restartOlsrTimer(MovementState externalState) {
+       if (!restartOlsrTxTimer(
+                       (externalState == MOVEMENT_STATE_STATIONARY) ? getUpdateIntervalStationary() :
+                                       getUpdateIntervalMoving(), &pud_olsr_tx_timer_callback)) {
+               pudError(0, "Could not restart OLSR tx timer, no periodic"
+                               " position updates will be sent to the OLSR network");
+       }
+}
+
+/**
+ Restart the uplink tx timer
+ */
+static void restartUplinkTimer(MovementState externalState) {
+       if (!restartUplinkTxTimer(
+                       (externalState == MOVEMENT_STATE_STATIONARY) ? getUplinkUpdateIntervalStationary() :
+                                       getUplinkUpdateIntervalMoving(),
+                       &pud_uplink_timer_callback)) {
+               pudError(0, "Could not restart uplink timer, no periodic"
+                               " position updates will be uplinked");
+       }
+}
+
+static void doImmediateTransmit(MovementState externalState) {
+       TimedTxInterface interfaces = TX_INTERFACE_OLSR; /* always send over olsr */
+       restartOlsrTimer(externalState);
+
+       if (isUplinkAddrSet()) {
+               interfaces |= TX_INTERFACE_UPLINK;
+               restartUplinkTimer(externalState);
+       }
+
+       /* do an immediate transmit */
+       txToAllOlsrInterfaces(interfaces);
 }
 
 /**
- Detemine whether we are moving by comparing fields from the average
- position against those of the last transmitted position.
+ The gateway timer callback
+
+ @param context
+ unused
+ */
+static void pud_gateway_timer_callback(void *context __attribute__ ((unused))) {
+       union olsr_ip_addr bestGateway;
+       bool externalStateChange;
+       MovementState externalState;
+       TristateBoolean movingNow = TRISTATE_BOOLEAN_UNSET;
+
+       getBestUplinkGateway(&bestGateway);
+
+       /*
+        * Movement detection
+        */
+
+       if (!ipequal(&bestGateway, &transmitGpsInformation.txGateway)) {
+               movingNow = TRISTATE_BOOLEAN_SET;
+       }
+
+       /*
+        * State Determination
+        */
+
+       determineStateWithHysteresis(SUBSTATE_GATEWAY, movingNow, &externalState, &externalStateChange, NULL);
+
+       /*
+        * Update transmitGpsInformation
+        */
+
+       if (movingNow == TRISTATE_BOOLEAN_SET) {
+               transmitGpsInformation.txGateway = bestGateway;
+       }
+
+       if (externalStateChange) {
+               doImmediateTransmit(externalState);
+       }
+}
+
+/**
+ Detemine whether we are moving from the position, by comparing fields from the
+ average position against those of the last transmitted position.
 
  MUST be called which the position average list locked.
 
@@ -385,8 +375,7 @@ static void pud_uplink_timer_callback(void *context __attribute__ ((unused))) {
  @param result
  the results of all movement criteria
  */
-static void detemineMoving(PositionUpdateEntry * avg,
-               PositionUpdateEntry * lastTx, MovementType * result) {
+static void detemineMovingFromPosition(PositionUpdateEntry * avg, PositionUpdateEntry * lastTx, MovementType * result) {
        /* avg field presence booleans */
        bool avgHasSpeed;
        bool avgHasPos;
@@ -418,48 +407,44 @@ static void detemineMoving(PositionUpdateEntry * avg,
        bool vDistanceValid;
        bool vdopDistanceValid;
 
-       /* clear outputs */
-       memset(result, UNKNOWN, sizeof(MovementType));
-
        /*
         * Validity
         *
         * avg  last  movingNow
         *  0     0   UNKNOWN : can't determine whether we're moving
         *  0     1   UNKNOWN : can't determine whether we're moving
-        *  1     0   MOVING  : always seen as movement
+        *  1     0   UNKNOWN : can't determine whether we're moving
         *  1     1   determine via other parameters
         */
 
        if (!positionValid(avg)) {
-               /* everything is unknown */
+               result->moving = TRISTATE_BOOLEAN_UNKNOWN;
                return;
        }
 
        /* avg is valid here */
 
        if (!positionValid(lastTx)) {
-               result->moving = SET;
-               /* the rest is unknown */
+               result->moving = TRISTATE_BOOLEAN_UNKNOWN;
                return;
        }
 
        /* both avg and lastTx are valid here */
 
        /* avg field presence booleans */
-       avgHasSpeed = nmea_INFO_has_field(avg->nmeaInfo.smask, SPEED);
-       avgHasPos = nmea_INFO_has_field(avg->nmeaInfo.smask, LAT)
-                       && nmea_INFO_has_field(avg->nmeaInfo.smask, LON);
-       avgHasHdop = nmea_INFO_has_field(avg->nmeaInfo.smask, HDOP);
-       avgHasElv = nmea_INFO_has_field(avg->nmeaInfo.smask, ELV);
-       avgHasVdop = nmea_INFO_has_field(avg->nmeaInfo.smask, VDOP);
+       avgHasSpeed = nmea_INFO_is_present(avg->nmeaInfo.present, SPEED);
+       avgHasPos = nmea_INFO_is_present(avg->nmeaInfo.present, LAT)
+                       && nmea_INFO_is_present(avg->nmeaInfo.present, LON);
+       avgHasHdop = nmea_INFO_is_present(avg->nmeaInfo.present, HDOP);
+       avgHasElv = nmea_INFO_is_present(avg->nmeaInfo.present, ELV);
+       avgHasVdop = nmea_INFO_is_present(avg->nmeaInfo.present, VDOP);
 
        /* lastTx field presence booleans */
-       lastTxHasPos = nmea_INFO_has_field(lastTx->nmeaInfo.smask, LAT)
-                       && nmea_INFO_has_field(lastTx->nmeaInfo.smask, LON);
-       lastTxHasHdop = nmea_INFO_has_field(lastTx->nmeaInfo.smask, HDOP);
-       lastTxHasElv = nmea_INFO_has_field(lastTx->nmeaInfo.smask, ELV);
-       lastTxHasVdop = nmea_INFO_has_field(lastTx->nmeaInfo.smask, VDOP);
+       lastTxHasPos = nmea_INFO_is_present(lastTx->nmeaInfo.present, LAT)
+                       && nmea_INFO_is_present(lastTx->nmeaInfo.present, LON);
+       lastTxHasHdop = nmea_INFO_is_present(lastTx->nmeaInfo.present, HDOP);
+       lastTxHasElv = nmea_INFO_is_present(lastTx->nmeaInfo.present, ELV);
+       lastTxHasVdop = nmea_INFO_is_present(lastTx->nmeaInfo.present, VDOP);
 
        /* fill in some values _or_ defaults */
        dopMultiplier = getDopMultiplier();
@@ -483,7 +468,7 @@ static void detemineMoving(PositionUpdateEntry * avg,
                lastTxPos.lat = nmea_degree2radian(lastTx->nmeaInfo.lat);
                lastTxPos.lon = nmea_degree2radian(lastTx->nmeaInfo.lon);
 
-               hDistance = nmea_distance_ellipsoid(&avgPos, &lastTxPos, NULL, NULL);
+               hDistance = fabs(nmea_distance_ellipsoid(&avgPos, &lastTxPos, NULL, NULL));
                hDistanceValid = true;
        } else {
                hDistanceValid = false;
@@ -523,9 +508,9 @@ static void detemineMoving(PositionUpdateEntry * avg,
        /* Speed */
        if (avgHasSpeed) {
                if (avg->nmeaInfo.speed >= getMovingSpeedThreshold()) {
-                       result->speedOverThreshold = SET;
+                       result->speedOverThreshold = TRISTATE_BOOLEAN_SET;
                } else {
-                       result->speedOverThreshold = UNSET;
+                       result->speedOverThreshold = TRISTATE_BOOLEAN_UNSET;
                }
        }
 
@@ -539,12 +524,12 @@ static void detemineMoving(PositionUpdateEntry * avg,
         *  1     1   determine via distance threshold and HDOP
         */
        if (avgHasPos && !lastTxHasPos) {
-               result->hDistanceOverThreshold = SET;
+               result->hDistanceOverThreshold = TRISTATE_BOOLEAN_SET;
        } else if (hDistanceValid) {
                if (hDistance >= getMovingDistanceThreshold()) {
-                       result->hDistanceOverThreshold = SET;
+                       result->hDistanceOverThreshold = TRISTATE_BOOLEAN_SET;
                } else {
-                       result->hDistanceOverThreshold = UNSET;
+                       result->hDistanceOverThreshold = TRISTATE_BOOLEAN_UNSET;
                }
 
                /*
@@ -559,16 +544,16 @@ static void detemineMoving(PositionUpdateEntry * avg,
                if (hdopDistanceValid) {
                        /* we are outside the HDOP when the HDOPs no longer overlap */
                        if (hDistance > hdopDistanceForOutside) {
-                               result->outsideHdop = SET;
+                               result->outsideHdop = TRISTATE_BOOLEAN_SET;
                        } else {
-                               result->outsideHdop = UNSET;
+                               result->outsideHdop = TRISTATE_BOOLEAN_UNSET;
                        }
 
                        /* we are inside the HDOP when the HDOPs fully overlap */
                        if (hDistance <= hdopDistanceForInside) {
-                               result->insideHdop = SET;
+                               result->insideHdop = TRISTATE_BOOLEAN_SET;
                        } else {
-                               result->insideHdop = UNSET;
+                               result->insideHdop = TRISTATE_BOOLEAN_UNSET;
                        }
                }
        }
@@ -583,12 +568,12 @@ static void detemineMoving(PositionUpdateEntry * avg,
         *  1     1   determine via distance threshold and VDOP
         */
        if (avgHasElv && !lastTxHasElv) {
-               result->vDistanceOverThreshold = SET;
+               result->vDistanceOverThreshold = TRISTATE_BOOLEAN_SET;
        } else if (vDistanceValid) {
                if (vDistance >= getMovingDistanceThreshold()) {
-                       result->vDistanceOverThreshold = SET;
+                       result->vDistanceOverThreshold = TRISTATE_BOOLEAN_SET;
                } else {
-                       result->vDistanceOverThreshold = UNSET;
+                       result->vDistanceOverThreshold = TRISTATE_BOOLEAN_UNSET;
                }
 
                /*
@@ -603,16 +588,16 @@ static void detemineMoving(PositionUpdateEntry * avg,
                if (vdopDistanceValid) {
                        /* we are outside the VDOP when the VDOPs no longer overlap */
                        if (vDistance > vdopDistanceForOutside) {
-                               result->outsideVdop = SET;
+                               result->outsideVdop = TRISTATE_BOOLEAN_SET;
                        } else {
-                               result->outsideVdop = UNSET;
+                               result->outsideVdop = TRISTATE_BOOLEAN_UNSET;
                        }
 
                        /* we are inside the VDOP when the VDOPs fully overlap */
                        if (vDistance <= vdopDistanceForInside) {
-                               result->insideVdop = SET;
+                               result->insideVdop = TRISTATE_BOOLEAN_SET;
                        } else {
-                               result->insideVdop = UNSET;
+                               result->insideVdop = TRISTATE_BOOLEAN_UNSET;
                        }
                }
        }
@@ -622,37 +607,37 @@ static void detemineMoving(PositionUpdateEntry * avg,
         */
 
        /* accumulate inside criteria */
-       if ((result->insideHdop == SET) && (result->insideVdop == SET)) {
-               result->inside = SET;
-       } else if ((result->insideHdop == UNSET) || (result->insideVdop == UNSET)) {
-               result->inside = UNSET;
+       if ((result->insideHdop == TRISTATE_BOOLEAN_SET) && (result->insideVdop == TRISTATE_BOOLEAN_SET)) {
+               result->inside = TRISTATE_BOOLEAN_SET;
+       } else if ((result->insideHdop == TRISTATE_BOOLEAN_UNSET) || (result->insideVdop == TRISTATE_BOOLEAN_UNSET)) {
+               result->inside = TRISTATE_BOOLEAN_UNSET;
        }
 
        /* accumulate outside criteria */
-       if ((result->outsideHdop == SET) || (result->outsideVdop == SET)) {
-               result->outside = SET;
-       } else if ((result->outsideHdop == UNSET)
-                       || (result->outsideVdop == UNSET)) {
-               result->outside = UNSET;
+       if ((result->outsideHdop == TRISTATE_BOOLEAN_SET) || (result->outsideVdop == TRISTATE_BOOLEAN_SET)) {
+               result->outside = TRISTATE_BOOLEAN_SET;
+       } else if ((result->outsideHdop == TRISTATE_BOOLEAN_UNSET)
+                       || (result->outsideVdop == TRISTATE_BOOLEAN_UNSET)) {
+               result->outside = TRISTATE_BOOLEAN_UNSET;
        }
 
        /* accumulate threshold criteria */
-       if ((result->speedOverThreshold == SET)
-                       || (result->hDistanceOverThreshold == SET)
-                       || (result->vDistanceOverThreshold == SET)) {
-               result->overThresholds = SET;
-       } else if ((result->speedOverThreshold == UNSET)
-                       || (result->hDistanceOverThreshold == UNSET)
-                       || (result->vDistanceOverThreshold == UNSET)) {
-               result->overThresholds = UNSET;
+       if ((result->speedOverThreshold == TRISTATE_BOOLEAN_SET)
+                       || (result->hDistanceOverThreshold == TRISTATE_BOOLEAN_SET)
+                       || (result->vDistanceOverThreshold == TRISTATE_BOOLEAN_SET)) {
+               result->overThresholds = TRISTATE_BOOLEAN_SET;
+       } else if ((result->speedOverThreshold == TRISTATE_BOOLEAN_UNSET)
+                       || (result->hDistanceOverThreshold == TRISTATE_BOOLEAN_UNSET)
+                       || (result->vDistanceOverThreshold == TRISTATE_BOOLEAN_UNSET)) {
+               result->overThresholds = TRISTATE_BOOLEAN_UNSET;
        }
 
        /* accumulate moving criteria */
-       if ((result->overThresholds == SET) || (result->outside == SET)) {
-               result->moving = SET;
-       } else if ((result->overThresholds == UNSET)
-                       && (result->outside == UNSET)) {
-               result->moving = UNSET;
+       if ((result->overThresholds == TRISTATE_BOOLEAN_SET) || (result->outside == TRISTATE_BOOLEAN_SET)) {
+               result->moving = TRISTATE_BOOLEAN_SET;
+       } else if ((result->overThresholds == TRISTATE_BOOLEAN_UNSET)
+                       && (result->outside == TRISTATE_BOOLEAN_UNSET)) {
+               result->moving = TRISTATE_BOOLEAN_UNSET;
        }
 
        return;
@@ -678,13 +663,12 @@ bool receiverUpdateGpsInformation(unsigned char * rxBuffer, size_t rxCount) {
 
        bool retval = false;
        PositionUpdateEntry * incomingEntry;
-       MovementState newState = MOVING;
        PositionUpdateEntry * posAvgEntry;
        MovementType movementResult;
-       TristateBoolean movingNow;
-       bool internalStateChange = false;
-       bool externalStateChange = false;
+       bool subStateExternalStateChange;
+       bool externalStateChange;
        bool updateTransmitGpsInformation = false;
+       MovementState externalState;
 
        /* do not process when the message does not start with $GP */
        if ((rxCount < rxBufferPrefixLength) || (strncmp((char *) rxBuffer,
@@ -692,19 +676,12 @@ bool receiverUpdateGpsInformation(unsigned char * rxBuffer, size_t rxCount) {
                return true;
        }
 
-       (void) pthread_mutex_lock(&positionAverageList.mutex);
-
        /* parse all NMEA strings in the rxBuffer into the incoming entry */
        incomingEntry = getPositionAverageEntry(&positionAverageList, INCOMING);
        nmea_zero_INFO(&incomingEntry->nmeaInfo);
        nmea_parse(&nmeaParser, (char *) rxBuffer, rxCount,
                        &incomingEntry->nmeaInfo);
 
-#if defined(PUD_DUMP_AVERAGING)
-       dump_nmeaInfo(&incomingEntry->nmeaInfo,
-                       "receiverUpdateGpsInformation: incoming entry");
-#endif /* PUD_DUMP_AVERAGING */
-
        /* ignore when no useful information */
        if (incomingEntry->nmeaInfo.smask == GPNON) {
                retval = true;
@@ -713,24 +690,14 @@ bool receiverUpdateGpsInformation(unsigned char * rxBuffer, size_t rxCount) {
 
        nmea_INFO_sanitise(&incomingEntry->nmeaInfo);
 
-#if defined(PUD_DUMP_AVERAGING)
-       dump_nmeaInfo(&incomingEntry->nmeaInfo,
-                       "receiverUpdateGpsInformation: incoming entry after sanitise");
-#endif /* PUD_DUMP_AVERAGING */
-
        /* we always work with latitude, longitude in degrees and DOPs in meters */
        nmea_INFO_unit_conversion(&incomingEntry->nmeaInfo);
 
-#if defined(PUD_DUMP_AVERAGING)
-       dump_nmeaInfo(&incomingEntry->nmeaInfo,
-                       "receiverUpdateGpsInformation: incoming entry after unit conversion");
-#endif /* PUD_DUMP_AVERAGING */
-
        /*
-        * Averageing
+        * Averaging
         */
 
-       if (state.internalState == MOVING) {
+       if (getInternalState(SUBSTATE_POSITION) == MOVEMENT_STATE_MOVING) {
                /* flush average: keep only the incoming entry */
                flushPositionAverageList(&positionAverageList);
        }
@@ -741,141 +708,59 @@ bool receiverUpdateGpsInformation(unsigned char * rxBuffer, size_t rxCount) {
         * Movement detection
         */
 
-       detemineMoving(posAvgEntry, &txPosition, &movementResult);
-       movingNow = movementResult.moving;
-
-#if defined(PUD_DUMP_AVERAGING)
-       olsr_printf(0, "receiverUpdateGpsInformation: internalState = %s\n",
-                       MovementStateToString(state.internalState));
-       olsr_printf(0, "receiverUpdateGpsInformation: movingNow     = %s\n",
-                       TristateBooleanToString(movingNow));
-#endif /* PUD_DUMP_AVERAGING */
+       clearMovementType(&movementResult);
 
-       /*
-        * Internal State
-        */
-
-       if (movingNow == SET) {
-               newState = MOVING;
-       } else if (movingNow == UNSET) {
-               newState = STATIONARY;
-       }
-       internalStateChange = (state.internalState != newState);
-       state.internalState = newState;
+       detemineMovingFromPosition(posAvgEntry, &transmitGpsInformation.txPosition, &movementResult);
 
        /*
-        * External State (+ hysteresis)
+        * State Determination
         */
 
-       if (internalStateChange) {
-               /* restart hysteresis for external state change when we have an internal
-                * state change */
-               state.hysteresisCounter = 0;
-       }
-
-       /* when internal state and external state are not the same we need to
-        * perform hysteresis before we can propagate the internal state to the
-        * external state */
-       newState = state.externalState;
-       if (state.internalState != state.externalState) {
-               switch (state.internalState) {
-                       case STATIONARY:
-                               /* external state is MOVING */
-
-                               /* delay going to stationary a bit */
-                               state.hysteresisCounter++;
-
-                               if (state.hysteresisCounter
-                                               >= getHysteresisCountToStationary()) {
-                                       /* outside the hysteresis range, go to stationary */
-                                       newState = STATIONARY;
-                               }
-                               break;
-
-                       case MOVING:
-                               /* external state is STATIONARY */
-
-                               /* delay going to moving a bit */
-                               state.hysteresisCounter++;
-
-                               if (state.hysteresisCounter >= getHysteresisCountToMoving()) {
-                                       /* outside the hysteresis range, go to moving */
-                                       newState = MOVING;
-                               }
-                               break;
-
-                       default:
-                               /* when unknown do just as if we transition into moving */
-                               newState = MOVING;
-                               break;
-               }
-       }
-       externalStateChange = (state.externalState != newState);
-       state.externalState = newState;
-
-#if defined(PUD_DUMP_AVERAGING)
-       olsr_printf(0, "receiverUpdateGpsInformation: newState = %s\n",
-                       MovementStateToString(newState));
-       dump_nmeaInfo(&posAvgEntry->nmeaInfo,
-                       "receiverUpdateGpsInformation: posAvgEntry");
-#endif /* PUD_DUMP_AVERAGING */
+       determineStateWithHysteresis(SUBSTATE_POSITION, movementResult.moving, &externalState, &externalStateChange,
+                       &subStateExternalStateChange);
 
        /*
         * Update transmitGpsInformation
         */
 
-       updateTransmitGpsInformation = externalStateChange
-                       || (positionValid(posAvgEntry) && !positionValid(&txPosition))
-                       || (movementResult.inside == SET);
-
-       if ((state.externalState == MOVING) || updateTransmitGpsInformation) {
-               memcpy(&txPosition.nmeaInfo, &posAvgEntry->nmeaInfo, sizeof(nmeaINFO));
-               (void) pthread_mutex_lock(&transmitGpsInformation.mutex);
-               memcpy(&transmitGpsInformation.txPosition.nmeaInfo,
-                               &posAvgEntry->nmeaInfo, sizeof(nmeaINFO));
-               transmitGpsInformation.updated = true;
+       updateTransmitGpsInformation = subStateExternalStateChange
+                       || (positionValid(posAvgEntry) && !positionValid(&transmitGpsInformation.txPosition))
+                       || (movementResult.inside == TRISTATE_BOOLEAN_SET);
 
-#if defined(PUD_DUMP_AVERAGING)
-               dump_nmeaInfo(&transmitGpsInformation.txPosition.nmeaInfo,
-                       "receiverUpdateGpsInformation: transmitGpsInformation");
-#endif /* PUD_DUMP_AVERAGING */
+       if ((externalState == MOVEMENT_STATE_MOVING) || updateTransmitGpsInformation) {
+               transmitGpsInformation.txPosition = *posAvgEntry;
+               transmitGpsInformation.positionUpdated = true;
 
-               (void) pthread_mutex_unlock(&transmitGpsInformation.mutex);
-       }
-
-       if (updateTransmitGpsInformation) {
-               TimedTxInterface interfaces = OLSR; /* always send over olsr */
-               if (!restartOlsrTxTimer(
-                               (state.externalState == STATIONARY) ? getUpdateIntervalStationary()
-                               : getUpdateIntervalMoving(), &pud_olsr_tx_timer_callback)) {
-                       pudError(0, "Could not restart OLSR tx timer, no periodic"
-                                       " position updates will be sent to the OLSR network");
-               }
-
-               if (isUplinkAddrSet()) {
-                       interfaces |= UPLINK;
-                       if (!restartUplinkTxTimer(
-                                       (state.externalState == STATIONARY) ? getUplinkUpdateIntervalStationary()
-                                       : getUplinkUpdateIntervalMoving(), &pud_uplink_timer_callback)
-                                       ) {
-                               pudError(0, "Could not restart uplink timer, no periodic"
-                                               " position updates will be uplinked");
-                       }
+               /*
+                * When we're stationary:
+                * - the track is not reliable or even invalid, so we must clear it.
+                * - to avoid confusion in consumers of the data, we must clear the speed
+                *   because it is possible to have a very low speed while moving.
+                */
+               if (externalState == MOVEMENT_STATE_STATIONARY) {
+                       transmitGpsInformation.txPosition.nmeaInfo.speed = (double)0.0;
+                       transmitGpsInformation.txPosition.nmeaInfo.track = (double)0.0;
                }
+       }
 
-               /* do an immediate transmit */
-               txToAllOlsrInterfaces(interfaces);
+       if (externalStateChange) {
+               doImmediateTransmit(externalState);
        }
 
        retval = true;
 
-       end: (void) pthread_mutex_unlock(&positionAverageList.mutex);
+       end:
        return retval;
 }
 
-/*
- * Receiver start/stop
+/**
+ * Log nmea library errors as plugin errors
+ * @param str
+ * @param str_size
  */
+static void nmea_errors(const char *str, int str_size __attribute__((unused))) {
+       pudError(false, "NMEA library error: %s", str);
+}
 
 /**
  Start the receiver
@@ -885,31 +770,29 @@ bool receiverUpdateGpsInformation(unsigned char * rxBuffer, size_t rxCount) {
  - true otherwise
  */
 bool startReceiver(void) {
-       pthread_mutexattr_t attr;
-       if (pthread_mutexattr_init(&attr)) {
-               return false;
-       }
-       if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP)) {
-               return false;
-       }
-       if (pthread_mutex_init(&transmitGpsInformation.mutex, &attr)) {
-               return false;
-       }
+       MovementState externalState;
+       char * positionFile = getPositionFile();
 
        if (!nmea_parser_init(&nmeaParser)) {
                pudError(false, "Could not initialise NMEA parser");
                return false;
        }
 
-       nmea_zero_INFO(&transmitGpsInformation.txPosition.nmeaInfo);
-       transmitGpsInformation.updated = false;
-
-       nmea_zero_INFO(&txPosition.nmeaInfo);
+       /* hook up the NMEA library error callback */
+       nmea_context_set_error_func(&nmea_errors);
 
-       state.internalState = MOVING;
-       state.externalState = MOVING;
-       state.hysteresisCounter = 0;
+       if (positionFile) {
+               readPositionFile(positionFile, &transmitGpsInformation.txPosition.nmeaInfo);
+       } else {
+               nmea_zero_INFO(&transmitGpsInformation.txPosition.nmeaInfo);
+       }
+       transmitGpsInformation.txGateway = olsr_cnf->main_addr;
+       transmitGpsInformation.positionUpdated = false;
+       transmitGpsInformation.nodeId = getNodeId();
 
+#ifdef HTTPINFO_PUD
+       olsr_cnf->pud_position = &transmitGpsInformation;
+#endif /* HTTPINFO_PUD */
        initPositionAverageList(&positionAverageList, getAverageDepth());
 
        if (!initOlsrTxTimer()) {
@@ -922,6 +805,20 @@ bool startReceiver(void) {
                return false;
        }
 
+       if (!initGatewayTimer()) {
+               stopReceiver();
+               return false;
+       }
+
+       externalState = getExternalState();
+       restartOlsrTimer(externalState);
+       restartUplinkTimer(externalState);
+       if (!restartGatewayTimer(getGatewayDeterminationInterval(), &pud_gateway_timer_callback)) {
+               pudError(0, "Could not start gateway timer");
+               stopReceiver();
+               return false;
+       }
+
        return true;
 }
 
@@ -929,21 +826,15 @@ bool startReceiver(void) {
  Stop the receiver
  */
 void stopReceiver(void) {
+       destroyGatewayTimer();
        destroyUplinkTxTimer();
        destroyOlsrTxTimer();
 
        destroyPositionAverageList(&positionAverageList);
 
-       state.hysteresisCounter = 0;
-       state.externalState = MOVING;
-       state.internalState = MOVING;
-
-       nmea_zero_INFO(&txPosition.nmeaInfo);
-
-       transmitGpsInformation.updated = false;
        nmea_zero_INFO(&transmitGpsInformation.txPosition.nmeaInfo);
+       transmitGpsInformation.txGateway = olsr_cnf->main_addr;
+       transmitGpsInformation.positionUpdated = false;
 
        nmea_parser_destroy(&nmeaParser);
-
-       (void) pthread_mutex_destroy(&transmitGpsInformation.mutex);
 }