diff --git a/sufst-controller/buffer.cpp b/sufst-controller/buffer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b9f37426c5d6fdadd7b2fc4fd96d831cb21b7924
--- /dev/null
+++ b/sufst-controller/buffer.cpp
@@ -0,0 +1,142 @@
+//
+// Created by Sil on 7/19/2019.
+//
+
+#include "buffer.h"
+
+uint8_t cirBufferBegin(cirBuffer_handle cirBuff, void *data, uint16_t len, uint8_t unitSize)
+{
+
+    if (cirBuff == NULL) {
+        return 0;
+    }
+
+    if (data == NULL) {
+        return 0;
+    }
+
+    cirBuff->head = 0;
+    cirBuff->tail = 0;
+    cirBuff->capacity = len;
+    cirBuff->full = 0;
+    cirBuff->data = (uint8_t *) data;
+    cirBuff->unitSize = unitSize;
+
+    return 1;
+}
+
+uint16_t cirBufferAvailable(cirBuffer_handle cirBuff)
+{
+    uint16_t availableForWrite = 0;
+
+    if (!cirBuff->full) {
+        if (cirBuff->head >= cirBuff->tail) {
+            availableForWrite = cirBuff->head - cirBuff->tail;
+        }
+        else {
+            availableForWrite = cirBuff->capacity - cirBuff->tail + cirBuff->head;
+        }
+    }
+    else {
+        availableForWrite = cirBuff->capacity;
+    }
+
+    return availableForWrite;
+}
+
+uint16_t cirBufferAvailableForWrite(cirBuffer_handle cirBuff)
+{
+    return cirBuff->capacity - cirBufferAvailable(cirBuff);
+}
+
+void cirBufferWrite(cirBuffer_handle cirBuff, void *unit)
+{
+    memcpy(&(cirBuff->data[cirBuff->head * cirBuff->unitSize]), unit, cirBuff->unitSize);
+
+    ++cirBuff->head &= (cirBuff->capacity - 1);
+
+    if (cirBuff->head == cirBuff->tail) {
+        cirBuff->full = 1;
+
+        ++cirBuff->tail &= (cirBuff->capacity - 1);
+    }
+}
+
+void cirBufferWriteBytes(cirBuffer_handle cirBuff, void *src, uint16_t len)
+{
+
+    uint16_t usedBytes = cirBufferAvailable(cirBuff);
+
+    if ((usedBytes + len) > cirBuff->capacity) {
+        cirBuff->full = 1;
+    }
+
+    for (uint16_t i = 0; i < len; i++) {
+        cirBuff->data[cirBuff->head] = ((uint8_t *) src)[i];
+
+        ++cirBuff->head &= cirBuff->capacity - 1;
+    }
+
+    if (cirBuff->full) {
+        cirBuff->tail = (cirBuff->tail + (len - (cirBuff->capacity - usedBytes))) & (cirBuff->capacity - 1);
+    }
+}
+
+void *cirBufferExternalWrite(cirBuffer_handle cirBuff)
+{
+    uint8_t *head = &(cirBuff->data[cirBuff->head]);
+
+    ++cirBuff->head &= cirBuff->capacity - 1;
+
+    if (cirBuff->head == cirBuff->tail) {
+        cirBuff->full = 1;
+
+        ++cirBuff->tail &= (cirBuff->capacity - 1);
+    }
+
+    return head;
+}
+
+void *cirBufferExternalRead(cirBuffer_handle cirBuff)
+{
+    uint8_t *tail = &(cirBuff->data[cirBuff->tail]);
+
+    ++cirBuff->tail &= (cirBuff->capacity - 1);
+
+    return (void *) tail;
+}
+
+void cirBufferRead(cirBuffer_handle cirBuff, void *loc)
+{
+
+    memcpy(loc, &(cirBuff->data[cirBuff->tail * cirBuff->unitSize]), cirBuff->unitSize);
+
+    ++cirBuff->tail &= (cirBuff->capacity - 1);
+
+    cirBuff->full = 0;
+
+}
+
+void *cirBufferPeek(cirBuffer_handle cirBuff)
+{
+    return (void *) (cirBuff->data[cirBuff->tail]);
+}
+
+void cirBufferReadBytes(cirBuffer_handle cirBuff, void *loc, uint16_t len)
+{
+    cirBuff->full = 0;
+
+    for (uint16_t i = 0; i < len; i++) {
+        ((uint8_t *) loc)[i] = cirBuff->data[cirBuff->tail];
+
+        ++cirBuff->tail &= cirBuff->capacity - 1;
+    }
+
+}
+
+void cirBufferReset(cirBuffer_handle cirBuff)
+{
+    cirBuff->head = 0;
+    cirBuff->tail = 0;
+    cirBuff->full = 0;
+}
\ No newline at end of file
diff --git a/sufst-controller/buffer.h b/sufst-controller/buffer.h
new file mode 100644
index 0000000000000000000000000000000000000000..d8f886dfb619dfe314927fcad7b24f44b1123307
--- /dev/null
+++ b/sufst-controller/buffer.h
@@ -0,0 +1,48 @@
+//
+// Created by Sil on 7/19/2019.
+//
+
+#ifndef BUFFER_H
+#define BUFFER_H
+
+#include "Arduino.h"
+
+typedef struct CirBuffer
+{
+    uint16_t head;
+    uint16_t tail;
+    uint8_t full;
+    uint8_t *data;
+    uint16_t capacity;
+    uint8_t unitSize;
+};
+
+typedef CirBuffer *cirBuffer_handle;
+
+/*
+ * THE LEN OF THE BUFFER MUST BE A POWER OF 2
+ */
+
+uint8_t cirBufferBegin(cirBuffer_handle cirBuff, void *data, uint16_t len, uint8_t unitSize);
+
+uint16_t cirBufferAvailable(cirBuffer_handle cirBuff);
+
+uint16_t cirBufferAvailableForWrite(cirBuffer_handle cirBuff);
+
+void cirBufferWrite(cirBuffer_handle cirBuff, void *unit);
+
+void cirBufferWriteBytes(cirBuffer_handle cirBuff, void *src, uint16_t len);
+
+void *cirBufferExternalWrite(cirBuffer_handle cirBuff);
+
+void cirBufferRead(cirBuffer_handle cirBuff, void *loc);
+
+void cirBufferReadBytes(cirBuffer_handle cirBuff, void *loc, uint16_t len);
+
+void *cirBufferExternalRead(cirBuffer_handle cirBuff);
+
+void *cirBufferPeek(cirBuffer_handle cirBuff);
+
+void cirBufferReset(cirBuffer_handle cirBuff);
+
+#endif //BUFFER_H
\ No newline at end of file
diff --git a/sufst-controller/can.cpp b/sufst-controller/can.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4cc3e65b3620dcfb528ed77e92740565586e339f
--- /dev/null
+++ b/sufst-controller/can.cpp
@@ -0,0 +1,321 @@
+//
+// Created by Sil on 15/07/2019.
+//
+
+#include "can.h"
+// Watchdog
+#include <avr/wdt.h>
+#include "buffer.h"
+#include <mcp_can.h>
+#include <mcp_can_dfs.h>
+
+#include "ecuCan.h"
+
+// Can instance
+MCP_CAN can;
+
+volatile unsigned long lastCanMsgRecTimeMs = 0;
+
+static CanMsgTemplate canRxBuffer[CAN_RX_BUFFER_LEN];
+
+volatile CirBuffer canRxCirBuffer;
+
+static CanMsgTemplate canTxBuffer[CAN_TX_BUFFER_LEN];
+
+volatile CirBuffer canTxCirBuffer;
+
+extern void ecuCanMsg0x2000(CanMsgTemplate *canMsg);
+
+extern void ecuCanMsg0x2001(CanMsgTemplate *canMsg);
+
+extern void ecuCanMsg0x2002(CanMsgTemplate *canMsg);
+
+extern void ecuCanMsg0x2003(CanMsgTemplate *canMsg);
+
+extern void ecuCanMsg0x2004(CanMsgTemplate *canMsg);
+
+extern void ecuCanMsg0x2005(CanMsgTemplate *canMsg);
+
+extern void ecuCanMsg0x2006(CanMsgTemplate *canMsg);
+
+extern void ecuCanMsg0x2007(CanMsgTemplate *canMsg);
+
+extern void ecuCanMsg0x2008(CanMsgTemplate *canMsg);
+
+extern void ecuCanMsg0x2009(CanMsgTemplate *canMsg);
+
+// ECU can messages function pointers for calling the corresponding function when that msg is seen.
+// Used in function canParseRxMsg.
+void
+(*ecuCanFuncPtrs[10])(CanMsgTemplate *) = {ecuCanMsg0x2000, ecuCanMsg0x2001,
+                                           ecuCanMsg0x2002,
+                                           ecuCanMsg0x2003,
+                                           ecuCanMsg0x2004, ecuCanMsg0x2005,
+                                           ecuCanMsg0x2006,
+                                           ecuCanMsg0x2007, ecuCanMsg0x2008,
+                                           ecuCanMsg0x2009};
+
+uint8_t canBegin()
+{
+    if (!cirBufferBegin(&canRxCirBuffer, canRxBuffer, CAN_RX_BUFFER_LEN, sizeof(CanMsgTemplate))) {
+        return 0;
+    }
+
+    if (!cirBufferBegin(&canTxCirBuffer, canTxBuffer, CAN_TX_BUFFER_LEN, sizeof(CanMsgTemplate))) {
+        return 0;
+    }
+
+    pinMode(CAN_INT_PIN, INPUT_PULLUP);
+
+    // Initialize the Arduino CAN class with slave select pin.
+    can.init_CS(CAN_CS_PIN);
+
+    // Initialize the CAN bus with speed 1000kbps
+    if (can.begin(CAN_1000KBPS) == CAN_FAILINIT) {
+        return 0;
+    }
+
+
+    return 1;
+}
+
+uint8_t canSaveMsg()
+{
+    if (digitalRead(CAN_INT_PIN)) {
+        return 0;
+    }
+
+    union
+    {
+        uint8_t id[4];
+        uint32_t id32;
+    } uid;
+
+    if (can.checkReceive() != CAN_MSGAVAIL) {
+        return 0;
+    }
+
+    CanMsgTemplate canReceivedMsg;
+
+    if (can.readMsgBuf(&(canReceivedMsg.len), canReceivedMsg.data) != CAN_OK) {
+        return 0;
+    }
+
+    uid.id32 = can.getCanId();
+
+    canReceivedMsg.timestamp = millis();
+
+    canReceivedMsg.id = (uid.id[1] << 8) | uid.id[0];
+
+    cirBufferWrite(&canRxCirBuffer, &canReceivedMsg);
+
+#if DEBUG_CAN_RX
+    Serial.print("CAN SV RX: ");
+
+    Serial.print(canReceivedMsg.id, HEX);
+    Serial.print(" : ");
+    for(uint8_t i = 0; i < 7; i++) {
+        Serial.print(canReceivedMsg.data[i]);
+        Serial.print(", ");
+    }
+    Serial.println(canReceivedMsg.data[7]);
+#endif // DEBUG_CAN_RX;
+
+    return 1;
+}
+
+void canAddTxRequest(CanMsgTemplate *canMsg)
+{
+    if (NULL == canMsg) {
+        return;
+    }
+
+#if DEBUG_CAN_TX_REQS
+    Serial.print("CAN TX ADD: ");
+    Serial.print(canMsg.id, HEX);
+    Serial.print(" : ");
+    for(uint8_t i = 0; i < 7; i++) {
+      Serial.print(canMsg.data[i], HEX);
+      Serial.print(", ");
+    }
+    Serial.println(canMsg.data[7], HEX);
+#endif // DEBUG_CAN_TX_REQS
+
+    cirBufferWrite(&canTxCirBuffer, canMsg);
+}
+
+// External interrupt currently only designed to work with atmega 2560
+#if !defined(__AVR_ATmega2560__)
+#error "PLATFORM NOT SUPPORTED CURRENTLY"
+#endif // !defined(atmega2560)
+
+#if CAN_INT_PIN != 2
+#error "CAN_INT_PIN CHANGED FROM 2"
+#endif //CAN_INT_PIN != 2
+
+void canBeginExtInterrupt()
+{
+    noInterrupts();
+
+    // On the mega 2560 pin 2 is external interrupt 4
+    EIMSK &= ~(1 << INT4);
+
+    EICRB &= ~(1 << ISC41);
+    //EICRB &= ~(1 << ISC40);
+
+    EIMSK |= (1 << INT4);
+
+    interrupts();
+}
+
+ISR(INT4_vect) {
+
+    noInterrupts();
+
+    lastCanMsgRecTimeMs = millis();
+
+    canSaveMsg();
+
+    interrupts();
+}
+
+void canParseRxMsg(CanMsgTemplate *canMsg)
+{
+    if ((canMsg->id >= 0x2000) && (canMsg->id <= 0x200A)) {
+        // canMsg->idByte[0] holds the 0x01 in 0x2001 received can msg
+        // We use the sub byte of the can ecu msg to determine the corresonding function
+        // This removes the need for a long if statement giving significant performance boosts
+        (*ecuCanFuncPtrs[canMsg->idByte[0]])(canMsg);
+    }
+}
+
+void canProcessRx()
+{
+    uint8_t retVal = 0;
+
+    noInterrupts();
+    canSaveMsg();
+    interrupts();
+
+    noInterrupts();
+    while (cirBufferAvailable(&canRxCirBuffer) > 0) {
+
+        CanMsgTemplate canMsg;
+
+        cirBufferRead(&canRxCirBuffer, &canMsg);
+
+#if DEBUG_CAN_RX
+        Serial.print("CAN RX: ");
+
+        Serial.print(canMsg.id, HEX);
+        Serial.print(" : ");
+        for(uint8_t i = 0; i < 7; i++) {
+          Serial.print(canMsg.data[i]);
+          Serial.print(", ");
+        }
+        Serial.println(canMsg.data[7]);
+#endif // DEBUG_CAN_RX
+
+#if DEBUG_CAN_RX_MINIMAL
+        Serial.print("CAN RX : ");
+        Serial.println(canMsg.id, HEX);
+#endif // DEBUG_CAN_RX_MINIMAL
+
+        canParseRxMsg(&canMsg);
+
+        // This may seem dumb but this allows any pending interrupts to be serviced safely
+        // just before we start another loop. Otherwise if we stay in this loop too long without
+        // servicing any interrupts we might miss one.
+        interrupts();
+        noInterrupts();
+    }
+    interrupts();
+}
+
+uint8_t canProcessTx()
+{
+
+    uint8_t retVal = 1;
+
+    noInterrupts();
+    while (cirBufferAvailable(&canTxCirBuffer) > 0) {
+
+        CanMsgTemplate canMsg;
+
+        cirBufferRead(&canTxCirBuffer, &canMsg);
+
+#if DEBUG_CAN_TX
+        Serial.print("CAN TX: ");
+        Serial.print(canMsg.id, HEX);
+        Serial.print(" : ");
+        for(uint8_t i = 0; i < (canMsg.len - 1); i++) {
+          Serial.print(canMsg.data[i]);
+          Serial.print(", ");
+        }
+        Serial.println(canMsg.data[canMsg.len - 1]);
+#endif // DEBUG_CAN_TX
+
+#if DEBUG_CAN_TX_MINIMAL
+        Serial.print("CAN TX : ");
+        Serial.println(canMsg.id, HEX);
+#endif // DEBUG_CAN_TX_MINIMAL
+
+        uint8_t canTxWasSuccessful = 0;
+
+        for (uint8_t i = 0; i < CAN_TX_RETRY_LIMIT; i++) {
+            if (can.sendMsgBuf(canMsg.id, 1, canMsg.len,
+                               canMsg.data, true) == CAN_OK) {
+
+                canTxWasSuccessful = 1;
+
+                retVal = 1;
+
+                break;
+            }
+            else {
+                retVal = 0;
+            }
+
+        }
+
+        if (!canTxWasSuccessful) {
+
+#if DEBUG_CAN_TX || DEBUG_CAN_TX_MINIMAL
+            Serial.println("CAN TX: TX FAILED");
+#endif // DEBUG_CAN_TX || DEBUG_CAN_TX_MINIMAL
+
+        }
+
+        // This may seem dumb but this allows any pending interrupts to be serviced safely
+        // just before we start another loop. Otherwise if we stay in this loop too long without
+        // servicing any interrupts we might miss one.
+        interrupts();
+        noInterrupts();
+    }
+
+    interrupts();
+
+    return retVal;
+}
+
+void canNoActivityResetDevice()
+{
+    Serial.println("CAN ERROR : RESETTING DEVICE");
+    Serial.flush();
+
+#if SD_ENABLED
+
+    uint8_t payload[10] = {0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00};
+
+    sdAddToWriteBlocks(payload, 10);
+
+    sdWriteBufferBlocks();
+
+#endif //SD_ENABLED
+
+    // Enabled watchdog timer to reset device as we will not pat it
+    wdt_enable(WDTO_15MS);
+
+    while (true);
+
+}
\ No newline at end of file
diff --git a/sufst-controller/can.h b/sufst-controller/can.h
new file mode 100644
index 0000000000000000000000000000000000000000..4fc862a7e2a9d8c0530414318d27bece992bd68d
--- /dev/null
+++ b/sufst-controller/can.h
@@ -0,0 +1,69 @@
+//
+// Created by Sil on 15/07/2019.
+//
+
+#ifndef SUFST_OPENLOGGER_CONTROLLER_CAN_H
+#define SUFST_OPENLOGGER_CONTROLLER_CAN_H
+
+#include "Arduino.h"
+
+// CAN_INT_PIN MUST BE 2
+#define CAN_INT_PIN 2
+#define CAN_CS_PIN 10
+
+#define CAN_RX_BUFFER_LEN 32
+#define CAN_TX_BUFFER_LEN 32
+
+#define CAN_RESET_DEVICE_ON_TIMEOUT 0
+#define CAN_NO_MSG_TIMEOUT_RESET_MS 10000
+
+#define CAN_TX_RETRY_LIMIT 5
+
+#define DEBUG_CAN_RX 0
+#define DEBUG_CAN_RX_MINIMAL 0
+
+#define DEBUG_CAN_TX 0
+#define DEBUG_CAN_TX_MINIMAL 0
+#define DEBUG_CAN_TX_REQS 0
+
+typedef struct CanMsgTemplate
+{
+    uint32_t timestamp;
+
+    union
+    {
+        uint16_t id;
+        // if id is 0x2001 then idByte[0] is 0x01 and idByte[1] is 0x20
+        // due to the endianness of the system
+        uint8_t idByte[2];
+    };
+
+    uint8_t len;
+
+    union
+    {
+        uint8_t data[8];
+        uint16_t data16[4];
+        uint32_t data32[2];
+        float dataFl[2];
+    };
+
+};
+
+uint8_t canBegin();
+
+uint8_t canSaveMsg();
+
+uint8_t canAddTxRequest(uint16_t id, void *data, uint8_t lenBytes);
+
+void canBeginExtInterrupt();
+
+void canParseRxMsg(CanMsgTemplate *canMsg);
+
+void canProcessRx();
+
+uint8_t canProcessTx();
+
+void canNoActivityResetDevice();
+
+#endif //SUFST_OPENLOGGER_CONTROLLER_CAN_H
diff --git a/sufst-controller/dashController.cpp b/sufst-controller/dashController.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e6cff4489eb072f1da24600c5319b49b271f5037
--- /dev/null
+++ b/sufst-controller/dashController.cpp
@@ -0,0 +1,56 @@
+//
+// Created by Sil on 23/07/2019.
+//
+
+#include "dashController.h"
+
+#define DASH_SERIAL_START_BYTE 0x55
+
+struct DashSerialFrame {
+    uint8_t startByte;
+    uint16_t waterTemp;
+    uint16_t rpm;
+    // uint8_t crc;
+} __attribute__((packed));
+
+uint16_t gRpm = 0;
+uint16_t gWaterTemp = 0;
+
+void dashBegin()
+{
+  Serial2.begin(57600);
+}
+
+void dashEcuSetRpm(uint16_t rpm)
+{
+  
+  Serial.println(rpm);
+  
+    gRpm = rpm;
+}
+
+void dashEcuSetWaterTemp(uint16_t waterTemp)
+{
+    gWaterTemp = waterTemp;
+}
+
+void dashControllerProcess()
+{
+    uint32_t currentMs = millis();
+
+    static uint32_t lastSerialMs = 0;
+
+    if ((currentMs - lastSerialMs) >= DASH_REFRESH_MS)
+    {
+
+      Serial.println(gRpm);
+      
+        DashSerialFrame dashSerialFrame;
+        dashSerialFrame.startByte = DASH_SERIAL_START_BYTE;
+        dashSerialFrame.rpm = gRpm;
+        dashSerialFrame.waterTemp = gWaterTemp;
+
+        Serial2.write((uint8_t *)(&dashSerialFrame), sizeof(DashSerialFrame));
+        Serial2.flush();
+    }
+}
diff --git a/sufst-controller/dashController.h b/sufst-controller/dashController.h
new file mode 100644
index 0000000000000000000000000000000000000000..9cf21a55c94e9eba5e4e42db396023e20ef8f28b
--- /dev/null
+++ b/sufst-controller/dashController.h
@@ -0,0 +1,17 @@
+//
+// Created by Sil on 23/07/2019.
+//
+
+#ifndef DASHCONTROLLER_H
+#define DASHCONTROLLER_H
+
+#include "Arduino.h"
+
+#define DASH_REFRESH_MS 20
+
+void dashBegin();
+void dashEcuSetRpm(uint16_t rpm);
+void dashEcuSetWaterTemp(uint16_t waterTemp);
+void dashControllerProcess();
+
+#endif //DASHCONTROLLER_H
diff --git a/sufst-controller/ecuCan.cpp b/sufst-controller/ecuCan.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..65e3bd34ec26523e467947275bd7e77150819135
--- /dev/null
+++ b/sufst-controller/ecuCan.cpp
@@ -0,0 +1,284 @@
+//
+// Created by Sil on 7/21/2019.
+//
+
+#include "ecuCan.h"
+#include "openLoggerController.h"
+#include "ecuOpenloggerTags.h"
+#include "dashController.h"
+
+struct EcuDataOpenlogger
+{
+    uint8_t startTag;
+    uint32_t timestamp;
+    uint16_t data;
+    uint8_t endTag;
+};
+
+void ecuOpenloggerWrite(EcuDataOpenlogger *ecuDataOpenlogger);
+
+void ecuOpenloggerSave(CanMsgTemplate *canMsgTemplate, uint8_t index);
+
+void ecuOpenloggerWrite(EcuDataOpenlogger &ecuDataOpenlogger)
+{
+    static uint8_t openloggerPayload[8];
+
+    memcpy(openloggerPayload, &(ecuDataOpenlogger.startTag), 1);
+
+    memcpy(&(openloggerPayload[1]), &(ecuDataOpenlogger.timestamp), 4);
+
+    memcpy(&(openloggerPayload[5]), &(ecuDataOpenlogger.data), 2);
+
+    memcpy(&(openloggerPayload[7]), &(ecuDataOpenlogger.endTag), 1);
+
+    openloggerWrite(openloggerPayload, 8);
+}
+
+void ecuOpenloggerSave(CanMsgTemplate *canMsg, uint8_t index)
+{
+    static EcuDataOpenlogger ecuDataOpenlogger;
+
+    uint8_t ecuId = canMsg->idByte[0];
+
+    uint8_t ecuParamterTag = ecuMsgParameterToOpenloggerTag[ecuId][index];
+
+    ecuDataOpenlogger.startTag = ecuParamterTag;
+
+    ecuDataOpenlogger.timestamp = canMsg->timestamp;
+
+    ecuDataOpenlogger.data = canMsg->data16[index];
+
+    ecuDataOpenlogger.endTag = ecuParamterTag;
+
+    ecuOpenloggerWrite(ecuDataOpenlogger);
+}
+
+void ecuCanMsg0x2000(CanMsgTemplate *canMsg)
+{
+#if DEBUG_ECU_CAN
+    Serial.print("ECU : 0x2000 : ");
+    for (uint8_t i = 0; i < 3; i++) {
+        Serial.print(canMsg->data16[i], HEX);
+        Serial.print(" , ");
+    }
+    Serial.println(canMsg->data16[3], HEX);
+#endif //DEBUG_ECU_CAN
+
+#if DEBUG_ECU_CAN_MINIMAL
+    Serial.println("ECU : 0x2000");
+#endif //DEBUG_ECU_CAN_MINIMAL
+
+    uint16_t rpm = canMsg->data16[0];
+    uint16_t tpsPerc = canMsg->data16[1];
+    uint16_t waterTempC = canMsg->data16[2];
+    uint16_t airTempC = canMsg->data16[3];
+
+//    ecuOpenloggerSave(canMsg, 0);
+//    ecuOpenloggerSave(canMsg, 1);
+//    ecuOpenloggerSave(canMsg, 2);
+
+  dashEcuSetRpm(tpsPerc);
+  dashEcuSetWaterTemp(waterTempC);
+}
+
+void ecuCanMsg0x2001(CanMsgTemplate *canMsg)
+{
+#if DEBUG_ECU_CAN
+    Serial.print("ECU : 0x2001 : ");
+    for (uint8_t i = 0; i < 3; i++) {
+        Serial.print(canMsg->data16[i], HEX);
+        Serial.print(" , ");
+    }
+    Serial.println(canMsg->data16[3], HEX);
+#endif //DEBUG_ECU_CAN
+
+#if DEBUG_ECU_CAN_MINIMAL
+    Serial.println("ECU : 0x2001");
+#endif //DEBUG_ECU_CAN_MINIMAL
+
+    uint16_t mainifoldPresKpa = canMsg->data16[0];
+    uint16_t lambdax1000 = canMsg->data16[1];
+    uint16_t speedKphx10 = canMsg->data16[2];
+    uint16_t oilPressKpa = canMsg->data16[3];
+
+//    ecuOpenloggerSave(canMsg, 1);
+}
+
+void ecuCanMsg0x2002(CanMsgTemplate *canMsg)
+{
+#if DEBUG_ECU_CAN
+    Serial.print("ECU : 0x2002 : ");
+    for (uint8_t i = 0; i < 3; i++) {
+        Serial.print(canMsg->data16[i], HEX);
+        Serial.print(" , ");
+    }
+    Serial.println(canMsg->data16[3], HEX);
+#endif //DEBUG_ECU_CAN
+
+#if DEBUG_ECU_CAN_MINIMAL
+    Serial.println("ECU : 0x2002");
+#endif //DEBUG_ECU_CAN_MINIMAL
+
+    uint16_t fuelPressKpa = canMsg->data16[0];
+    uint16_t oilTempC = canMsg->data16[1];
+    uint16_t batteryVx10 = canMsg->data16[2];
+    uint16_t fuelComsumLpHrx10 = canMsg->data16[3];
+
+//    ecuOpenloggerSave(canMsg, 2);
+
+    // Serial.println((float)(batteryVx10 / 10.0));
+}
+
+void ecuCanMsg0x2003(CanMsgTemplate *canMsg)
+{
+#if DEBUG_ECU_CAN
+    Serial.print("ECU : 0x2003 : ");
+    for (uint8_t i = 0; i < 3; i++) {
+        Serial.print(canMsg->data16[i], HEX);
+        Serial.print(" , ");
+    }
+    Serial.println(canMsg->data16[3], HEX);
+#endif //DEBUG_ECU_CAN
+
+#if DEBUG_ECU_CAN_MINIMAL
+    Serial.println("ECU : 0x2003");
+#endif //DEBUG_ECU_CAN_MINIMAL
+
+    uint16_t currentGear = canMsg->data16[0];
+    uint16_t advanceDegx10 = canMsg->data16[1];
+    uint16_t injectionTimeMsx100 = canMsg->data16[2];
+    uint16_t fuelComsumLp100kMx10 = canMsg->data16[3];
+
+//    ecuOpenloggerSave(canMsg, 1);
+}
+
+void ecuCanMsg0x2004(CanMsgTemplate *canMsg)
+{
+#if DEBUG_ECU_CAN
+    Serial.print("ECU : 0x2004 : ");
+    for (uint8_t i = 0; i < 3; i++) {
+        Serial.print(canMsg->data16[i], HEX);
+        Serial.print(" , ");
+    }
+    Serial.println(canMsg->data16[3], HEX);
+#endif //DEBUG_ECU_CAN
+
+#if DEBUG_ECU_CAN_MINIMAL
+    Serial.println("ECU : 0x2004");
+#endif //DEBUG_ECU_CAN_MINIMAL
+
+    uint16_t ana1mV = canMsg->data16[0];
+    uint16_t ana2mV = canMsg->data16[1];
+    uint16_t ana3mV = canMsg->data16[2];
+    uint16_t camAdvanceDegx10 = canMsg->data16[3];
+
+//    ecuOpenloggerSave(canMsg, 3);
+}
+
+void ecuCanMsg0x2005(CanMsgTemplate *canMsg)
+{
+#if DEBUG_ECU_CAN
+    Serial.print("ECU : 0x2005 : ");
+    for (uint8_t i = 0; i < 3; i++) {
+        Serial.print(canMsg->data16[i], HEX);
+        Serial.print(" , ");
+    }
+    Serial.println(canMsg->data16[3], HEX);
+#endif //DEBUG_ECU_CAN
+
+#if DEBUG_ECU_CAN_MINIMAL
+    Serial.println("ECU : 0x2005");
+#endif //DEBUG_ECU_CAN_MINIMAL
+
+    uint16_t camTargDegx10 = canMsg->data16[0];
+    uint16_t camPwmPercx10 = canMsg->data16[1];
+    uint16_t crankErrorsNr = canMsg->data16[2];
+    uint16_t camErrorsNr = canMsg->data16[3];
+
+//    ecuOpenloggerSave(canMsg, 0);
+}
+
+void ecuCanMsg0x2006(CanMsgTemplate *canMsg)
+{
+#if DEBUG_ECU_CAN
+    Serial.print("ECU : 0x2006 : ");
+    for (uint8_t i = 0; i < 3; i++) {
+        Serial.print(canMsg->data16[i], HEX);
+        Serial.print(" , ");
+    }
+    Serial.println(canMsg->data16[3], HEX);
+#endif //DEBUG_ECU_CAN
+
+#if DEBUG_ECU_CAN_MINIMAL
+    Serial.println("ECU : 0x2006");
+#endif //DEBUG_ECU_CAN_MINIMAL
+
+    uint16_t cam2AdvDegx10 = canMsg->data16[0];
+    uint16_t cam2TargDegx10 = canMsg->data16[1];
+    uint16_t cam2PwmPercx10 = canMsg->data16[2];
+    uint16_t external5VmV = canMsg->data16[3];
+}
+
+void ecuCanMsg0x2007(CanMsgTemplate *canMsg)
+{
+#if DEBUG_ECU_CAN
+    Serial.print("ECU : 0x2007 : ");
+    for (uint8_t i = 0; i < 3; i++) {
+        Serial.print(canMsg->data16[i], HEX);
+        Serial.print(" , ");
+    }
+    Serial.println(canMsg->data16[3], HEX);
+#endif //DEBUG_ECU_CAN
+
+#if DEBUG_ECU_CAN_MINIMAL
+    Serial.println("ECU : 0x2007");
+#endif //DEBUG_ECU_CAN_MINIMAL
+
+    uint16_t injDutyCyclePerc = canMsg->data16[0];
+    uint16_t lambdaPidTargPercx10 = canMsg->data16[1];
+    uint16_t lambdaPidAdjPercx10 = canMsg->data16[2];
+    uint16_t ecuSwitchesBitField = canMsg->data16[3];
+//
+//    ecuOpenloggerSave(canMsg, 0);
+//    ecuOpenloggerSave(canMsg, 1);
+//    ecuOpenloggerSave(canMsg, 2);
+}
+
+void ecuCanMsg0x2008(CanMsgTemplate *canMsg)
+{
+#if DEBUG_ECU_CAN
+    Serial.print("ECU : 0x2008 : ");
+    for (uint8_t i = 0; i < 3; i++) {
+        Serial.print(canMsg->data16[i], HEX);
+        Serial.print(" , ");
+    }
+    Serial.println(canMsg->data16[3], HEX);
+#endif //DEBUG_ECU_CAN
+
+#if DEBUG_ECU_CAN_MINIMAL
+    Serial.println("ECU : 0x2008");
+#endif //DEBUG_ECU_CAN_MINIMAL
+
+    uint16_t rdSpeedKphx10 = canMsg->data16[0];
+    uint16_t rUdSpeedKphx10 = canMsg->data16[1];
+    uint16_t ldSpeedKphx10 = canMsg->data16[2];
+    uint16_t lUdSpeedKphx10 = canMsg->data16[3];
+}
+
+void ecuCanMsg0x2009(CanMsgTemplate *canMsg)
+{
+#if DEBUG_ECU_CAN
+    Serial.print("ECU : 0x2009 : ");
+    for (uint8_t i = 0; i < 3; i++) {
+        Serial.print(canMsg->data16[i], HEX);
+        Serial.print(" , ");
+    }
+    Serial.println(canMsg->data16[3], HEX);
+#endif //DEBUG_ECU_CAN
+
+#if DEBUG_ECU_CAN_MINIMAL
+    Serial.println("ECU : 0x2008");
+#endif //DEBUG_ECU_CAN_MINIMAL
+
+    // uint16_t rightLambdax1000 = canMsg->data16[0];
+}
diff --git a/sufst-controller/ecuCan.h b/sufst-controller/ecuCan.h
new file mode 100644
index 0000000000000000000000000000000000000000..3e0f98577cbe551131f608da0cd846dffa8ffbb4
--- /dev/null
+++ b/sufst-controller/ecuCan.h
@@ -0,0 +1,38 @@
+//
+// Created by Sil on 7/21/2019.
+//
+
+#ifndef ECUCAN_H
+#define ECUCAN_H
+
+#include "Arduino.h"
+#include "can.h"
+
+/*
+ * These are called from can.cpp when their corresponding can msg id is seen.
+ * So functions name *must* ecuCanMsg0x200N.
+ *
+ */
+
+void ecuCanMsg0x2000(CanMsgTemplate *canMsg);
+
+void ecuCanMsg0x2001(CanMsgTemplate *canMsg);
+
+void ecuCanMsg0x2002(CanMsgTemplate *canMsg);
+
+void ecuCanMsg0x2003(CanMsgTemplate *canMsg);
+
+void ecuCanMsg0x2004(CanMsgTemplate *canMsg);
+
+void ecuCanMsg0x2005(CanMsgTemplate *canMsg);
+
+void ecuCanMsg0x2006(CanMsgTemplate *canMsg);
+
+void ecuCanMsg0x2007(CanMsgTemplate *canMsg);
+
+void ecuCanMsg0x2008(CanMsgTemplate *canMsg);
+
+void ecuCanMsg0x2009(CanMsgTemplate *canMsg);
+
+
+#endif //ECUCAN_H
diff --git a/sufst-controller/ecuOpenloggerTags.h b/sufst-controller/ecuOpenloggerTags.h
new file mode 100644
index 0000000000000000000000000000000000000000..c0bc6d3e3810f6e4963268e2c2af16033d2d2f84
--- /dev/null
+++ b/sufst-controller/ecuOpenloggerTags.h
@@ -0,0 +1,19 @@
+//
+// Created by Sil on 7/21/2019.
+//
+
+#ifndef ECUOPENLOGGERTAGS_H
+#define ECUOPENLOGGERTAGS_H
+
+uint8_t ecuMsgParameterToOpenloggerTag[8][4] = {
+    {1, 2, 3, 4},
+    {5, 6, 7, 8},
+    {9, 10, 11, 12},
+    {13, 14, 15, 16},
+    {17, 18, 19, 20},
+    {21, 22, 23, 24},
+    {25, 26, 27, 28},
+    {29, 30, 31, 32}
+};
+
+#endif //ECUOPENLOGGERTAGS_H
diff --git a/sufst-controller/openLoggerController.cpp b/sufst-controller/openLoggerController.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d530acb5318f4ff7451e7cebddbbcddf383edad4
--- /dev/null
+++ b/sufst-controller/openLoggerController.cpp
@@ -0,0 +1,296 @@
+//
+// Created by Sil on 14/07/2019.
+//
+
+#include "openLoggerController.h"
+#include "buffer.h"
+
+enum
+{
+    uartStartByte = 's',
+    uartStopByte = 'f'
+};
+
+enum
+{
+    uartTypeData = 0,
+    uartTypeCommand,
+    uartTypeInfo
+};
+
+enum
+{
+    uartCommandStartWrites = 0,
+    uartCommandStopWrites
+};
+
+enum
+{
+    uartInfoAlreadyRunning = 0,
+    uartInfoAlreadyIdle,
+    uartInfoWritesStarted,
+    uartInfoWritesStartError,
+    uartInfoStopped,
+    uartInfoStopError,
+    uartInfoWriteError,
+    uartInfoWritesNotStarted
+};
+
+typedef enum
+{
+    idle = 0,
+    running,
+    failedToStart,
+    failedToStop,
+    failToWrite
+} OpenloggerState;
+
+typedef enum
+{
+    checkingForStart = 0,
+    checkingForType,
+    checkingForInfo
+} OpenloggerUartRxState;
+
+static uint8_t openloggerTxDataBuffer[OPENLOGGER_TX_BUFFER_LEN];
+
+static CirBuffer openloggerTxDataCirBuffer;
+
+static uint8_t openloggerTxCommandBuffer[OPENLOGGER_TX_COMMAND_BUFFER_LEN];
+
+static CirBuffer openloggerTxCommandCirBuffer;
+
+OpenloggerState openloggerState = idle;
+
+OpenloggerUartRxState openloggerUartRxState = checkingForStart;
+
+static bool uartCurrentlyTransmitting = false;
+
+void openloggerIdle();
+
+void openloggerRunning();
+
+void openloggerFailedToStart();
+
+void openloggerFailedToStop();
+
+void openloggerFailedToWrite();
+
+void openloggerRxCheckingForStart();
+
+void openloggerRxCheckingForType();
+
+void openloggerRxCheckingForInfo();
+
+/*
+ * openloggerState_ptrs correspond to INFO packets, so e.g if OPENLOGGER_INFO_WRITES_STARTED (0x02) is seen then
+ * [2] is called which is openloggerRunning.
+ */
+
+OpenloggerState rxInfoTypeToStateLookup[8] = {running, idle, running, failedToStart, idle, failedToStop,
+                                              failToWrite, idle};
+
+void (*openloggerState_ptrs[8])(void) = {openloggerIdle, openloggerRunning,
+                                         openloggerFailedToStart, openloggerFailedToStop,
+                                         openloggerFailedToWrite};
+
+void (*openloggerUartRxState_ptrs[3])(void) = {openloggerRxCheckingForStart, openloggerRxCheckingForType,
+                                               openloggerRxCheckingForInfo};
+
+void serialEvent1()
+{
+    openloggerUartRxState_ptrs[openloggerUartRxState]();
+}
+
+void openloggerRxCheckingForStart()
+{
+
+#if DEBUG_OPENLOGGER
+    Serial.print("OPLOG: CHECKING START ");
+    Serial.println(Serial1.peek());
+#endif // DEBUG_OPENLOGGER
+
+    if (Serial1.read() == uartStartByte) {
+        openloggerUartRxState = checkingForType;
+    }
+}
+
+void openloggerRxCheckingForType()
+{
+#if DEBUG_OPENLOGGER
+    Serial.print("OPLOG: CHECKING FOR TYPE ");
+    Serial.println(Serial1.peek());
+#endif // DEBUG_OPENLOGGER
+
+    if (Serial1.read() == uartTypeInfo) {
+        openloggerUartRxState = checkingForInfo;
+    }
+    else {
+        openloggerUartRxState = checkingForStart;
+    }
+}
+
+void openloggerRxCheckingForInfo()
+{
+#if DEBUG_OPENLOGGER
+    Serial.print("OPLOG: CHECKING FOR INFO ");
+    Serial.println(Serial1.peek());
+#endif // DEBUG_OPENLOGGER
+    /*
+     * The value received from the openlogger dictates what state we are in.
+     */
+    openloggerState = rxInfoTypeToStateLookup[Serial1.read()];
+
+    openloggerUartRxState = checkingForStart;
+
+}
+
+void openloggerIdle()
+{
+
+}
+
+void openloggerRunning()
+{
+    /*
+     * The uart Tx buffer has a capacity of 64 bytes, if we try to write more than this then Serial.write becomes
+     * blocking, so we want to avoid this at all costs.
+     *
+     */
+
+    static uint8_t uartTxPayload[64];
+
+    static uint16_t uartAmountToTransmit = 0;
+
+    static uint32_t uartLastTransmissionMs = millis();
+
+    uint32_t uartCurrentMs = millis();
+
+    uint16_t uartTxDataAmount = cirBufferAvailable(&openloggerTxDataCirBuffer);
+
+    if (uartTxDataAmount > 0) {
+
+        if ((uartCurrentMs - uartLastTransmissionMs) >= 5) {
+            if (Serial1.availableForWrite() >= 35) {
+
+                uint8_t uartTxBytes = (uartTxDataAmount >= 32) ? 32 : uartTxDataAmount;
+
+                Serial1.write(uartStartByte);
+                Serial1.write(uartTypeData);
+                Serial1.write(uartTxBytes);
+
+                cirBufferReadBytes(&openloggerTxDataCirBuffer, uartTxPayload, uartTxBytes);
+
+                Serial1.write(uartTxPayload, uartTxBytes);
+
+                uartLastTransmissionMs = millis();
+            }
+        }
+    }
+}
+
+void openloggerFailedToStart()
+{
+#if DEBUG_OPENLOGGER
+    Serial.println("OPLOG: FAILED TO START");
+#endif // DEBUG_OPENLOGGER
+
+    openloggerState = idle;
+}
+
+void openloggerFailedToStop()
+{
+#if DEBUG_OPENLOGGER
+    Serial.println("OPLOG: FAILED TO STOP");
+#endif // DEBUG_OPENLOGGER
+
+    openloggerState = idle;
+}
+
+void openloggerFailedToWrite()
+{
+#if DEBUG_OPENLOGGER
+    Serial.println("OPLOG: FAILED TO WRITE");
+#endif // DEBUG_OPENLOGGER
+
+    openloggerState = idle;
+}
+
+void openloggerBegin(uint32_t serialBaud)
+{
+
+    Serial1.begin(serialBaud);
+
+    cirBufferBegin(&openloggerTxDataCirBuffer,
+                   openloggerTxDataBuffer,
+                   OPENLOGGER_TX_BUFFER_LEN,
+                   sizeof(uint8_t));
+
+    cirBufferBegin(&openloggerTxCommandCirBuffer,
+                   openloggerTxCommandBuffer,
+                   OPENLOGGER_TX_COMMAND_BUFFER_LEN,
+                   sizeof(uint8_t));
+
+#if DEBUG_OPENLOGGER
+    Serial.println("OPLOG: BEGIN FINISHED");
+#endif // DEBUG_OPENLOGGER
+}
+
+void openloggerBeginWrites()
+{
+    openloggerWriteCommand(uartCommandStartWrites);
+
+#if DEBUG_OPENLOGGER
+    Serial.println("OPLOG: OPLOG STARTING WRITES");
+#endif // DEBUG_OPENLOGGER
+}
+
+void openloggerStopWrites()
+{
+    openloggerWriteCommand(uartCommandStopWrites);
+
+#if DEBUG_OPENLOGGER
+    Serial.println("OPLOG: OPLOG STOPPING WRITES");
+#endif // DEBUG_OPENLOGGER
+}
+
+void openloggerProcess()
+{
+    if (!uartCurrentlyTransmitting) {
+        if (cirBufferAvailable(&openloggerTxCommandCirBuffer) > 0) {
+
+#if DEBUG_OPENLOGGER
+            Serial.print("OPLOG: COMMAND ");
+            Serial.println(*((uint8_t *)cirBufferPeek(&openloggerTxCommandCirBuffer)), HEX);
+#endif // DEBUG_OPENLOGGER
+
+            Serial1.write(uartStartByte);
+            Serial1.write(uartTypeCommand);
+            Serial1.write(*((uint8_t *) cirBufferExternalRead(&openloggerTxCommandCirBuffer)));
+            Serial1.flush();
+        }
+    }
+
+    openloggerState_ptrs[openloggerState]();
+}
+
+void openloggerWrite(void *pPayload, uint8_t payloadLen)
+{
+
+    if (openloggerState == running) {
+        cirBufferWriteBytes(&openloggerTxDataCirBuffer, pPayload, payloadLen);
+    }
+}
+
+void openloggerWriteCommand(uint8_t command)
+{
+
+#if DEBUG_OPENLOGGER
+    Serial.print("OPLOG: APPENDING COMMAND ");
+    Serial.println(command, HEX);
+    Serial.flush();
+#endif // DEBUG_OPENLOGGER
+
+    cirBufferWrite(&openloggerTxCommandCirBuffer, &command);
+
+}
diff --git a/sufst-controller/openLoggerController.h b/sufst-controller/openLoggerController.h
new file mode 100644
index 0000000000000000000000000000000000000000..fd48ca605dfe165b8f0d885d9a4f2f7005fef928
--- /dev/null
+++ b/sufst-controller/openLoggerController.h
@@ -0,0 +1,49 @@
+//
+// Created by Sil on 14/07/2019.
+//
+
+#ifndef SUFST_OPENLOGGER_INO_SDCARDMASTER_H
+#define SUFST_OPENLOGGER_INO_SDCARDMASTER_H
+
+#include "Arduino.h"
+
+#define DEBUG_OPENLOGGER 0
+
+#define OPENLOGGER_TX_BUFFER_LEN 1024
+#define OPENLOGGER_TX_COMMAND_BUFFER_LEN 8
+
+/*
+ * The serial port is hard coded to serial1 so ensure openlogger is connected to serial1.
+ *
+ * There are three types of transactions: save this data, do this command or heres some info.
+ *
+ * STRUCTURE OF A VALID SD CARD UART SAVE THIS DATA TRANSACTION
+ *
+ * START BYTE | DATA TYPE | LENGTH BYTE | DATA BYTES | STOP BYTE
+ *
+ * STRUCTURE OF A VALID COMMAND TRANSACTION
+ *
+ * START BYTE | COMMAND TYPE | COMMAND BYTE | STOP BYTE
+ *
+ * STUCTURE OF VALID INFO TRANSACTION
+ *
+ * START BYTE | INFO TYPE | INFO BYTE | STOP BYTE
+ *
+ * If no rx has been received for more than 500ms the rx state is reset to checking for start byte
+ * This ensures the open logger does not get stuck in the middle of a transaction if the master stops sending data
+ *
+ */
+void openloggerBegin(uint32_t serialBaud);
+
+void openloggerBeginWrites();
+
+void openloggerStopWrites();
+
+void openloggerProcess();
+
+void openloggerWrite(void *pPayload, uint8_t payloadLen);
+
+void openloggerWriteCommand(uint8_t command);
+
+
+#endif //SUFST_OPENLOGGER_INO_SDCARDMASTER_H
diff --git a/sufst-controller/sufst-controller.ino b/sufst-controller/sufst-controller.ino
new file mode 100644
index 0000000000000000000000000000000000000000..53ba526bb1e25c95febe66fe3a8133bad34dc92e
--- /dev/null
+++ b/sufst-controller/sufst-controller.ino
@@ -0,0 +1,68 @@
+#define DEBUG_ECU_CAN 0
+#define DEBUG_ECU_CAN_MINIMAL 0
+
+#include "openLoggerController.h"
+#include "ecuCan.h"
+#include "can.h"
+#include "dashController.h"
+
+// Watchdog
+#include <avr/wdt.h>
+
+#define OPENLOGGER_SERIAL_BUAD_RATE 115200
+
+uint8_t arduinoProcessSerial();
+
+void setup()
+{
+    // put your setup code here, to run once:
+
+
+    // Serial port for host pc communications
+    Serial.begin(115200);
+
+    Serial.println("BEGIN");
+
+    dashBegin();
+
+    // Openlogger runs on uart port TX1 RX1 (Serial1)
+    //openloggerBegin(OPENLOGGER_SERIAL_BUAD_RATE);
+
+    canBegin();
+}
+
+void loop()
+{
+    // put your main code here, to run repeatedly:
+
+    /*
+   * ALL PROCESSES *MUST* BE NON BLOCKING
+   */
+    //wdt_reset();
+
+    //openloggerProcess();
+    //arduinoProcessSerial();
+    canProcessRx();
+    dashControllerProcess();
+    //canProcessTx();
+}
+
+uint8_t arduinoProcessSerial()
+{
+    if (Serial.available()) {
+
+        uint8_t serialByte = Serial.read();
+
+        switch (serialByte) {
+            case 'g':openloggerBeginWrites();
+
+                break;
+
+            case 's':openloggerStopWrites();
+
+                break;
+        }
+    }
+
+    return 1;
+}
diff --git a/sufst-dash/sufst-dash.ino b/sufst-dash/sufst-dash.ino
new file mode 100644
index 0000000000000000000000000000000000000000..c562714f111f9a9f6d2dc613c6b50a896835d981
--- /dev/null
+++ b/sufst-dash/sufst-dash.ino
@@ -0,0 +1,265 @@
+#define DASH_RPM_LED_RED_1_PIN 2
+#define DASH_RPM_LED_RED_2_PIN 4
+#define DASH_RPM_LED_GREEN_1_PIN 6
+#define DASH_RPM_LED_GREEN_2_PIN 8
+#define DASH_RPM_LED_GREEN_3_PIN 10
+#define DASH_RPM_LED_1_BLUE_PIN 3
+#define DASH_RPM_LED_2_BLUE_PIN 5
+#define DASH_RPM_LED_3_BLUE_PIN 7
+#define DASH_RPM_LED_4_BLUE_PIN 9
+#define DASH_RPM_LED_5_BLUE_PIN 11
+
+#define DASH_RPM_SHIFT_FLASH_SWITCH_MS 100
+
+#define DASH_RPM_LED_ACTIVE LOW
+#define DASH_RPM_LED_NON_ACTIVE HIGH
+
+#define DASH_WATER_LED_PIN 12
+#define DASH_WATER_LED_ACTIVE LOW
+#define DASH_WATER_LED_NON_ACTIVE HIGH
+
+#define DASH_SERIAL_START_BYTE 0x55
+
+#define DASH_RPM_THRESHOLD_1 30
+#define DASH_RPM_THRESHOLD_2 40
+#define DASH_RPM_THRESHOLD_3 50
+#define DASH_RPM_THRESHOLD_4 60
+#define DASH_RPM_THRESHOLD_5 70
+#define DASH_RPM_THRESHOLD_SHIFT 80
+
+#define DASH_WATER_C_THRESHOLD 100
+ 
+void dashRpmLedsBegin();
+void dashRpmLedsProcess();
+
+bool gShiftFlashState = true;
+
+struct DashSerialFrame {
+  uint8_t startByte;
+  uint16_t waterTemp;
+  uint16_t rpm;
+  // uint8_t crc; 
+} __attribute__((packed));
+
+uint16_t gDashRpm = 0;
+uint16_t gDashWaterTemp = 0;
+
+void setup() {
+  // put your setup code here, to run once:
+
+  Serial.begin(57600);
+
+  dashRpmLedsBegin();
+  dashWaterLedBegin();
+}
+
+void loop() {
+  // put your main code here, to run repeatedly:
+
+  serialProcess();
+  dashRpmLedsProcess();
+  dashWaterLedProcess();
+}
+
+void dashWaterLedBegin()
+{
+  pinMode(DASH_WATER_LED_PIN, OUTPUT);
+
+  digitalWrite(DASH_WATER_LED_PIN, DASH_WATER_LED_NON_ACTIVE);
+}
+
+void dashRpmLedsBegin()
+{
+  pinMode(DASH_RPM_LED_RED_1_PIN, OUTPUT);
+  pinMode(DASH_RPM_LED_RED_2_PIN, OUTPUT);
+  pinMode(DASH_RPM_LED_GREEN_1_PIN, OUTPUT);
+  pinMode(DASH_RPM_LED_GREEN_2_PIN, OUTPUT);
+  pinMode(DASH_RPM_LED_GREEN_3_PIN, OUTPUT);
+  pinMode(DASH_RPM_LED_1_BLUE_PIN, OUTPUT);
+  pinMode(DASH_RPM_LED_2_BLUE_PIN, OUTPUT);
+  pinMode(DASH_RPM_LED_3_BLUE_PIN, OUTPUT);
+  pinMode(DASH_RPM_LED_4_BLUE_PIN, OUTPUT);
+  pinMode(DASH_RPM_LED_5_BLUE_PIN, OUTPUT);
+  
+  digitalWrite(DASH_RPM_LED_RED_1_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_RED_2_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_1_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_2_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_3_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_1_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_2_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_3_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_4_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_5_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);  
+}
+
+void dashRpmLedsThreshold0()
+{
+  digitalWrite(DASH_RPM_LED_RED_1_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_RED_2_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_1_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_2_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_3_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_1_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_2_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_3_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_4_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_5_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);  
+}
+
+void dashRpmLedsThreshold1()
+{
+  digitalWrite(DASH_RPM_LED_RED_1_PIN, DASH_RPM_LED_ACTIVE);
+  digitalWrite(DASH_RPM_LED_RED_2_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_1_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_2_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_3_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_1_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_2_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_3_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_4_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_5_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE); 
+}
+
+void dashRpmLedsThreshold2()
+{
+  digitalWrite(DASH_RPM_LED_RED_1_PIN, DASH_RPM_LED_ACTIVE);
+  digitalWrite(DASH_RPM_LED_RED_2_PIN, DASH_RPM_LED_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_1_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_2_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_3_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_1_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_2_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_3_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_4_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_5_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE); 
+}
+
+void dashRpmLedsThreshold3()
+{
+  digitalWrite(DASH_RPM_LED_RED_1_PIN, DASH_RPM_LED_ACTIVE);
+  digitalWrite(DASH_RPM_LED_RED_2_PIN, DASH_RPM_LED_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_1_PIN, DASH_RPM_LED_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_2_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_3_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_1_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_2_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_3_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_4_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_5_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE); 
+}
+
+void dashRpmLedsThreshold4()
+{
+  digitalWrite(DASH_RPM_LED_RED_1_PIN, DASH_RPM_LED_ACTIVE);
+  digitalWrite(DASH_RPM_LED_RED_2_PIN, DASH_RPM_LED_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_1_PIN, DASH_RPM_LED_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_2_PIN, DASH_RPM_LED_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_3_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_1_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_2_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_3_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_4_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_5_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE); 
+}
+
+void dashRpmLedsThreshold5()
+{
+  digitalWrite(DASH_RPM_LED_RED_1_PIN, DASH_RPM_LED_ACTIVE);
+  digitalWrite(DASH_RPM_LED_RED_2_PIN, DASH_RPM_LED_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_1_PIN, DASH_RPM_LED_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_2_PIN, DASH_RPM_LED_ACTIVE);
+  digitalWrite(DASH_RPM_LED_GREEN_3_PIN, DASH_RPM_LED_ACTIVE);
+  digitalWrite(DASH_RPM_LED_1_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_2_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_3_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_4_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+  digitalWrite(DASH_RPM_LED_5_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE); 
+}
+
+void dashRpmLedsThresholdShift()
+{
+  uint32_t currentMs = millis();
+
+  static uint32_t dashShiftLastFlashMs = 0;
+
+  if ((currentMs - dashShiftLastFlashMs) >= DASH_RPM_SHIFT_FLASH_SWITCH_MS) {
+    gShiftFlashState = !gShiftFlashState;
+
+    dashShiftLastFlashMs = millis();
+  }
+
+  if (gShiftFlashState) {
+    digitalWrite(DASH_RPM_LED_RED_1_PIN, DASH_RPM_LED_NON_ACTIVE);
+    digitalWrite(DASH_RPM_LED_RED_2_PIN, DASH_RPM_LED_NON_ACTIVE);
+    digitalWrite(DASH_RPM_LED_GREEN_1_PIN, DASH_RPM_LED_NON_ACTIVE);
+    digitalWrite(DASH_RPM_LED_GREEN_2_PIN, DASH_RPM_LED_NON_ACTIVE);
+    digitalWrite(DASH_RPM_LED_GREEN_3_PIN, DASH_RPM_LED_NON_ACTIVE);
+    digitalWrite(DASH_RPM_LED_1_BLUE_PIN, DASH_RPM_LED_ACTIVE);
+    digitalWrite(DASH_RPM_LED_2_BLUE_PIN, DASH_RPM_LED_ACTIVE);
+    digitalWrite(DASH_RPM_LED_3_BLUE_PIN, DASH_RPM_LED_ACTIVE);
+    digitalWrite(DASH_RPM_LED_4_BLUE_PIN, DASH_RPM_LED_ACTIVE);
+    digitalWrite(DASH_RPM_LED_5_BLUE_PIN, DASH_RPM_LED_ACTIVE); 
+  }
+  else {
+    digitalWrite(DASH_RPM_LED_RED_1_PIN, DASH_RPM_LED_NON_ACTIVE);
+    digitalWrite(DASH_RPM_LED_RED_2_PIN, DASH_RPM_LED_NON_ACTIVE);
+    digitalWrite(DASH_RPM_LED_GREEN_1_PIN, DASH_RPM_LED_NON_ACTIVE);
+    digitalWrite(DASH_RPM_LED_GREEN_2_PIN, DASH_RPM_LED_NON_ACTIVE);
+    digitalWrite(DASH_RPM_LED_GREEN_3_PIN, DASH_RPM_LED_NON_ACTIVE);
+    digitalWrite(DASH_RPM_LED_1_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+    digitalWrite(DASH_RPM_LED_2_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+    digitalWrite(DASH_RPM_LED_3_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+    digitalWrite(DASH_RPM_LED_4_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE);
+    digitalWrite(DASH_RPM_LED_5_BLUE_PIN, DASH_RPM_LED_NON_ACTIVE); 
+  }
+}
+
+void dashRpmLedsProcess() 
+{
+  if (gDashRpm < DASH_RPM_THRESHOLD_1) {
+    dashRpmLedsThreshold0();
+  }
+  else if (gDashRpm < DASH_RPM_THRESHOLD_2) {
+    dashRpmLedsThreshold1();
+  }
+  else if (gDashRpm < DASH_RPM_THRESHOLD_3) {
+    dashRpmLedsThreshold2();
+  }
+  else if (gDashRpm < DASH_RPM_THRESHOLD_4) {
+    dashRpmLedsThreshold3();
+  }
+  else if (gDashRpm < DASH_RPM_THRESHOLD_5) {
+    dashRpmLedsThreshold4();
+  }
+  else if (gDashRpm < DASH_RPM_THRESHOLD_SHIFT) {
+    dashRpmLedsThreshold5();
+  }
+  else {
+    dashRpmLedsThresholdShift();
+  }
+}
+
+void dashWaterLedProcess()
+{
+  if (gDashWaterTemp >= DASH_WATER_C_THRESHOLD) {
+    digitalWrite(DASH_WATER_LED_PIN, DASH_WATER_LED_ACTIVE);
+  }
+  else {
+    digitalWrite(DASH_WATER_LED_PIN, DASH_WATER_LED_NON_ACTIVE);
+  }
+}
+
+void serialProcess()
+{ 
+  DashSerialFrame dashSerialFrame;
+
+  if (Serial.available() >= sizeof(DashSerialFrame)) {
+    Serial.readBytes((uint8_t *)(&dashSerialFrame), sizeof(DashSerialFrame));
+
+    if (dashSerialFrame.startByte == DASH_SERIAL_START_BYTE) {
+      gDashRpm = dashSerialFrame.rpm;
+      gDashWaterTemp = dashSerialFrame.waterTemp;
+    }
+  }
+}