diff --git a/CSCE838-Lab4-Node/CSCE838-Lab4-Node.ino b/CSCE838-Lab4-Node/CSCE838-Lab4-Node.ino
index 7735c85dd6778b689486cb8c3e39e26938738f1f..ecd81f6db0bb9e8b04376c3e43b8569744bc7c10 100644
--- a/CSCE838-Lab4-Node/CSCE838-Lab4-Node.ino
+++ b/CSCE838-Lab4-Node/CSCE838-Lab4-Node.ino
@@ -15,6 +15,40 @@
 #include <lmic.h>
 #include <hal/hal.h>
 #include <SPI.h>
+#include <TemperatureZero.h>
+
+/** Defines **/
+#define NODE_ID  1
+#define NETWORK_FREQUENCY 914
+
+// clock parameters
+#define CPU_HZ 48000000
+#define TIMER_PRESCALER_DIV 1024
+
+// Error codes
+#define ERROR_CODE_MISSING_PACKET     0x01
+#define ERROR_CODE_RECEPTION_FAILURE    0x02
+#define ERROR_CODE_WDT_EARLY_WARNING    0x04
+#define ERROR_CODE_INVALID_TEMPERATURE    0x08
+#define ERROR_CODE_MISMATCHED_PACKET_NUM  0x10
+
+/** Type Definitions **/
+typedef struct
+{
+  uint8_t NodeID;
+  uint16_t PacketID;
+  uint32_t Timestamp;
+  float AverageTemperature;
+  uint8_t ErrorType;
+} Lab3Packet_t;
+
+/** Static Variables **/
+static TemperatureZero TempZero = TemperatureZero();
+static float g_temperature_samples[5];
+static float g_temperature_samples_count = 0;
+static int32_t initialMillis = 0;
+static volatile Lab3Packet_t txPacket;
+static volatile unsigned int counterISR = 1;
 
 // we formerly would check this configuration; but now there is a flag,
 // in the LMIC, LMIC.noRXIQinversion;
@@ -49,24 +83,36 @@ const lmic_pinmap lmic_pins = {
 void os_getArtEui (u1_t* buf) { }
 void os_getDevEui (u1_t* buf) { }
 void os_getDevKey (u1_t* buf) { }
-
-void onEvent (ev_t ev)
-{
-  
-}
+void onEvent (ev_t ev) { }
 
 osjob_t txjob;
 osjob_t timeoutjob;
 static void tx_func (osjob_t* job);
 
 // Transmit the given string and call the given function afterwards
+/*
 void tx(const char *str, osjobcb_t func)
 {
   os_radio(RADIO_RST); // Stop RX first
   delay(1); // Wait a bit, without this os_radio below asserts, apparently because the state hasn't changed yet
   LMIC.dataLen = 0;
   while (*str)
-  LMIC.frame[LMIC.dataLen++] = *str++;
+    LMIC.frame[LMIC.dataLen++] = *str++;
+  LMIC.osjob.func = func;
+  os_radio(RADIO_TX);
+  SerialUSB.println("TX");
+}
+*/
+void tx(const void* msg, uint8_t len, osjobcb_t func)
+{
+  os_radio(RADIO_RST); // Stop RX first
+  delay(1); // Wait a bit, without this os_radio below asserts, apparently because the state hasn't changed yet
+  LMIC.dataLen = len;
+  uint16_t i;
+  for (i = 0; i < len; i++)
+  {
+    LMIC.frame[i] = *(((uint8_t*)msg) + i);
+  }
   LMIC.osjob.func = func;
   os_radio(RADIO_TX);
   SerialUSB.println("TX");
@@ -96,14 +142,31 @@ static void rx_func (osjob_t* job)
   digitalWrite(LED_BUILTIN, HIGH); // on
   // Timeout RX (i.e. update led status) after 3 periods without RX
   os_setTimedCallback(&timeoutjob, os_getTime() + ms2osticks(3*TX_INTERVAL), rxtimeout_func);
-  // Reschedule TX so that it should not collide with the other side's
-  // next TX
+  // Reschedule TX so that it should not collide with the other side's next TX
   os_setTimedCallback(&txjob, os_getTime() + ms2osticks(TX_INTERVAL / 2), tx_func);
-  SerialUSB.print("Got ");
-  SerialUSB.print(LMIC.dataLen);
-  SerialUSB.println(" bytes");
-  SerialUSB.write(LMIC.frame, LMIC.dataLen);
-  SerialUSB.println();
+
+  // Print the received output
+  if (LMIC.dataLen != sizeof(Lab3Packet_t))
+  {
+    SerialUSB.println("Error: improper RX packet size.");
+  }
+  else
+  {
+    Lab3Packet_t* packet = (Lab3Packet_t*)LMIC.frame;
+
+    if (initialMillis == 0)
+    {
+      initialMillis = millis() - packet->Timestamp;
+    }
+    printPacket(packet);
+  }
+
+  //SerialUSB.print("Got ");
+  //SerialUSB.print(LMIC.dataLen);
+  //SerialUSB.println(" bytes");
+  //SerialUSB.write(LMIC.frame, LMIC.dataLen);
+  //SerialUSB.println();
+
   // Restart RX
   rx(rx_func);
 }
@@ -117,7 +180,22 @@ static void txdone_func (osjob_t* job)
 static void tx_func (osjob_t* job)
 {
   // say hello
-  tx("Hello, world!", txdone_func);
+  //tx("Hello, world!", txdone_func);
+  
+  // Construct packet
+  constructTxPacket((Lab3Packet_t*)&txPacket);
+
+  // Send the packet
+  tx((Lab3Packet_t*)&txPacket, sizeof(txPacket), txdone_func);
+
+  // Print the packet
+  printPacket((Lab3Packet_t*)&txPacket);
+  IsErrorDetect((Lab3Packet_t*)&txPacket);
+
+  // Set up the next packet's packet ID
+  txPacket.PacketID++;
+  txPacket.ErrorType = 0;
+
   // reschedule job every TX_INTERVAL (plus a bit of random to prevent systematic collisions), unless packets are received, then rx_func will reschedule at half this time.
   os_setTimedCallback(job, os_getTime() + ms2osticks(TX_INTERVAL + random(500)), tx_func);
 }
@@ -125,9 +203,17 @@ static void tx_func (osjob_t* job)
 // application entry point
 void setup()
 {
+  // Initialize temperature readings, start timer that gets temperature samples
+  TempZero.init();
+  startTimer(1);
+
+  // Initialize Serial USB interface
   SerialUSB.begin(115200);
   while (!SerialUSB); // wait for Serial Monitor to be attached to SparkFun Pro RF board
   SerialUSB.println("Starting");
+
+  // Initialize transmit packet
+  initPacket((Lab3Packet_t*)&txPacket);
   
   pinMode(LED_BUILTIN, OUTPUT);
   // initialize runtime env
@@ -137,7 +223,7 @@ void setup()
   uint32_t uBandwidth;
   LMIC.freq = 915000000; // originally was 903900000 in the example
   uBandwidth = 125;
-  LMIC.datarate = US915_DR_SF10; // DR4, originally was US915_DR_SF7 in the example
+  LMIC.datarate = US915_DR_SF7; // DR4
   LMIC.txpow = 20;  // originally was 21 in the example
   
   // disable RX IQ inversion
@@ -168,3 +254,213 @@ void loop()
   // execute scheduled jobs and events
   os_runloop_once();
 }
+
+
+
+/** Function Definitions **/
+int getNewTemperatureSample()
+{
+  // Returns 0 if new temperature sample is valid, returns -1 if not
+  float temperature = TempZero.readInternalTemperature();
+  SerialUSB.print("Temp = ");
+  SerialUSB.println(temperature);
+  
+  // Is this a valid temperature?
+  if ((temperature >= 0.1) && (temperature <= 100.0))
+  {
+    // This is a valid temperature
+    // Shift over the sample buffer
+    int i;
+    for (i = 4; i > 0; i--)
+    {
+      g_temperature_samples[i] = g_temperature_samples[i - 1];
+    }
+    
+    // Put the new temperature in the sample buffer
+    g_temperature_samples[0] = temperature;
+    
+    // If the sample buffer is not full, increment the number of samples in it
+    if (g_temperature_samples_count < 5)
+    {
+      g_temperature_samples_count++;
+    }
+    
+    return 0;
+  }
+  else
+  {
+    return -1;
+  }
+}
+
+float getAverageTemperature()
+{
+  // Returns the average temperature if there are 5 valid temperature samples in the buffer, returns NAN otherwise
+  if (g_temperature_samples_count == 5)
+  {
+    // Calculate average
+    float sum = 0;
+    int i;
+    for (i = 0; i < 5; i++)
+    {
+      sum += g_temperature_samples[i];
+    }
+    return sum / 5.0;
+  }
+  else
+  {
+    return NAN;
+  }
+}
+
+void initPacket(Lab3Packet_t* packet)
+{
+  packet->NodeID = NODE_ID;
+  packet->PacketID = 1;
+  packet->Timestamp = 0;
+  packet->AverageTemperature = 0;
+  packet->ErrorType = 0x00;
+}
+
+String packetToString(Lab3Packet_t* packet, bool printThisDeviceTag)
+{
+  String s = String("Node ID = ")
+    + String(packet->NodeID)
+    + String(", Packet ID = ")
+    + String(packet->PacketID)
+    + String(", Timestamp = ")
+    + String(packet->Timestamp)
+    + String(", Average Temperature = ")
+    + String(packet->AverageTemperature)
+    + String(", Error Type = 0x")
+    + String(packet->ErrorType, HEX)
+  ;
+
+  if (printThisDeviceTag && (packet->NodeID == NODE_ID))
+  {
+    s += String(" (this device)");
+  }
+
+  return s;
+}
+
+void printPacket(Lab3Packet_t* packet)
+{
+  SerialUSB.println(packetToString(packet, true));
+}
+
+void constructTxPacket(Lab3Packet_t* packet_out)
+{
+  // Assumes that you have added all the errors to packet->ErrorType before calling this function
+  
+  // Get the average temperature
+  float averageTemperature = getAverageTemperature();
+  if ((averageTemperature != averageTemperature) || (averageTemperature > 60) || (averageTemperature < 0))
+  {
+    packet_out->ErrorType |= ERROR_CODE_INVALID_TEMPERATURE;
+  }
+
+  // Populate packet with data
+  packet_out->Timestamp = millis() - initialMillis;
+  packet_out->AverageTemperature = averageTemperature;
+
+  return;
+}
+
+void setTimerFrequency(int frequencyHz)
+{
+  int compareValue3 = (CPU_HZ / (TIMER_PRESCALER_DIV * frequencyHz)) - 1;
+  TcCount16* TC4p = (TcCount16*) TC4;
+  // Make sure the count is in a proportional position to where it was
+  // to prevent any jitter or disconnect when changing the compare value.
+  TC4p->COUNT.reg = map(TC4p->COUNT.reg, 0, TC4p->CC[0].reg, 0, compareValue3);
+  TC4p->CC[0].reg = compareValue3;
+  while (TC4p->STATUS.bit.SYNCBUSY == 1);
+}
+
+void startTimer(int frequencyHz)
+{
+  REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN |
+  GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TC4_TC5) ;
+  while ( GCLK->STATUS.bit.SYNCBUSY == 1 ); // wait for sync
+  TcCount16* TC4p = (TcCount16*) TC4;
+  TC4p->CTRLA.reg &= ~TC_CTRLA_ENABLE; //Disable timer
+  TC4p->CTRLA.reg |= TC_CTRLA_MODE_COUNT16 | TC_CTRLA_WAVEGEN_MFRQ | TC_CTRLA_PRESCALER_DIV1024;
+  while (TC4p->STATUS.bit.SYNCBUSY == 1); // wait for sync
+  setTimerFrequency(1);
+  TC4p->INTENSET.reg = 0;
+  TC4p->INTENSET.bit.OVF = 1;
+  NVIC_EnableIRQ(TC4_IRQn);
+  TC4p->CTRLA.reg |= TC_CTRLA_ENABLE;
+  while (TC4p->STATUS.bit.SYNCBUSY == 1); // wait for sync
+}
+
+bool IsErrorDetect(Lab3Packet_t* packet)
+{
+  if (packet->ErrorType == 0)
+  {
+    return false;
+  }
+  
+  uint32_t errorCode = 0
+    | ((uint32_t)packet->ErrorType) << 24 // ErrorType occupies bits 31 downto 24
+    | ((uint32_t)packet->NodeID) << 16    // NodeID occupies bits 23 downto 16
+    | ((uint32_t)packet->PacketID)      // PacketID occupies bits 15 downto 0
+  ;
+  
+  // Print an error message
+  printError(errorCode);
+  
+  return true;
+}
+
+void printError(uint32_t errorCode)
+{
+  uint8_t errorType = errorCode >> 24;
+  uint8_t nodeID = errorCode >> 16;
+  uint16_t packetID = errorCode;
+  
+  SerialUSB.print("Error(s) on Node ID ");
+  SerialUSB.print(": ");
+  
+  if (errorType == 0)
+  {
+    SerialUSB.println("No errors");
+    return;
+  }
+  
+  SerialUSB.print("Error(s): ");
+  if (errorType & ERROR_CODE_MISSING_PACKET)
+  {
+    SerialUSB.print("{Missing packet} ");
+  }
+  if (errorType & ERROR_CODE_RECEPTION_FAILURE)
+  {
+    SerialUSB.print("{Reception failure} ");
+  }
+  if (errorType & ERROR_CODE_WDT_EARLY_WARNING)
+  {
+    SerialUSB.print("{WDT early warning} ");
+  }
+  if (errorType & ERROR_CODE_INVALID_TEMPERATURE)
+  {
+    SerialUSB.print("{Invalid temperature} ");
+  }
+  SerialUSB.println("");
+}
+
+/** ISRs **/
+void TC4_Handler()
+{
+  TcCount16* TC4p = (TcCount16*) TC4;
+  // If this interrupt is due to the compare register matching the timer count
+  // we toggle the LED.
+  if (TC4p->INTFLAG.bit.OVF == 1)
+  {
+    TC4p->INTFLAG.bit.OVF = 1;
+    // Write callback here!!!
+    // This handler executes every 1.0 seconds to measure the temperature and add it to the moving average
+    getNewTemperatureSample();
+    // five second block counter
+  }
+}
\ No newline at end of file