update comments on all ifdefs
[olsrd.git] / lib / pud / src / receiver.c
index 8db152a..86295af 100644 (file)
@@ -2,38 +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"
-#include "ipcalc.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
@@ -46,46 +31,10 @@ 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 */
 
-       TristateBoolean differentGateway; /**< SET: the gateway is different */
-
        TristateBoolean overThresholds; /**< SET: at least 1 threshold state is set */
        TristateBoolean speedOverThreshold; /**< SET: speed is over threshold */
        TristateBoolean hDistanceOverThreshold; /**< SET: horizontal distance is outside threshold */
@@ -112,18 +61,10 @@ 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 */
-       union olsr_ip_addr txGateway; /**< the best gateway at the time the transmitted position was determined */
-} TransmitGpsInformation;
-
 /** The latest position information that is transmitted */
 static TransmitGpsInformation transmitGpsInformation;
 
@@ -140,18 +81,17 @@ static TransmitGpsInformation transmitGpsInformation;
  */
 static void clearMovementType(MovementType * result) {
        /* clear outputs */
-       result->moving = UNKNOWN;
-       result->differentGateway = UNSET;
-       result->overThresholds = UNKNOWN;
-       result->speedOverThreshold = UNKNOWN;
-       result->hDistanceOverThreshold = UNKNOWN;
-       result->vDistanceOverThreshold = UNKNOWN;
-       result->outside = UNKNOWN;
-       result->outsideHdop = UNKNOWN;
-       result->outsideVdop = UNKNOWN;
-       result->inside = UNKNOWN;
-       result->insideHdop = UNKNOWN;
-       result->insideVdop = UNKNOWN;
+       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;
 }
 
 /**
@@ -165,7 +105,7 @@ static void clearMovementType(MovementType * result) {
  - 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));
 }
 
@@ -185,9 +125,7 @@ static void txToAllOlsrInterfaces(TimedTxInterface interfaces) {
        /*
         * The first message in txBuffer is an OLSR position update.
         *
-        * The position update is not present when the position is not valid.
-        * Otherwise it is always present: when we transmit onto the OLSR network
-        * and/or when we transmit onto the uplink.
+        * The position update is always present.
         *
         * The second message is the cluster leader message, but only when uplink
         * was requested and correctly configured.
@@ -197,31 +135,37 @@ static void txToAllOlsrInterfaces(TimedTxInterface interfaces) {
        union olsr_message * pu = &pu_uplink->msg.olsrMessage;
        unsigned int pu_size = 0;
        union olsr_ip_addr gateway;
+       MovementState externalState;
+       nmeaINFO nmeaInfo;
 
-       (void) pthread_mutex_lock(&transmitGpsInformation.mutex);
+       externalState = getExternalState();
 
        /* only fixup timestamp when the position is valid _and_ when the position was not updated */
-       if (positionValid(&transmitGpsInformation.txPosition) && !transmitGpsInformation.updated) {
-               nmea_time_now(&transmitGpsInformation.txPosition.nmeaInfo.utc);
+       if (positionValid(&transmitGpsInformation.txPosition) && !transmitGpsInformation.positionUpdated) {
+               nmea_time_now(&transmitGpsInformation.txPosition.nmeaInfo.utc, &transmitGpsInformation.txPosition.nmeaInfo.present);
        }
 
+       nmeaInfo = transmitGpsInformation.txPosition.nmeaInfo;
+       transmitGpsInformation.positionUpdated = false;
+       gateway = transmitGpsInformation.txGateway;
+
        /* convert nmeaINFO to wireformat olsr message */
        txBufferBytesUsed += sizeof(UplinkHeader); /* keep before txBufferSpaceFree usage */
-       pu_size = gpsToOlsr(&transmitGpsInformation.txPosition.nmeaInfo, pu, txBufferBytesFree,
-                       ((state.externalState == MOVING) ? getUpdateIntervalMoving() : getUpdateIntervalStationary()));
+       pu_size = gpsToOlsr(&nmeaInfo, pu, txBufferBytesFree,
+                       ((externalState == MOVEMENT_STATE_STATIONARY) ? getUpdateIntervalStationary() : getUpdateIntervalMoving()));
        txBufferBytesUsed += pu_size;
-       gateway = transmitGpsInformation.txGateway;
-
-       transmitGpsInformation.updated = false;
-       (void) pthread_mutex_unlock(&transmitGpsInformation.mutex);
 
        /*
         * push out to all OLSR interfaces
         */
-       if (((interfaces & OLSR) != 0) && (pu_size > 0)) {
+       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(
@@ -231,13 +175,6 @@ static void txToAllOlsrInterfaces(TimedTxInterface interfaces) {
                                                ((r == -1) ? "no buffer was found" :
                                                        (r == 0) ? "there was not enough room in the buffer" : "unknown reason"), pu_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, pu_size);
-                               dump_packet((unsigned char *)pu, pu_size);
-                       }
-#endif
                }
 
                /* loopback to tx interface when so configured */
@@ -247,20 +184,34 @@ static void txToAllOlsrInterfaces(TimedTxInterface interfaces) {
        }
 
        /* push out over uplink when an uplink is configured */
-       if (((interfaces & UPLINK) != 0) && isUplinkAddrSet()) {
+       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)
                         */
@@ -276,10 +227,7 @@ static void txToAllOlsrInterfaces(TimedTxInterface interfaces) {
                                setUplinkMessagePadding(&pu_uplink->header, 0);
 
                                /* fixup validity time */
-                               setValidityTime(
-                                               &pu_gpsMessage->validityTime,
-                                               (state.externalState == MOVING) ?
-                                                               getUplinkUpdateIntervalMoving() : getUplinkUpdateIntervalStationary());
+                               setValidityTime(&pu_gpsMessage->validityTime, uplinkUpdateInterval);
                        }
 
                        /*
@@ -294,10 +242,7 @@ static void txToAllOlsrInterfaces(TimedTxInterface interfaces) {
 
                        /* setup cl */
                        setClusterLeaderVersion(cl, PUD_WIRE_FORMAT_VERSION);
-                       setValidityTime(
-                                       &cl->validityTime,
-                                       (state.externalState == MOVING) ?
-                                                       getUplinkUpdateIntervalMoving() : getUplinkUpdateIntervalStationary());
+                       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);
@@ -307,17 +252,9 @@ static void txToAllOlsrInterfaces(TimedTxInterface interfaces) {
                        txBufferBytesUsed += cl_size;
 
                        errno = 0;
-                       if (sendto(fd, &txBuffer, txBufferBytesUsed, 0, (struct sockaddr *) &uplink_addr->in,
-                                       sizeof(uplink_addr->in)) < 0) {
+                       if (sendto(fd, &txBuffer, txBufferBytesUsed, 0, addr, addrSize) < 0) {
                                pudError(true, "Could not send to uplink (size=%u)", txBufferBytesUsed);
                        }
-#ifdef PUD_DUMP_GPS_PACKETS_TX_UPLINK
-                       else {
-                               olsr_printf(0, "%s: packet sent to uplink (%d bytes)\n",
-                                               PUD_PLUGIN_ABBR, pu_size);
-                               dump_packet((unsigned char *)&txBuffer, txBufferBytesUsed);
-                       }
-#endif
                }
        }
 }
@@ -333,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);
 }
 
 /**
@@ -343,34 +280,86 @@ 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);
 }
 
 /**
- Detemine whether we are moving from the gateway.
+ 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");
+       }
+}
 
- MUST be called which the position average list locked.
+/**
+ 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");
+       }
+}
 
- @param gateway
- the current best gateway
- @param lastGateway
- the last best gateway
- @param result
- the results of all movement criteria
+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);
+}
+
+/**
+ The gateway timer callback
+
+ @param context
+ unused
  */
-static void detemineMovingFromGateway(union olsr_ip_addr * gateway, union olsr_ip_addr * lastGateway,
-               MovementType * result) {
+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);
+
        /*
-        * When the gateway is different from the gateway during last transmit, then
-        * we force MOVING
+        * Movement detection
         */
-       if (!ipequal(gateway, lastGateway)) {
-               result->moving = SET;
-               result->differentGateway = SET;
-               return;
+
+       if (!ipequal(&bestGateway, &transmitGpsInformation.txGateway)) {
+               movingNow = TRISTATE_BOOLEAN_SET;
        }
 
-       result->differentGateway = UNSET;
+       /*
+        * State Determination
+        */
+
+       determineStateWithHysteresis(SUBSTATE_GATEWAY, movingNow, &externalState, &externalStateChange, NULL);
+
+       /*
+        * Update transmitGpsInformation
+        */
+
+       if (movingNow == TRISTATE_BOOLEAN_SET) {
+               transmitGpsInformation.txGateway = bestGateway;
+       }
+
+       if (externalStateChange) {
+               doImmediateTransmit(externalState);
+       }
 }
 
 /**
@@ -429,33 +418,33 @@ static void detemineMovingFromPosition(PositionUpdateEntry * avg, PositionUpdate
         */
 
        if (!positionValid(avg)) {
-               result->moving = UNKNOWN;
+               result->moving = TRISTATE_BOOLEAN_UNKNOWN;
                return;
        }
 
        /* avg is valid here */
 
        if (!positionValid(lastTx)) {
-               result->moving = 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();
@@ -479,7 +468,7 @@ static void detemineMovingFromPosition(PositionUpdateEntry * avg, PositionUpdate
                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;
@@ -519,9 +508,9 @@ static void detemineMovingFromPosition(PositionUpdateEntry * avg, PositionUpdate
        /* 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;
                }
        }
 
@@ -535,12 +524,12 @@ static void detemineMovingFromPosition(PositionUpdateEntry * avg, PositionUpdate
         *  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;
                }
 
                /*
@@ -555,16 +544,16 @@ static void detemineMovingFromPosition(PositionUpdateEntry * avg, PositionUpdate
                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;
                        }
                }
        }
@@ -579,12 +568,12 @@ static void detemineMovingFromPosition(PositionUpdateEntry * avg, PositionUpdate
         *  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;
                }
 
                /*
@@ -599,16 +588,16 @@ static void detemineMovingFromPosition(PositionUpdateEntry * avg, PositionUpdate
                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;
                        }
                }
        }
@@ -618,67 +607,42 @@ static void detemineMovingFromPosition(PositionUpdateEntry * avg, PositionUpdate
         */
 
        /* 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;
 }
 
-/**
- Restart the OLSR tx timer
- */
-static void restartOlsrTimer(void) {
-       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");
-       }
-}
-
-/**
- Restart the uplink tx timer
- */
-static void restartUplinkTimer(void) {
-       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");
-       }
-}
-
 /**
  Update the latest GPS information. This function is called when a packet is
  received from a rxNonOlsr interface, containing one or more NMEA strings with
@@ -699,16 +663,12 @@ bool receiverUpdateGpsInformation(unsigned char * rxBuffer, size_t rxCount) {
 
        bool retval = false;
        PositionUpdateEntry * incomingEntry;
-       MovementState newState;
        PositionUpdateEntry * posAvgEntry;
        MovementType movementResult;
-       TristateBoolean movingNow;
-       bool internalStateChange = false;
-       bool externalStateChange = false;
+       bool subStateExternalStateChange;
+       bool externalStateChange;
        bool updateTransmitGpsInformation = false;
-       union olsr_ip_addr bestGateway;
-       PositionUpdateEntry txPosition;
-       union olsr_ip_addr txGateway;
+       MovementState externalState;
 
        /* do not process when the message does not start with $GP */
        if ((rxCount < rxBufferPrefixLength) || (strncmp((char *) rxBuffer,
@@ -716,22 +676,12 @@ bool receiverUpdateGpsInformation(unsigned char * rxBuffer, size_t rxCount) {
                return true;
        }
 
-       (void) pthread_mutex_lock(&transmitGpsInformation.mutex);
-       txPosition = transmitGpsInformation.txPosition;
-       txGateway = transmitGpsInformation.txGateway;
-       (void) pthread_mutex_unlock(&transmitGpsInformation.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;
@@ -740,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);
        }
@@ -768,125 +708,43 @@ bool receiverUpdateGpsInformation(unsigned char * rxBuffer, size_t rxCount) {
         * Movement detection
         */
 
-       getBestUplinkGateway(&bestGateway);
        clearMovementType(&movementResult);
-       detemineMovingFromGateway(&bestGateway, &txGateway, &movementResult);
-       if (movementResult.moving != SET) {
-               detemineMovingFromPosition(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 */
 
-       /*
-        * Internal State
-        */
-
-       if (movingNow == SET) {
-               newState = MOVING;
-       } else if (movingNow == UNSET) {
-               newState = STATIONARY;
-       } else {
-               /* force back to stationary for unknown movement */
-               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 stationary */
-                               newState = STATIONARY;
-                               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);
+       updateTransmitGpsInformation = subStateExternalStateChange
+                       || (positionValid(posAvgEntry) && !positionValid(&transmitGpsInformation.txPosition))
+                       || (movementResult.inside == TRISTATE_BOOLEAN_SET);
 
-       if ((state.externalState == MOVING) || updateTransmitGpsInformation) {
-               (void) pthread_mutex_lock(&transmitGpsInformation.mutex);
-               transmitGpsInformation.txPosition.nmeaInfo = posAvgEntry->nmeaInfo;
-               transmitGpsInformation.txGateway = bestGateway;
-               transmitGpsInformation.updated = true;
-               (void) pthread_mutex_unlock(&transmitGpsInformation.mutex);
+       if ((externalState == MOVEMENT_STATE_MOVING) || updateTransmitGpsInformation) {
+               transmitGpsInformation.txPosition = *posAvgEntry;
+               transmitGpsInformation.positionUpdated = true;
 
-#if defined(PUD_DUMP_AVERAGING)
-               dump_nmeaInfo(&posAvgEntry->nmeaInfo,
-                       "receiverUpdateGpsInformation: transmitGpsInformation");
-#endif /* PUD_DUMP_AVERAGING */
+               /*
+                * 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;
+               }
        }
 
        if (externalStateChange) {
-               TimedTxInterface interfaces = OLSR; /* always send over olsr */
-               restartOlsrTimer();
-
-               if (isUplinkAddrSet()) {
-                       interfaces |= UPLINK;
-                       restartUplinkTimer();
-               }
-
-               /* do an immediate transmit */
-               txToAllOlsrInterfaces(interfaces);
+               doImmediateTransmit(externalState);
        }
 
        retval = true;
@@ -895,9 +753,14 @@ bool receiverUpdateGpsInformation(unsigned char * rxBuffer, size_t rxCount) {
        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
@@ -907,30 +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.txGateway = olsr_cnf->main_addr;
-       transmitGpsInformation.updated = false;
+       /* 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()) {
@@ -943,8 +805,19 @@ bool startReceiver(void) {
                return false;
        }
 
-       restartOlsrTimer();
-       restartUplinkTimer();
+       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;
 }
@@ -953,22 +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;
-
-       (void) pthread_mutex_lock(&transmitGpsInformation.mutex);
-       transmitGpsInformation.updated = false;
        nmea_zero_INFO(&transmitGpsInformation.txPosition.nmeaInfo);
        transmitGpsInformation.txGateway = olsr_cnf->main_addr;
-       (void) pthread_mutex_unlock(&transmitGpsInformation.mutex);
+       transmitGpsInformation.positionUpdated = false;
 
        nmea_parser_destroy(&nmeaParser);
-
-       (void) pthread_mutex_destroy(&transmitGpsInformation.mutex);
 }