diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000000000000000000000000000000000000..827c89b435020ccdca9a15eb06a972ac5947c742
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,13 @@
+{
+    BasedOnStyle: Google,
+    AccessModifierOffset: -4,
+    AlignConsecutiveAssignments: true,
+    AllowShortIfStatementsOnASingleLine: false,
+    AllowShortLoopsOnASingleLine: false,
+    BreakBeforeBraces: Allman,
+    ColumnLimit: 80,
+    ConstructorInitializerAllOnOneLineOrOnePerLine: false,
+    IndentCaseLabels: true,
+    IndentWidth: 4,
+    KeepEmptyLinesAtTheStartOfBlocks: true,
+}
diff --git a/.gitignore b/.gitignore
index 259148fa18f9fb7ef58563f4ff15fc7b172339fb..45f7d5708d689f77c2f325da11361bb2cbfbaf8e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,7 @@
 *.exe
 *.out
 *.app
+
+# ide folder & virtual environments
+.venv/
+.vscode
\ No newline at end of file
diff --git a/utils/BitPacker.h b/bitpacking/BitPacker.h
similarity index 100%
rename from utils/BitPacker.h
rename to bitpacking/BitPacker.h
diff --git a/bitpacking/generator.py b/bitpacking/generator.py
new file mode 100644
index 0000000000000000000000000000000000000000..d79917b8b6e80c3e8af68d9d212700ffef7be44b
--- /dev/null
+++ b/bitpacking/generator.py
@@ -0,0 +1,344 @@
+#!/usr/bin/python3
+
+# Copyright (c) 2018 Skyward Experimental Rocketry
+# Authors: Luca Erbetta
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+from googleapiclient.discovery import build
+from httplib2 import Http
+from oauth2client import file, client, tools
+
+import math
+import re
+import datetime
+import sys
+from os.path import join
+import os
+from string import Template
+
+SCOPES = "https://www.googleapis.com/auth/spreadsheets.readonly"
+
+service = None
+
+# Spreadsheet file used to generate the events. The can be found in the link
+# to the spreadsheet, for example:
+# https://docs.google.com/spreadsheets/d/184kR2OAD7yWV0fYJdiGUDmHmy5_prY3nr-XgNA0Uge0/
+# -->
+# 184kR2OAD7yWV0fYJdiGUDmHmy5_prY3nr-XgNA0Uge0
+spreadsheet_id = "1D5m5WrNXxAL8XA6CKyfe5JcQ5IRnpNHg5ZG5bzt5G4I"
+output_folder = "hermes/"
+
+RANGE_FIELD_NAME = "{sheet_name}!A2:A"
+RANGE_FIELD_TYPE = "{sheet_name}!B2:B"
+RANGE_FIELD_SIZE = "{sheet_name}!C2:C"
+RANGE_FIELD_MIN = "{sheet_name}!D2:D"
+RANGE_FIELD_MAX = "{sheet_name}!E2:E"
+RANGE_REPEAT_NUM = "{sheet_name}!J1"
+RANGE_MAV_NAME = "{sheet_name}!J2"
+
+with open("templates/TelemetryPacker.h.template", "r") as template_file:
+    tm_packer_template = Template(template_file.read())
+with open("templates/packFunction.cpp.template", "r") as template_file:
+    pack_fun_template = Template(template_file.read())
+with open("templates/unpackFunction.cpp.template", "r") as template_file:
+    unpack_fun_template = Template(template_file.read())
+with open("templates/ConversionFunctions.h.template", "r") as template_file:
+    convfuns_template = Template(template_file.read())
+
+
+def auth():
+    try:
+        store = file.Storage("store.json")
+        creds = store.get()
+        if not creds or creds.invalid:
+            flow = client.flow_from_clientsecrets("credentials.json", SCOPES)
+            creds = tools.run_flow(flow, store)
+
+        global service
+        service = build("sheets", "v4", http=creds.authorize(Http()))
+        return True
+    except:
+        print("Authentication error:", sys.exc_info()[0])
+        return False
+
+
+def listSheets():
+    result = service.spreadsheets().get(spreadsheetId=spreadsheet_id).execute()
+
+    return [x["properties"]["title"] for x in result["sheets"]]
+
+
+def loadPacketFromSheet(packet_name: str):
+    # Strip 'Packet' at the end
+    packet = {"name": packet_name[0:-6]}
+
+    result = (
+        service.spreadsheets()
+        .values()
+        .get(
+            spreadsheetId=spreadsheet_id,
+            range=RANGE_FIELD_NAME.format(sheet_name=packet_name),
+        )
+        .execute()
+    )
+    fields = [x[0] for x in result["values"]]
+
+    result = (
+        service.spreadsheets()
+        .values()
+        .get(
+            spreadsheetId=spreadsheet_id,
+            range=RANGE_FIELD_SIZE.format(sheet_name=packet_name),
+        )
+        .execute()
+    )
+    sizes = [int(x[0]) for x in result["values"]]
+
+    result = (
+        service.spreadsheets()
+        .values()
+        .get(
+            spreadsheetId=spreadsheet_id,
+            range=RANGE_FIELD_TYPE.format(sheet_name=packet_name),
+        )
+        .execute()
+    )
+    types = [x[0] for x in result["values"]]
+
+    result = (
+        service.spreadsheets()
+        .values()
+        .get(
+            spreadsheetId=spreadsheet_id,
+            range=RANGE_FIELD_MIN.format(sheet_name=packet_name),
+        )
+        .execute()
+    )
+    rmins = [float(x[0]) for x in result["values"]]
+
+    result = (
+        service.spreadsheets()
+        .values()
+        .get(
+            spreadsheetId=spreadsheet_id,
+            range=RANGE_FIELD_MAX.format(sheet_name=packet_name),
+        )
+        .execute()
+    )
+    rmaxs = [float(x[0]) for x in result["values"]]
+
+    
+
+    result = (
+        service.spreadsheets()
+        .values()
+        .get(
+            spreadsheetId=spreadsheet_id,
+            range=RANGE_REPEAT_NUM.format(sheet_name=packet_name),
+        )
+        .execute()
+    )
+
+    indices = [0]
+    for i in range(1, len(sizes)):
+        indices += [indices[i - 1] + sizes[i - 1]]
+
+    packet["repeat"] = int(result["values"][0][0])
+
+    result = (
+        service.spreadsheets()
+        .values()
+        .get(
+            spreadsheetId=spreadsheet_id,
+            range=RANGE_MAV_NAME.format(sheet_name=packet_name),
+        )
+        .execute()
+    )
+
+    indices = [0]
+    for i in range(1, len(sizes)):
+        indices += [indices[i - 1] + sizes[i - 1]]
+
+    packet["mav_tm_name"] = result["values"][0][0]
+
+    packet["fields"] = [
+        {"name": f, "size": s, "index": i, "type": t, "range": (rmin, rmax)}
+        for f, s, i, t, rmin, rmax in zip(fields, sizes, indices, types, rmins, rmaxs)
+    ]
+    packet["partial_size"] = sum(sizes)
+    packet["total_size"] = math.ceil((sum(sizes) * packet["repeat"]) / 8)
+    return packet
+
+
+def generatePackFunction(packet_data, field):
+    convert_fun_template = Template(
+        "${telemetry_name}Conversion::${field_name_ccase}ToBits"
+    )
+
+    subs = {}
+
+    subs["max_index"] = packet_data["repeat"]
+
+    subs["type"] = field["type"]
+    name = field["name"].replace("_", " ").title().replace(" ", "")
+    name = name[0].lower() + name[1:]
+    subs["convert_fun"] = convert_fun_template.substitute(
+        telemetry_name=packet_data["name"], field_name_ccase=name,
+    )
+    subs["field_name_lcase"] = field["name"].lower()
+    subs["field_name_ccase"] = field["name"].replace("_", " ").title().replace(" ", "")
+    subs["field_name_ucase"] = field["name"].upper()
+    subs["mav_tm_name"] = packet_data["mav_tm_name"]
+
+    return pack_fun_template.substitute(**subs)
+
+
+def generateUnpackFunction(packet_data, field):
+    convert_fun_template = Template(
+        "${telemetry_name}Conversion::bitsTo${field_name_ccase}"
+    )
+
+    subs = {}
+    subs["max_index"] = packet_data["repeat"]
+
+    subs["type"] = field["type"]
+    subs["convert_fun"] = convert_fun_template.substitute(
+        telemetry_name=packet_data["name"],
+        field_name_ccase=field["name"].replace("_", " ").title().replace(" ", ""),
+    )
+    subs["field_name_lcase"] = field["name"].lower()
+    subs["field_name_ccase"] = field["name"].replace("_", " ").title().replace(" ", "")
+    subs["field_name_ucase"] = field["name"].upper()
+    subs["mav_tm_name"] = packet_data["mav_tm_name"]
+
+    return unpack_fun_template.substitute(**subs)
+
+
+def generatePackerClass(packet_data, output_folder):
+    index_template = Template("        INDEX_$field_name = $index")
+    size_template = Template("        SIZE_$field_name = $size")
+
+    indices = ""
+    sizes = ""
+    pack_funcs = ""
+    unpack_funcs = ""
+    for f in packet_data["fields"]:
+        indices += index_template.substitute(field_name=f["name"].upper(), index=f["index"])
+        sizes += size_template.substitute(field_name=f["name"].upper(), size=f["size"])
+
+        if f != packet_data["fields"][-1]:
+            indices += ",\n"
+            sizes += ",\n"
+
+        pack_funcs += generatePackFunction(packet_data, f)
+        unpack_funcs += generateUnpackFunction(packet_data, f)
+
+    out = tm_packer_template.substitute(
+        mav_tm_name=packet_data["mav_tm_name"],
+        telemetry_name_ccase=packet_data["name"],
+        tm_total_size=packet_data["total_size"],
+        tm_partial_size=packet_data["partial_size"],
+        field_indices=indices,
+        field_sizes=sizes,
+        pack_functions=pack_funcs,
+        unpack_functions=unpack_funcs,
+    )
+
+    with open(join(output_folder, packet_data["name"] + "Packer.h"), "w") as out_file:
+        out_file.write(out)
+
+
+def generateConversionFunctions(packet_data):
+    class_template = Template(
+        "\nclass ${telemetry_name_ccase}Conversion\n{\npublic:\n    $functions\n};\n\n\n"
+    )
+    
+    frombits_template = Template(
+        "\n    static $type bitsTo${field_name_ccase}(uint64_t bits)\n    "
+        + "{\n        return ($type)bits;\n    }\n"
+    )
+
+    tobits_template = Template(
+        "\n    static uint64_t ${field_name_ccase}ToBits($type $field_name_lcase)"
+        + "\n    {\n        return (uint64_t)$field_name_lcase;\n    }\n\n"
+    )
+
+    frombits_template_ranged = Template(
+        "\n    static $type bitsTo${field_name_ccase}(uint64_t bits)\n    "
+        + "{\n        return undiscretizeRange<$type>(bits, $rmin, $rmax, $res);\n    }\n"
+    )
+
+    tobits_template_ranged = Template(
+        "\n    static uint64_t ${field_name_ccase}ToBits($type $field_name_lcase)"
+        + "\n    {\n        return discretizeRange<$type>($field_name_lcase, $rmin, $rmax, $res);\n    }\n\n"
+    )
+
+    funs = ""
+    for field in packet_data["fields"]:
+        subs = {}
+        subs["type"] = field["type"]
+        subs["field_name_lcase"] = field["name"].lower()
+        subs["field_name_ccase"] = field["name"].replace("_", " ").title().replace(" ", "")
+        subs["field_name_ucase"] = field["name"].upper()
+        subs["rmin"] = field["range"][0]
+        subs["rmax"] = field["range"][1]
+        subs["res"] = 2**field["size"]
+        if subs["rmax"] - subs["rmin"] != 0:
+            funs += frombits_template_ranged.substitute(**subs)
+
+            subs["field_name_ccase"] = (
+                subs["field_name_ccase"][0].lower() + subs["field_name_ccase"][1:]
+            )
+            funs += tobits_template_ranged.substitute(**subs)
+        else:
+            funs += frombits_template.substitute(**subs)
+
+            subs["field_name_ccase"] = (
+                subs["field_name_ccase"][0].lower() + subs["field_name_ccase"][1:]
+            )
+            funs += tobits_template.substitute(**subs)
+
+    return class_template.substitute(
+        functions=funs, telemetry_name_ccase=packet_data["name"]
+    )
+
+
+print("Skyward telemetry packet generator")
+print("Google sheets API auth in progress...")
+
+if auth():
+    print("Auth successfull.")
+else:
+    print("Authentication failed.")
+    exit()
+
+sheets = listSheets()
+packet_sheets = [x for x in sheets if x.endswith("Packet")]
+
+conversion_classes = ""
+for p in packet_sheets:
+    data = loadPacketFromSheet(p)
+    generatePackerClass(data, output_folder)
+    conversion_classes += generateConversionFunctions(data)
+
+with open(join(output_folder, "ConversionFuncions_generated.h"), "w") as out_file:
+    out_file.write(convfuns_template.substitute(classes=conversion_classes))
+
diff --git a/bitpacking/hermes/ConversionFunctions.h b/bitpacking/hermes/ConversionFunctions.h
new file mode 100644
index 0000000000000000000000000000000000000000..03284ea516c5be4e07a2e214564fa8528f02de5d
--- /dev/null
+++ b/bitpacking/hermes/ConversionFunctions.h
@@ -0,0 +1,533 @@
+/**
+ * Copyright (c) 2019 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+#include <cstdint>
+#include <type_traits>
+
+template<typename T>
+inline uint64_t discretizeRange(T value, T min, T max, unsigned int resolution)
+{
+    static_assert(std::is_arithmetic<T>::value);
+
+    if (value < min)
+    {
+        return 0;
+    }
+    else if (value > max)
+    {
+        return resolution - 1;
+    }
+
+    return (uint64_t)((value - min) * resolution / (max - min));
+
+}
+
+template <typename T>
+inline T undiscretizeRange(uint64_t value, T min, T max,
+                           unsigned int resolution)
+{
+    static_assert(std::is_arithmetic<T>::value);
+
+    return value * (max - min) / resolution + min;
+}
+
+
+
+class HighRateTMConversion
+{
+public:
+    
+    static long long bitsToTimestamp(uint64_t bits)
+    {
+        return (long long)bits;
+    }
+
+    static uint64_t timestampToBits(long long timestamp)
+    {
+        return (uint64_t)timestamp;
+    }
+
+
+    static float bitsToPressureAda(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, 50000.0, 105000.0, 4096);
+    }
+
+    static uint64_t pressureAdaToBits(float pressure_ada)
+    {
+        return discretizeRange<float>(pressure_ada, 50000.0, 105000.0, 4096);
+    }
+
+
+    static float bitsToPressureDigi(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, 50000.0, 105000.0, 8192);
+    }
+
+    static uint64_t pressureDigiToBits(float pressure_digi)
+    {
+        return discretizeRange<float>(pressure_digi, 50000.0, 105000.0, 8192);
+    }
+
+
+    static float bitsToMslAltitude(uint64_t bits)
+    {
+        return (float)bits;
+    }
+
+    static uint64_t mslAltitudeToBits(float msl_altitude)
+    {
+        return (uint64_t)msl_altitude;
+    }
+
+
+    static float bitsToAglAltitude(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, -500.0, 3000.0, 512);
+    }
+
+    static uint64_t aglAltitudeToBits(float agl_altitude)
+    {
+        return discretizeRange<float>(agl_altitude, -500.0, 3000.0, 512);
+    }
+
+
+    static float bitsToVertSpeed(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, -512.0, 512.0, 1024);
+    }
+
+    static uint64_t vertSpeedToBits(float vert_speed)
+    {
+        return discretizeRange<float>(vert_speed, -512.0, 512.0, 1024);
+    }
+
+
+    static float bitsToVertSpeed2(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, -512.0, 512.0, 1024);
+    }
+
+    static uint64_t vertSpeed2ToBits(float vert_speed_2)
+    {
+        return discretizeRange<float>(vert_speed_2, -512.0, 512.0, 1024);
+    }
+
+
+    static float bitsToAccX(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, -16.0, 16.0, 2048);
+    }
+
+    static uint64_t accXToBits(float acc_x)
+    {
+        return discretizeRange<float>(acc_x, -16.0, 16.0, 2048);
+    }
+
+
+    static float bitsToAccY(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, -16.0, 16.0, 2048);
+    }
+
+    static uint64_t accYToBits(float acc_y)
+    {
+        return discretizeRange<float>(acc_y, -16.0, 16.0, 2048);
+    }
+
+
+    static float bitsToAccZ(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, -16.0, 16.0, 2048);
+    }
+
+    static uint64_t accZToBits(float acc_z)
+    {
+        return discretizeRange<float>(acc_z, -16.0, 16.0, 2048);
+    }
+
+
+    static float bitsToGyroX(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, -2000.0, 2000.0, 2048);
+    }
+
+    static uint64_t gyroXToBits(float gyro_x)
+    {
+        return discretizeRange<float>(gyro_x, -2000.0, 2000.0, 2048);
+    }
+
+
+    static float bitsToGyroY(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, -2000.0, 2000.0, 2048);
+    }
+
+    static uint64_t gyroYToBits(float gyro_y)
+    {
+        return discretizeRange<float>(gyro_y, -2000.0, 2000.0, 2048);
+    }
+
+
+    static float bitsToGyroZ(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, -2000.0, 2000.0, 2048);
+    }
+
+    static uint64_t gyroZToBits(float gyro_z)
+    {
+        return discretizeRange<float>(gyro_z, -2000.0, 2000.0, 2048);
+    }
+
+
+    static float bitsToGpsLat(uint64_t bits)
+    {
+        return (float)bits;
+    }
+
+    static uint64_t gpsLatToBits(float gps_lat)
+    {
+        return (uint64_t)gps_lat;
+    }
+
+
+    static float bitsToGpsLon(uint64_t bits)
+    {
+        return (float)bits;
+    }
+
+    static uint64_t gpsLonToBits(float gps_lon)
+    {
+        return (uint64_t)gps_lon;
+    }
+
+
+    static float bitsToGpsAlt(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, 0.0, 5120.0, 1024);
+    }
+
+    static uint64_t gpsAltToBits(float gps_alt)
+    {
+        return discretizeRange<float>(gps_alt, 0.0, 5120.0, 1024);
+    }
+
+
+    static uint8_t bitsToFmmState(uint64_t bits)
+    {
+        return (uint8_t)bits;
+    }
+
+    static uint64_t fmmStateToBits(uint8_t fmm_state)
+    {
+        return (uint64_t)fmm_state;
+    }
+
+
+    static uint8_t bitsToDplState(uint64_t bits)
+    {
+        return (uint8_t)bits;
+    }
+
+    static uint64_t dplStateToBits(uint8_t dpl_state)
+    {
+        return (uint64_t)dpl_state;
+    }
+
+
+    static uint8_t bitsToPinLaunch(uint64_t bits)
+    {
+        return (uint8_t)bits;
+    }
+
+    static uint64_t pinLaunchToBits(uint8_t pin_launch)
+    {
+        return (uint64_t)pin_launch;
+    }
+
+
+    static uint8_t bitsToPinDetach(uint64_t bits)
+    {
+        return (uint8_t)bits;
+    }
+
+    static uint64_t pinDetachToBits(uint8_t pin_detach)
+    {
+        return (uint64_t)pin_detach;
+    }
+
+
+    static uint8_t bitsToGpsFix(uint64_t bits)
+    {
+        return (uint8_t)bits;
+    }
+
+    static uint64_t gpsFixToBits(uint8_t gps_fix)
+    {
+        return (uint64_t)gps_fix;
+    }
+
+
+};
+
+
+
+class LowRateTMConversion
+{
+public:
+    
+    static long long bitsToLiftoffTs(uint64_t bits)
+    {
+        return (long long)bits;
+    }
+
+    static uint64_t liftoffTsToBits(long long liftoff_ts)
+    {
+        return (uint64_t)liftoff_ts;
+    }
+
+
+    static long long bitsToLiftoffMaxAccTs(uint64_t bits)
+    {
+        return (long long)bits;
+    }
+
+    static uint64_t liftoffMaxAccTsToBits(long long liftoff_max_acc_ts)
+    {
+        return (uint64_t)liftoff_max_acc_ts;
+    }
+
+
+    static float bitsToLiftoffMaxAcc(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, -16.0, 16.0, 2048);
+    }
+
+    static uint64_t liftoffMaxAccToBits(float liftoff_max_acc)
+    {
+        return discretizeRange<float>(liftoff_max_acc, -16.0, 16.0, 2048);
+    }
+
+
+    static long long bitsToMaxZspeedTs(uint64_t bits)
+    {
+        return (long long)bits;
+    }
+
+    static uint64_t maxZspeedTsToBits(long long max_zspeed_ts)
+    {
+        return (uint64_t)max_zspeed_ts;
+    }
+
+
+    static float bitsToMaxZspeed(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, -512.0, 512.0, 1024);
+    }
+
+    static uint64_t maxZspeedToBits(float max_zspeed)
+    {
+        return discretizeRange<float>(max_zspeed, -512.0, 512.0, 1024);
+    }
+
+
+    static float bitsToMaxSpeedAltitude(uint64_t bits)
+    {
+        return (float)bits;
+    }
+
+    static uint64_t maxSpeedAltitudeToBits(float max_speed_altitude)
+    {
+        return (uint64_t)max_speed_altitude;
+    }
+
+
+    static long long bitsToApogeeTs(uint64_t bits)
+    {
+        return (long long)bits;
+    }
+
+    static uint64_t apogeeTsToBits(long long apogee_ts)
+    {
+        return (uint64_t)apogee_ts;
+    }
+
+
+    static float bitsToNxpMinPressure(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, 50000.0, 100000.0, 4096);
+    }
+
+    static uint64_t nxpMinPressureToBits(float nxp_min_pressure)
+    {
+        return discretizeRange<float>(nxp_min_pressure, 50000.0, 100000.0, 4096);
+    }
+
+
+    static float bitsToHwMinPressure(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, 50000.0, 100000.0, 4096);
+    }
+
+    static uint64_t hwMinPressureToBits(float hw_min_pressure)
+    {
+        return discretizeRange<float>(hw_min_pressure, 50000.0, 100000.0, 4096);
+    }
+
+
+    static float bitsToKalmanMinPressure(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, 50000.0, 100000.0, 4096);
+    }
+
+    static uint64_t kalmanMinPressureToBits(float kalman_min_pressure)
+    {
+        return discretizeRange<float>(kalman_min_pressure, 50000.0, 100000.0, 4096);
+    }
+
+
+    static float bitsToDigitalMinPressure(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, 50000.0, 100000.0, 8192);
+    }
+
+    static uint64_t digitalMinPressureToBits(float digital_min_pressure)
+    {
+        return discretizeRange<float>(digital_min_pressure, 50000.0, 100000.0, 8192);
+    }
+
+
+    static float bitsToBaroMaxAltitutde(uint64_t bits)
+    {
+        return (float)bits;
+    }
+
+    static uint64_t baroMaxAltitutdeToBits(float baro_max_altitutde )
+    {
+        return (uint64_t)baro_max_altitutde ;
+    }
+
+
+    static float bitsToGpsMaxAltitude(uint64_t bits)
+    {
+        return (float)bits;
+    }
+
+    static uint64_t gpsMaxAltitudeToBits(float gps_max_altitude)
+    {
+        return (uint64_t)gps_max_altitude;
+    }
+
+
+    static float bitsToApogeeLat(uint64_t bits)
+    {
+        return (float)bits;
+    }
+
+    static uint64_t apogeeLatToBits(float apogee_lat)
+    {
+        return (uint64_t)apogee_lat;
+    }
+
+
+    static float bitsToApogeeLon(uint64_t bits)
+    {
+        return (float)bits;
+    }
+
+    static uint64_t apogeeLonToBits(float apogee_lon)
+    {
+        return (uint64_t)apogee_lon;
+    }
+
+
+    static long long bitsToDrogueDplTs(uint64_t bits)
+    {
+        return (long long)bits;
+    }
+
+    static uint64_t drogueDplTsToBits(long long drogue_dpl_ts)
+    {
+        return (uint64_t)drogue_dpl_ts;
+    }
+
+
+    static float bitsToDrogueDplMaxAcc(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, -16.0, 16.0, 2048);
+    }
+
+    static uint64_t drogueDplMaxAccToBits(float drogue_dpl_max_acc)
+    {
+        return discretizeRange<float>(drogue_dpl_max_acc, -16.0, 16.0, 2048);
+    }
+
+
+    static long long bitsToMainDplTs(uint64_t bits)
+    {
+        return (long long)bits;
+    }
+
+    static uint64_t mainDplTsToBits(long long main_dpl_ts)
+    {
+        return (uint64_t)main_dpl_ts;
+    }
+
+
+    static float bitsToMainDplAltitude(uint64_t bits)
+    {
+        return (float)bits;
+    }
+
+    static uint64_t mainDplAltitudeToBits(float main_dpl_altitude)
+    {
+        return (uint64_t)main_dpl_altitude;
+    }
+
+
+    static float bitsToMainDplZspeed(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, -512.0, 512.0, 1024);
+    }
+
+    static uint64_t mainDplZspeedToBits(float main_dpl_zspeed)
+    {
+        return discretizeRange<float>(main_dpl_zspeed, -512.0, 512.0, 1024);
+    }
+
+
+    static float bitsToMainDplAcc(uint64_t bits)
+    {
+        return undiscretizeRange<float>(bits, -16.0, 16.0, 2048);
+    }
+
+    static uint64_t mainDplAccToBits(float main_dpl_acc)
+    {
+        return discretizeRange<float>(main_dpl_acc, -16.0, 16.0, 2048);
+    }
+
+
+};
+
+
diff --git a/bitpacking/hermes/HermesPackets.h b/bitpacking/hermes/HermesPackets.h
new file mode 100644
index 0000000000000000000000000000000000000000..7e2d4c967697b383d424fcdffbfa15e108b0837c
--- /dev/null
+++ b/bitpacking/hermes/HermesPackets.h
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2019 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include "HighRateTMPacker.h"
+#include "LowRateTMPacker.h"
\ No newline at end of file
diff --git a/bitpacking/hermes/HighRateTMPacker.h b/bitpacking/hermes/HighRateTMPacker.h
new file mode 100644
index 0000000000000000000000000000000000000000..acb8b57de4c0910f7e90a80d11b1b00636d3873f
--- /dev/null
+++ b/bitpacking/hermes/HighRateTMPacker.h
@@ -0,0 +1,943 @@
+/**
+ * Copyright (c) 2019 Skyward Experimental Rocketry
+ * Authors: Luca Conterio, Luca Erbetta
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include "BitPacker.h"
+#include "mavlink_lib/hermes/mavlink.h"
+#include "ConversionFunctions.h"
+
+class HighRateTMPacker
+{
+public:
+    static constexpr int HR_TM_PACKET_SIZE            = 100;
+    static constexpr int HR_TM_SINGLE_PACKET_SIZE_BITS = 200;
+
+    static_assert(MAVLINK_MSG_HR_TM_FIELD_PAYLOAD_LEN == HR_TM_PACKET_SIZE,
+                  "Payload size mismatch! Mavlnk payload size differes from declared size. Maybe your mavlink definitions are outdated?");
+
+
+
+
+    HighRateTMPacker(uint8_t *packet) : packet(packet), bitpacker(packet, HR_TM_PACKET_SIZE)
+    {
+    }
+
+    
+    bool packTimestamp(long long timestamp, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::timestampToBits(timestamp);
+
+        return bitpacker.pack(
+            INDEX_TIMESTAMP + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_TIMESTAMP, bits);
+    }
+
+    bool packPressureAda(float pressure_ada, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::pressureAdaToBits(pressure_ada);
+
+        return bitpacker.pack(
+            INDEX_PRESSURE_ADA + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_PRESSURE_ADA, bits);
+    }
+
+    bool packPressureDigi(float pressure_digi, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::pressureDigiToBits(pressure_digi);
+
+        return bitpacker.pack(
+            INDEX_PRESSURE_DIGI + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_PRESSURE_DIGI, bits);
+    }
+
+    bool packMslAltitude(float msl_altitude, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::mslAltitudeToBits(msl_altitude);
+
+        return bitpacker.pack(
+            INDEX_MSL_ALTITUDE + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_MSL_ALTITUDE, bits);
+    }
+
+    bool packAglAltitude(float agl_altitude, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::aglAltitudeToBits(agl_altitude);
+
+        return bitpacker.pack(
+            INDEX_AGL_ALTITUDE + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_AGL_ALTITUDE, bits);
+    }
+
+    bool packVertSpeed(float vert_speed, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::vertSpeedToBits(vert_speed);
+
+        return bitpacker.pack(
+            INDEX_VERT_SPEED + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_VERT_SPEED, bits);
+    }
+
+    bool packVertSpeed2(float vert_speed_2, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::vertSpeed2ToBits(vert_speed_2);
+
+        return bitpacker.pack(
+            INDEX_VERT_SPEED_2 + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_VERT_SPEED_2, bits);
+    }
+
+    bool packAccX(float acc_x, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::accXToBits(acc_x);
+
+        return bitpacker.pack(
+            INDEX_ACC_X + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_ACC_X, bits);
+    }
+
+    bool packAccY(float acc_y, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::accYToBits(acc_y);
+
+        return bitpacker.pack(
+            INDEX_ACC_Y + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_ACC_Y, bits);
+    }
+
+    bool packAccZ(float acc_z, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::accZToBits(acc_z);
+
+        return bitpacker.pack(
+            INDEX_ACC_Z + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_ACC_Z, bits);
+    }
+
+    bool packGyroX(float gyro_x, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::gyroXToBits(gyro_x);
+
+        return bitpacker.pack(
+            INDEX_GYRO_X + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_GYRO_X, bits);
+    }
+
+    bool packGyroY(float gyro_y, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::gyroYToBits(gyro_y);
+
+        return bitpacker.pack(
+            INDEX_GYRO_Y + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_GYRO_Y, bits);
+    }
+
+    bool packGyroZ(float gyro_z, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::gyroZToBits(gyro_z);
+
+        return bitpacker.pack(
+            INDEX_GYRO_Z + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_GYRO_Z, bits);
+    }
+
+    bool packGpsLat(float gps_lat, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::gpsLatToBits(gps_lat);
+
+        return bitpacker.pack(
+            INDEX_GPS_LAT + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_GPS_LAT, bits);
+    }
+
+    bool packGpsLon(float gps_lon, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::gpsLonToBits(gps_lon);
+
+        return bitpacker.pack(
+            INDEX_GPS_LON + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_GPS_LON, bits);
+    }
+
+    bool packGpsAlt(float gps_alt, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::gpsAltToBits(gps_alt);
+
+        return bitpacker.pack(
+            INDEX_GPS_ALT + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_GPS_ALT, bits);
+    }
+
+    bool packFmmState(uint8_t fmm_state, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::fmmStateToBits(fmm_state);
+
+        return bitpacker.pack(
+            INDEX_FMM_STATE + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_FMM_STATE, bits);
+    }
+
+    bool packDplState(uint8_t dpl_state, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::dplStateToBits(dpl_state);
+
+        return bitpacker.pack(
+            INDEX_DPL_STATE + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_DPL_STATE, bits);
+    }
+
+    bool packPinLaunch(uint8_t pin_launch, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::pinLaunchToBits(pin_launch);
+
+        return bitpacker.pack(
+            INDEX_PIN_LAUNCH + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_PIN_LAUNCH, bits);
+    }
+
+    bool packPinDetach(uint8_t pin_detach, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::pinDetachToBits(pin_detach);
+
+        return bitpacker.pack(
+            INDEX_PIN_DETACH + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_PIN_DETACH, bits);
+    }
+
+    bool packGpsFix(uint8_t gps_fix, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = HighRateTMConversion::gpsFixToBits(gps_fix);
+
+        return bitpacker.pack(
+            INDEX_GPS_FIX + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_GPS_FIX, bits);
+    }
+
+
+    
+    bool unpackTimestamp(long long* timestamp, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_TIMESTAMP + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_TIMESTAMP, &bits);
+        
+        if(success)
+        {
+            *timestamp = HighRateTMConversion::bitsToTimestamp(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackPressureAda(float* pressure_ada, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_PRESSURE_ADA + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_PRESSURE_ADA, &bits);
+        
+        if(success)
+        {
+            *pressure_ada = HighRateTMConversion::bitsToPressureAda(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackPressureDigi(float* pressure_digi, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_PRESSURE_DIGI + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_PRESSURE_DIGI, &bits);
+        
+        if(success)
+        {
+            *pressure_digi = HighRateTMConversion::bitsToPressureDigi(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackMslAltitude(float* msl_altitude, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_MSL_ALTITUDE + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_MSL_ALTITUDE, &bits);
+        
+        if(success)
+        {
+            *msl_altitude = HighRateTMConversion::bitsToMslAltitude(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackAglAltitude(float* agl_altitude, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_AGL_ALTITUDE + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_AGL_ALTITUDE, &bits);
+        
+        if(success)
+        {
+            *agl_altitude = HighRateTMConversion::bitsToAglAltitude(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackVertSpeed(float* vert_speed, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_VERT_SPEED + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_VERT_SPEED, &bits);
+        
+        if(success)
+        {
+            *vert_speed = HighRateTMConversion::bitsToVertSpeed(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackVertSpeed2(float* vert_speed_2, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_VERT_SPEED_2 + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_VERT_SPEED_2, &bits);
+        
+        if(success)
+        {
+            *vert_speed_2 = HighRateTMConversion::bitsToVertSpeed2(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackAccX(float* acc_x, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_ACC_X + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_ACC_X, &bits);
+        
+        if(success)
+        {
+            *acc_x = HighRateTMConversion::bitsToAccX(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackAccY(float* acc_y, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_ACC_Y + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_ACC_Y, &bits);
+        
+        if(success)
+        {
+            *acc_y = HighRateTMConversion::bitsToAccY(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackAccZ(float* acc_z, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_ACC_Z + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_ACC_Z, &bits);
+        
+        if(success)
+        {
+            *acc_z = HighRateTMConversion::bitsToAccZ(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackGyroX(float* gyro_x, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_GYRO_X + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_GYRO_X, &bits);
+        
+        if(success)
+        {
+            *gyro_x = HighRateTMConversion::bitsToGyroX(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackGyroY(float* gyro_y, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_GYRO_Y + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_GYRO_Y, &bits);
+        
+        if(success)
+        {
+            *gyro_y = HighRateTMConversion::bitsToGyroY(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackGyroZ(float* gyro_z, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_GYRO_Z + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_GYRO_Z, &bits);
+        
+        if(success)
+        {
+            *gyro_z = HighRateTMConversion::bitsToGyroZ(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackGpsLat(float* gps_lat, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_GPS_LAT + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_GPS_LAT, &bits);
+        
+        if(success)
+        {
+            *gps_lat = HighRateTMConversion::bitsToGpsLat(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackGpsLon(float* gps_lon, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_GPS_LON + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_GPS_LON, &bits);
+        
+        if(success)
+        {
+            *gps_lon = HighRateTMConversion::bitsToGpsLon(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackGpsAlt(float* gps_alt, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_GPS_ALT + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_GPS_ALT, &bits);
+        
+        if(success)
+        {
+            *gps_alt = HighRateTMConversion::bitsToGpsAlt(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackFmmState(uint8_t* fmm_state, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_FMM_STATE + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_FMM_STATE, &bits);
+        
+        if(success)
+        {
+            *fmm_state = HighRateTMConversion::bitsToFmmState(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackDplState(uint8_t* dpl_state, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_DPL_STATE + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_DPL_STATE, &bits);
+        
+        if(success)
+        {
+            *dpl_state = HighRateTMConversion::bitsToDplState(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackPinLaunch(uint8_t* pin_launch, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_PIN_LAUNCH + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_PIN_LAUNCH, &bits);
+        
+        if(success)
+        {
+            *pin_launch = HighRateTMConversion::bitsToPinLaunch(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackPinDetach(uint8_t* pin_detach, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_PIN_DETACH + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_PIN_DETACH, &bits);
+        
+        if(success)
+        {
+            *pin_detach = HighRateTMConversion::bitsToPinDetach(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackGpsFix(uint8_t* gps_fix, size_t packet_index)
+    {
+        if(packet_index >= 4)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_GPS_FIX + packet_index * HR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_GPS_FIX, &bits);
+        
+        if(success)
+        {
+            *gps_fix = HighRateTMConversion::bitsToGpsFix(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+
+private:
+    enum FieldIndex
+    {
+        INDEX_TIMESTAMP = 0,
+        INDEX_PRESSURE_ADA = 25,
+        INDEX_PRESSURE_DIGI = 37,
+        INDEX_MSL_ALTITUDE = 50,
+        INDEX_AGL_ALTITUDE = 62,
+        INDEX_VERT_SPEED = 71,
+        INDEX_VERT_SPEED_2 = 81,
+        INDEX_ACC_X = 91,
+        INDEX_ACC_Y = 102,
+        INDEX_ACC_Z = 113,
+        INDEX_GYRO_X = 124,
+        INDEX_GYRO_Y = 135,
+        INDEX_GYRO_Z = 146,
+        INDEX_GPS_LAT = 157,
+        INDEX_GPS_LON = 168,
+        INDEX_GPS_ALT = 179,
+        INDEX_FMM_STATE = 189,
+        INDEX_DPL_STATE = 194,
+        INDEX_PIN_LAUNCH = 197,
+        INDEX_PIN_DETACH = 198,
+        INDEX_GPS_FIX = 199
+    };
+
+    enum FieldSize
+    {
+        SIZE_TIMESTAMP = 25,
+        SIZE_PRESSURE_ADA = 12,
+        SIZE_PRESSURE_DIGI = 13,
+        SIZE_MSL_ALTITUDE = 12,
+        SIZE_AGL_ALTITUDE = 9,
+        SIZE_VERT_SPEED = 10,
+        SIZE_VERT_SPEED_2 = 10,
+        SIZE_ACC_X = 11,
+        SIZE_ACC_Y = 11,
+        SIZE_ACC_Z = 11,
+        SIZE_GYRO_X = 11,
+        SIZE_GYRO_Y = 11,
+        SIZE_GYRO_Z = 11,
+        SIZE_GPS_LAT = 11,
+        SIZE_GPS_LON = 11,
+        SIZE_GPS_ALT = 10,
+        SIZE_FMM_STATE = 5,
+        SIZE_DPL_STATE = 3,
+        SIZE_PIN_LAUNCH = 1,
+        SIZE_PIN_DETACH = 1,
+        SIZE_GPS_FIX = 1
+    };
+
+    uint8_t *packet;
+    BitPacker bitpacker;
+};
\ No newline at end of file
diff --git a/bitpacking/hermes/LowRateTMPacker.h b/bitpacking/hermes/LowRateTMPacker.h
new file mode 100644
index 0000000000000000000000000000000000000000..2e1febfae026105b4512ebc60d0c4223d011ba6b
--- /dev/null
+++ b/bitpacking/hermes/LowRateTMPacker.h
@@ -0,0 +1,943 @@
+/**
+ * Copyright (c) 2019 Skyward Experimental Rocketry
+ * Authors: Luca Conterio, Luca Erbetta
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include "BitPacker.h"
+#include "mavlink_lib/hermes/mavlink.h"
+#include "ConversionFunctions.h"
+
+class LowRateTMPacker
+{
+public:
+    static constexpr int LR_TM_PACKET_SIZE            = 40;
+    static constexpr int LR_TM_SINGLE_PACKET_SIZE_BITS = 319;
+
+    static_assert(MAVLINK_MSG_LR_TM_FIELD_PAYLOAD_LEN == LR_TM_PACKET_SIZE,
+                  "Payload size mismatch! Mavlnk payload size differes from declared size. Maybe your mavlink definitions are outdated?");
+
+
+
+
+    LowRateTMPacker(uint8_t *packet) : packet(packet), bitpacker(packet, LR_TM_PACKET_SIZE)
+    {
+    }
+
+    
+    bool packLiftoffTs(long long liftoff_ts, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::liftoffTsToBits(liftoff_ts);
+
+        return bitpacker.pack(
+            INDEX_LIFTOFF_TS + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_LIFTOFF_TS, bits);
+    }
+
+    bool packLiftoffMaxAccTs(long long liftoff_max_acc_ts, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::liftoffMaxAccTsToBits(liftoff_max_acc_ts);
+
+        return bitpacker.pack(
+            INDEX_LIFTOFF_MAX_ACC_TS + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_LIFTOFF_MAX_ACC_TS, bits);
+    }
+
+    bool packLiftoffMaxAcc(float liftoff_max_acc, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::liftoffMaxAccToBits(liftoff_max_acc);
+
+        return bitpacker.pack(
+            INDEX_LIFTOFF_MAX_ACC + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_LIFTOFF_MAX_ACC, bits);
+    }
+
+    bool packMaxZspeedTs(long long max_zspeed_ts, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::maxZspeedTsToBits(max_zspeed_ts);
+
+        return bitpacker.pack(
+            INDEX_MAX_ZSPEED_TS + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_MAX_ZSPEED_TS, bits);
+    }
+
+    bool packMaxZspeed(float max_zspeed, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::maxZspeedToBits(max_zspeed);
+
+        return bitpacker.pack(
+            INDEX_MAX_ZSPEED + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_MAX_ZSPEED, bits);
+    }
+
+    bool packMaxSpeedAltitude(float max_speed_altitude, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::maxSpeedAltitudeToBits(max_speed_altitude);
+
+        return bitpacker.pack(
+            INDEX_MAX_SPEED_ALTITUDE + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_MAX_SPEED_ALTITUDE, bits);
+    }
+
+    bool packApogeeTs(long long apogee_ts, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::apogeeTsToBits(apogee_ts);
+
+        return bitpacker.pack(
+            INDEX_APOGEE_TS + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_APOGEE_TS, bits);
+    }
+
+    bool packNxpMinPressure(float nxp_min_pressure, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::nxpMinPressureToBits(nxp_min_pressure);
+
+        return bitpacker.pack(
+            INDEX_NXP_MIN_PRESSURE + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_NXP_MIN_PRESSURE, bits);
+    }
+
+    bool packHwMinPressure(float hw_min_pressure, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::hwMinPressureToBits(hw_min_pressure);
+
+        return bitpacker.pack(
+            INDEX_HW_MIN_PRESSURE + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_HW_MIN_PRESSURE, bits);
+    }
+
+    bool packKalmanMinPressure(float kalman_min_pressure, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::kalmanMinPressureToBits(kalman_min_pressure);
+
+        return bitpacker.pack(
+            INDEX_KALMAN_MIN_PRESSURE + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_KALMAN_MIN_PRESSURE, bits);
+    }
+
+    bool packDigitalMinPressure(float digital_min_pressure, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::digitalMinPressureToBits(digital_min_pressure);
+
+        return bitpacker.pack(
+            INDEX_DIGITAL_MIN_PRESSURE + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_DIGITAL_MIN_PRESSURE, bits);
+    }
+
+    bool packBaroMaxAltitutde(float baro_max_altitutde , size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::baroMaxAltitutdeToBits(baro_max_altitutde );
+
+        return bitpacker.pack(
+            INDEX_BARO_MAX_ALTITUTDE  + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_BARO_MAX_ALTITUTDE , bits);
+    }
+
+    bool packGpsMaxAltitude(float gps_max_altitude, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::gpsMaxAltitudeToBits(gps_max_altitude);
+
+        return bitpacker.pack(
+            INDEX_GPS_MAX_ALTITUDE + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_GPS_MAX_ALTITUDE, bits);
+    }
+
+    bool packApogeeLat(float apogee_lat, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::apogeeLatToBits(apogee_lat);
+
+        return bitpacker.pack(
+            INDEX_APOGEE_LAT + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_APOGEE_LAT, bits);
+    }
+
+    bool packApogeeLon(float apogee_lon, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::apogeeLonToBits(apogee_lon);
+
+        return bitpacker.pack(
+            INDEX_APOGEE_LON + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_APOGEE_LON, bits);
+    }
+
+    bool packDrogueDplTs(long long drogue_dpl_ts, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::drogueDplTsToBits(drogue_dpl_ts);
+
+        return bitpacker.pack(
+            INDEX_DROGUE_DPL_TS + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_DROGUE_DPL_TS, bits);
+    }
+
+    bool packDrogueDplMaxAcc(float drogue_dpl_max_acc, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::drogueDplMaxAccToBits(drogue_dpl_max_acc);
+
+        return bitpacker.pack(
+            INDEX_DROGUE_DPL_MAX_ACC + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_DROGUE_DPL_MAX_ACC, bits);
+    }
+
+    bool packMainDplTs(long long main_dpl_ts, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::mainDplTsToBits(main_dpl_ts);
+
+        return bitpacker.pack(
+            INDEX_MAIN_DPL_TS + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_MAIN_DPL_TS, bits);
+    }
+
+    bool packMainDplAltitude(float main_dpl_altitude, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::mainDplAltitudeToBits(main_dpl_altitude);
+
+        return bitpacker.pack(
+            INDEX_MAIN_DPL_ALTITUDE + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_MAIN_DPL_ALTITUDE, bits);
+    }
+
+    bool packMainDplZspeed(float main_dpl_zspeed, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::mainDplZspeedToBits(main_dpl_zspeed);
+
+        return bitpacker.pack(
+            INDEX_MAIN_DPL_ZSPEED + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_MAIN_DPL_ZSPEED, bits);
+    }
+
+    bool packMainDplAcc(float main_dpl_acc, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = LowRateTMConversion::mainDplAccToBits(main_dpl_acc);
+
+        return bitpacker.pack(
+            INDEX_MAIN_DPL_ACC + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_MAIN_DPL_ACC, bits);
+    }
+
+
+    
+    bool unpackLiftoffTs(long long* liftoff_ts, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_LIFTOFF_TS + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_LIFTOFF_TS, &bits);
+        
+        if(success)
+        {
+            *liftoff_ts = LowRateTMConversion::bitsToLiftoffTs(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackLiftoffMaxAccTs(long long* liftoff_max_acc_ts, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_LIFTOFF_MAX_ACC_TS + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_LIFTOFF_MAX_ACC_TS, &bits);
+        
+        if(success)
+        {
+            *liftoff_max_acc_ts = LowRateTMConversion::bitsToLiftoffMaxAccTs(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackLiftoffMaxAcc(float* liftoff_max_acc, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_LIFTOFF_MAX_ACC + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_LIFTOFF_MAX_ACC, &bits);
+        
+        if(success)
+        {
+            *liftoff_max_acc = LowRateTMConversion::bitsToLiftoffMaxAcc(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackMaxZspeedTs(long long* max_zspeed_ts, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_MAX_ZSPEED_TS + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_MAX_ZSPEED_TS, &bits);
+        
+        if(success)
+        {
+            *max_zspeed_ts = LowRateTMConversion::bitsToMaxZspeedTs(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackMaxZspeed(float* max_zspeed, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_MAX_ZSPEED + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_MAX_ZSPEED, &bits);
+        
+        if(success)
+        {
+            *max_zspeed = LowRateTMConversion::bitsToMaxZspeed(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackMaxSpeedAltitude(float* max_speed_altitude, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_MAX_SPEED_ALTITUDE + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_MAX_SPEED_ALTITUDE, &bits);
+        
+        if(success)
+        {
+            *max_speed_altitude = LowRateTMConversion::bitsToMaxSpeedAltitude(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackApogeeTs(long long* apogee_ts, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_APOGEE_TS + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_APOGEE_TS, &bits);
+        
+        if(success)
+        {
+            *apogee_ts = LowRateTMConversion::bitsToApogeeTs(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackNxpMinPressure(float* nxp_min_pressure, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_NXP_MIN_PRESSURE + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_NXP_MIN_PRESSURE, &bits);
+        
+        if(success)
+        {
+            *nxp_min_pressure = LowRateTMConversion::bitsToNxpMinPressure(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackHwMinPressure(float* hw_min_pressure, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_HW_MIN_PRESSURE + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_HW_MIN_PRESSURE, &bits);
+        
+        if(success)
+        {
+            *hw_min_pressure = LowRateTMConversion::bitsToHwMinPressure(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackKalmanMinPressure(float* kalman_min_pressure, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_KALMAN_MIN_PRESSURE + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_KALMAN_MIN_PRESSURE, &bits);
+        
+        if(success)
+        {
+            *kalman_min_pressure = LowRateTMConversion::bitsToKalmanMinPressure(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackDigitalMinPressure(float* digital_min_pressure, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_DIGITAL_MIN_PRESSURE + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_DIGITAL_MIN_PRESSURE, &bits);
+        
+        if(success)
+        {
+            *digital_min_pressure = LowRateTMConversion::bitsToDigitalMinPressure(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackBaroMaxAltitutde(float* baro_max_altitutde , size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_BARO_MAX_ALTITUTDE  + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_BARO_MAX_ALTITUTDE , &bits);
+        
+        if(success)
+        {
+            *baro_max_altitutde  = LowRateTMConversion::bitsToBaroMaxAltitutde(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackGpsMaxAltitude(float* gps_max_altitude, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_GPS_MAX_ALTITUDE + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_GPS_MAX_ALTITUDE, &bits);
+        
+        if(success)
+        {
+            *gps_max_altitude = LowRateTMConversion::bitsToGpsMaxAltitude(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackApogeeLat(float* apogee_lat, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_APOGEE_LAT + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_APOGEE_LAT, &bits);
+        
+        if(success)
+        {
+            *apogee_lat = LowRateTMConversion::bitsToApogeeLat(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackApogeeLon(float* apogee_lon, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_APOGEE_LON + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_APOGEE_LON, &bits);
+        
+        if(success)
+        {
+            *apogee_lon = LowRateTMConversion::bitsToApogeeLon(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackDrogueDplTs(long long* drogue_dpl_ts, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_DROGUE_DPL_TS + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_DROGUE_DPL_TS, &bits);
+        
+        if(success)
+        {
+            *drogue_dpl_ts = LowRateTMConversion::bitsToDrogueDplTs(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackDrogueDplMaxAcc(float* drogue_dpl_max_acc, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_DROGUE_DPL_MAX_ACC + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_DROGUE_DPL_MAX_ACC, &bits);
+        
+        if(success)
+        {
+            *drogue_dpl_max_acc = LowRateTMConversion::bitsToDrogueDplMaxAcc(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackMainDplTs(long long* main_dpl_ts, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_MAIN_DPL_TS + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_MAIN_DPL_TS, &bits);
+        
+        if(success)
+        {
+            *main_dpl_ts = LowRateTMConversion::bitsToMainDplTs(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackMainDplAltitude(float* main_dpl_altitude, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_MAIN_DPL_ALTITUDE + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_MAIN_DPL_ALTITUDE, &bits);
+        
+        if(success)
+        {
+            *main_dpl_altitude = LowRateTMConversion::bitsToMainDplAltitude(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackMainDplZspeed(float* main_dpl_zspeed, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_MAIN_DPL_ZSPEED + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_MAIN_DPL_ZSPEED, &bits);
+        
+        if(success)
+        {
+            *main_dpl_zspeed = LowRateTMConversion::bitsToMainDplZspeed(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+    bool unpackMainDplAcc(float* main_dpl_acc, size_t packet_index)
+    {
+        if(packet_index >= 1)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_MAIN_DPL_ACC + packet_index * LR_TM_SINGLE_PACKET_SIZE_BITS,
+            SIZE_MAIN_DPL_ACC, &bits);
+        
+        if(success)
+        {
+            *main_dpl_acc = LowRateTMConversion::bitsToMainDplAcc(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
+
+
+private:
+    enum FieldIndex
+    {
+        INDEX_LIFTOFF_TS = 0,
+        INDEX_LIFTOFF_MAX_ACC_TS = 25,
+        INDEX_LIFTOFF_MAX_ACC = 50,
+        INDEX_MAX_ZSPEED_TS = 61,
+        INDEX_MAX_ZSPEED = 86,
+        INDEX_MAX_SPEED_ALTITUDE = 96,
+        INDEX_APOGEE_TS = 108,
+        INDEX_NXP_MIN_PRESSURE = 133,
+        INDEX_HW_MIN_PRESSURE = 145,
+        INDEX_KALMAN_MIN_PRESSURE = 157,
+        INDEX_DIGITAL_MIN_PRESSURE = 169,
+        INDEX_BARO_MAX_ALTITUTDE  = 182,
+        INDEX_GPS_MAX_ALTITUDE = 194,
+        INDEX_APOGEE_LAT = 204,
+        INDEX_APOGEE_LON = 214,
+        INDEX_DROGUE_DPL_TS = 224,
+        INDEX_DROGUE_DPL_MAX_ACC = 249,
+        INDEX_MAIN_DPL_TS = 260,
+        INDEX_MAIN_DPL_ALTITUDE = 285,
+        INDEX_MAIN_DPL_ZSPEED = 298,
+        INDEX_MAIN_DPL_ACC = 308
+    };
+
+    enum FieldSize
+    {
+        SIZE_LIFTOFF_TS = 25,
+        SIZE_LIFTOFF_MAX_ACC_TS = 25,
+        SIZE_LIFTOFF_MAX_ACC = 11,
+        SIZE_MAX_ZSPEED_TS = 25,
+        SIZE_MAX_ZSPEED = 10,
+        SIZE_MAX_SPEED_ALTITUDE = 12,
+        SIZE_APOGEE_TS = 25,
+        SIZE_NXP_MIN_PRESSURE = 12,
+        SIZE_HW_MIN_PRESSURE = 12,
+        SIZE_KALMAN_MIN_PRESSURE = 12,
+        SIZE_DIGITAL_MIN_PRESSURE = 13,
+        SIZE_BARO_MAX_ALTITUTDE  = 12,
+        SIZE_GPS_MAX_ALTITUDE = 10,
+        SIZE_APOGEE_LAT = 10,
+        SIZE_APOGEE_LON = 10,
+        SIZE_DROGUE_DPL_TS = 25,
+        SIZE_DROGUE_DPL_MAX_ACC = 11,
+        SIZE_MAIN_DPL_TS = 25,
+        SIZE_MAIN_DPL_ALTITUDE = 13,
+        SIZE_MAIN_DPL_ZSPEED = 10,
+        SIZE_MAIN_DPL_ACC = 11
+    };
+
+    uint8_t *packet;
+    BitPacker bitpacker;
+};
\ No newline at end of file
diff --git a/bitpacking/requirements.txt b/bitpacking/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b4e01569861fa5001ba712bfb61688ed45a3f451
--- /dev/null
+++ b/bitpacking/requirements.txt
@@ -0,0 +1,20 @@
+cachetools==3.1.0
+certifi==2019.3.9
+chardet==3.0.4
+google-api-python-client==1.7.8
+google-auth==1.6.3
+google-auth-httplib2==0.0.3
+google-auth-oauthlib==0.3.0
+httplib2==0.12.3
+idna==2.8
+oauth2client==4.1.3
+oauthlib==3.0.1
+pkg-resources==0.0.0
+pyasn1==0.4.5
+pyasn1-modules==0.2.5
+requests==2.21.0
+requests-oauthlib==1.2.0
+rsa==4.0
+six==1.12.0
+uritemplate==3.0.0
+urllib3==1.24.2
diff --git a/bitpacking/store.json b/bitpacking/store.json
new file mode 100644
index 0000000000000000000000000000000000000000..8baac9df5268f3b5ef31dccd6735112edacb87b5
--- /dev/null
+++ b/bitpacking/store.json
@@ -0,0 +1 @@
+{"access_token": "ya29.ImGpB0kQ2N9LHGFRjUZpe8EQ35iCmsjt19OAmFQxg_gOqN-oIWhPDkZH-Htg6m6tXHZCujHjuVTZdK4WtMluYzAddhRmhxece9N-xjgIPYxSIVolAJQp90U1JWgYjeBeplRs", "client_id": "1025168905991-tv31etsgm3lecodc5c798shqciekad40.apps.googleusercontent.com", "client_secret": "Yhaf67HuHR4DyXZKNXWu2lre", "refresh_token": "1/GVZfj7UPkTTZg9juq5hDEvwprbaeD8UbHjyrphyuFR4", "token_expiry": "2019-10-29T22:10:15Z", "token_uri": "https://www.googleapis.com/oauth2/v3/token", "user_agent": null, "revoke_uri": "https://oauth2.googleapis.com/revoke", "id_token": null, "id_token_jwt": null, "token_response": {"access_token": "ya29.ImGpB0kQ2N9LHGFRjUZpe8EQ35iCmsjt19OAmFQxg_gOqN-oIWhPDkZH-Htg6m6tXHZCujHjuVTZdK4WtMluYzAddhRmhxece9N-xjgIPYxSIVolAJQp90U1JWgYjeBeplRs", "expires_in": 3600, "scope": "https://www.googleapis.com/auth/spreadsheets.readonly", "token_type": "Bearer"}, "scopes": ["https://www.googleapis.com/auth/spreadsheets.readonly"], "token_info_uri": "https://oauth2.googleapis.com/tokeninfo", "invalid": false, "_class": "OAuth2Credentials", "_module": "oauth2client.client"}
\ No newline at end of file
diff --git a/bitpacking/templates/ConversionFunctions.h.template b/bitpacking/templates/ConversionFunctions.h.template
new file mode 100644
index 0000000000000000000000000000000000000000..ab87ada1f579d07f3a11e6d34b8108382363960f
--- /dev/null
+++ b/bitpacking/templates/ConversionFunctions.h.template
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2019 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+#include <cstdint>
+#include <type_traits>
+
+template<typename T>
+inline uint64_t discretizeRange(T value, T min, T max, unsigned int resolution)
+{
+    static_assert(std::is_arithmetic<T>::value);
+
+    if (value < min)
+    {
+        return 0;
+    }
+    else if (value > max)
+    {
+        return resolution - 1;
+    }
+
+    return (uint64_t)((value - min) * resolution / (max - min));
+
+}
+
+template <typename T>
+inline T undiscretizeRange(uint64_t value, T min, T max,
+                           unsigned int resolution)
+{
+    static_assert(std::is_arithmetic<T>::value);
+
+    return value * (max - min) / resolution + min;
+}
+
+
+$classes
\ No newline at end of file
diff --git a/bitpacking/templates/Packer.h.template b/bitpacking/templates/Packer.h.template
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bitpacking/templates/TelemetryPacker.h.template b/bitpacking/templates/TelemetryPacker.h.template
new file mode 100644
index 0000000000000000000000000000000000000000..17476f47ec8bae9485434a0b8bd628221832b51a
--- /dev/null
+++ b/bitpacking/templates/TelemetryPacker.h.template
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2019 Skyward Experimental Rocketry
+ * Authors: Luca Conterio, Luca Erbetta
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include "BitPacker.h"
+#include "mavlink_lib/hermes/mavlink.h"
+#include "ConversionFunctions.h"
+
+class ${telemetry_name_ccase}Packer
+{
+public:
+    static constexpr int ${mav_tm_name}_PACKET_SIZE            = $tm_total_size;
+    static constexpr int ${mav_tm_name}_SINGLE_PACKET_SIZE_BITS = $tm_partial_size;
+
+    static_assert(MAVLINK_MSG_${mav_tm_name}_FIELD_PAYLOAD_LEN == ${mav_tm_name}_PACKET_SIZE,
+                  "Payload size mismatch! Mavlnk payload size differes from declared size. Maybe your mavlink definitions are outdated?");
+
+
+
+
+    ${telemetry_name_ccase}Packer(uint8_t *packet) : packet(packet), bitpacker(packet, ${mav_tm_name}_PACKET_SIZE)
+    {
+    }
+
+    $pack_functions
+
+    $unpack_functions
+
+private:
+    enum FieldIndex
+    {
+$field_indices
+    };
+
+    enum FieldSize
+    {
+$field_sizes
+    };
+
+    uint8_t *packet;
+    BitPacker bitpacker;
+};
\ No newline at end of file
diff --git a/bitpacking/templates/packFunction.cpp.template b/bitpacking/templates/packFunction.cpp.template
new file mode 100644
index 0000000000000000000000000000000000000000..d8cfd768f48f433916f39891029b048794c1b671
--- /dev/null
+++ b/bitpacking/templates/packFunction.cpp.template
@@ -0,0 +1,15 @@
+
+    bool pack${field_name_ccase}($type $field_name_lcase, size_t packet_index)
+    {
+        if(packet_index >= $max_index)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits = $convert_fun($field_name_lcase);
+
+        return bitpacker.pack(
+            INDEX_$field_name_ucase + packet_index * ${mav_tm_name}_SINGLE_PACKET_SIZE_BITS,
+            SIZE_$field_name_ucase, bits);
+    }
diff --git a/bitpacking/templates/unpackFunction.cpp.template b/bitpacking/templates/unpackFunction.cpp.template
new file mode 100644
index 0000000000000000000000000000000000000000..ecdb4b65d606db04c9704e0bfe42da38d12b7bed
--- /dev/null
+++ b/bitpacking/templates/unpackFunction.cpp.template
@@ -0,0 +1,25 @@
+
+    bool unpack${field_name_ccase}($type* $field_name_lcase, size_t packet_index)
+    {
+        if(packet_index >= $max_index)
+        {
+            return false;
+        }
+
+        // Convert data to a suitable format and store in an unsigned int
+        uint64_t bits;
+
+        bool success = bitpacker.unpack(
+            INDEX_$field_name_ucase + packet_index * ${mav_tm_name}_SINGLE_PACKET_SIZE_BITS,
+            SIZE_$field_name_ucase, &bits);
+        
+        if(success)
+        {
+            *$field_name_lcase = $convert_fun(bits);
+            return true;
+        }else
+        {
+            return false;
+        }
+
+    }
diff --git a/utils/tests/Makefile b/bitpacking/tests/Makefile
similarity index 68%
rename from utils/tests/Makefile
rename to bitpacking/tests/Makefile
index 753796148abf9f5c6dcd299dfce2fcb68cc62424..2ef8853080508ff8ef5ab7633846ba6bda78c35a 100644
--- a/utils/tests/Makefile
+++ b/bitpacking/tests/Makefile
@@ -9,5 +9,7 @@ test-bitpacker: test-bitpacker.cpp include/catch.hpp
 manual-test-bitpacker: manual-test-bitpacker.cpp
 	g++ -O2 -o manual-test-bitpacker manual-test-bitpacker.cpp
 
+test-telemetry: test-telemetry.cpp $(wildcard ../hermes/*.h)
+	g++ -O2 -o test-telemetry test-telemetry.cpp -I../ -I../../
 clean:
-	rm test-bitpacker manual-test-bitpacker
+	rm -rvf test-bitpacker manual-test-bitpacker test-telemetry
diff --git a/utils/tests/include/catch.hpp b/bitpacking/tests/include/catch.hpp
similarity index 100%
rename from utils/tests/include/catch.hpp
rename to bitpacking/tests/include/catch.hpp
diff --git a/utils/tests/manual-test-bitpacker.cpp b/bitpacking/tests/manual-test-bitpacker.cpp
similarity index 100%
rename from utils/tests/manual-test-bitpacker.cpp
rename to bitpacking/tests/manual-test-bitpacker.cpp
diff --git a/utils/tests/test-bitpacker.cpp b/bitpacking/tests/test-bitpacker.cpp
similarity index 100%
rename from utils/tests/test-bitpacker.cpp
rename to bitpacking/tests/test-bitpacker.cpp
diff --git a/bitpacking/tests/test-telemetry b/bitpacking/tests/test-telemetry
new file mode 100755
index 0000000000000000000000000000000000000000..1a19f624d87a85379c4a2266736608ec73f5d636
Binary files /dev/null and b/bitpacking/tests/test-telemetry differ
diff --git a/bitpacking/tests/test-telemetry.cpp b/bitpacking/tests/test-telemetry.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..197225b50d0042aaebff4ae5fed2beb70f6deb81
--- /dev/null
+++ b/bitpacking/tests/test-telemetry.cpp
@@ -0,0 +1,26 @@
+#define CATCH_CONFIG_MAIN
+#include "include/catch.hpp"
+
+#include <cstdio>
+#include "hermes/HermesPackets.h"
+
+uint8_t buf[HighRateTMPacker::HR_TM_PACKET_SIZE];
+
+TEST_CASE("Test HR_TELEMETRY")
+{
+    HighRateTMPacker packer{buf};
+    packer.packTimestamp(12345678, 0);
+    packer.packTimestamp(123, 1);
+    packer.packTimestamp(1111111, 2);
+
+    long long ts;
+    REQUIRE(packer.unpackTimestamp(&ts, 0));
+    REQUIRE(ts == 12345678);
+
+    REQUIRE(packer.unpackTimestamp(&ts, 1));
+    REQUIRE(ts == 123);
+
+    REQUIRE(packer.unpackTimestamp(&ts, 2));
+    REQUIRE(ts == 1111111);
+
+}
\ No newline at end of file