diff --git a/README.md b/README.md
index dcdc52c912a763d408ee3f324d7f2b5cf55d2b0c..cb89a05ca57317be423e05415fdd6cd0589bbfc8 100644
--- a/README.md
+++ b/README.md
@@ -9,10 +9,10 @@ Helper library for easy usp communication using mqtt over tcp or ws.
 
 `npm install usp-js`
 
-# Usage
+# Quick Example
 
-To connect provide necessary info to the default export. (Values will differ depending on setup).
-Note: for browser usage make sure to import from usp-js/web.
+To connect provide necessary info to the default export.
+Note: for browser usage make sure to import the library from usp-js/web.
 
 ```javascript
 const connect = require("usp-js").default;
@@ -39,203 +39,365 @@ run();
 
 # API
 
-- Connect
+### connect
+
+The most commonly used connection options (usp-js extends the connection options provided by [mqtt.js](https://github.com/mqttjs/MQTT.js#mqttconnecturl-options)):
+
+- host (string): host address,
+- protocol (string): protocol to use (i.e. ws, wss, mqtt, etc),
+- port (number): port number,
+- username (string): log in username,
+- password (string): log in password,
+- useLatestUSPVersion (boolean): if set to true will automatically retrieve and use latest version of usp protocol available on the device (can instead be set manually by using the "version" option)
+
+The connect function also has several events that can be used to keep track of the connection state:
+
+- onError ((err: string) => void): Handler for client errors, will not trigger on usp error (for instance trying to get a non-existent path),
+- onOffline (() => void): triggers when connection goes offline,
+- onReconnect (() => void): triggers when connection reconnects (can use options like "reconnectsBeforeClosing" to control reconnection attempts),
+- onClose (() => void): triggers when connection is closed
+
+Example
+
+```typescript
+const usp = await connect(
+  {
+    host: "host.ip",
+    protocol: "ws",
+    port: 9001,
+    username: "username",
+    password: "password",
+    useLatestUSPVersion: true,
+  },
+  {
+    onError: (err) => console.error("connection error:", err),
+    onOffline: () => console.log("connection offline"),
+    onReconnect: () => console.log("connection reconnecting"),
+    onClose: () => console.log("connection closed"),
+  }
+);
+```
+
+### get
+
+- get single item
 
 ```javascript
-// options are based on https://github.com/mqttjs/MQTT.js#mqttconnecturl-options
-const usp = await connect(options);
+const result = await usp.get("Device.Time.");
 ```
 
-> Additional connection options:
+- get raw message
 
-> - fromId: Source ID
-> - toId: Destination ID
-> - closeOnDisconnect: Close connection if disconnected
-> - reconnectsBeforeClosing: Number of times to attempt reconnecting before closing connection
-> - connectTimeout: Timeout in milliseconds for connection
+Returns full USP message as sent by the device, mostly useful for debugging purposes.
 
-- Get
+```javascript
+const result = await usp.get("Device.Time.", { raw: true });
+```
 
-  - get object
+- get with max depth (requires usp version 1.2)
 
-  ```javascript
-  await usp.get("Device.Time.");
-  // => {
-  //   "CurrentLocalTime": "2020-12-15T12:33:19Z",
-  //   "Enable": true,
-  //   ...
-  // }
-  ```
+Max depth determines how granular the result is for large objects, defaults to 2.
 
-  - get property
+```javascript
+const result = await usp.get("Device.Time.", { max_depth: 2 });
+```
 
-  ```javascript
-  await usp.get("Device.Time.CurrentLocalTime"); // => "2020-12-15T12:33:19Z"
-  ```
+- get nested
 
-  - get multiple paths
+Results include "\_\_query\_\_" variable which holds the full path
 
-  ```javascript
-  await usp.get(["Device.WiFi.Radio.1.", "Device.WiFi.Radio.2."]);
-  // => [
-  // { ... },
-  // { ... }
-  // ]
-  ```
+```javascript
+await usp.get("Device.NAT.PortMapping.");
+const standardGet = [
+  {
+    Status: "Disabled",
+    RemoteHost: "",
+    Protocol: "TCP",
+    LeaseDuration: "0",
+    InternalPort: "0",
+    InternalClient: "",
+    Interface: "",
+    ExternalPortEndRange: "0",
+    ExternalPort: "0",
+    Enable: "0",
+    Description: "cat-1",
+    AllInterfaces: "0",
+    Alias: "cpe-1",
+  },
+];
+
+await usp.getNested("Device.NAT.PortMapping.");
+const nestedGet = {
+  __query__: "Device.NAT.PortMapping.",
+  result: [
+    {
+      __query__: "Device.NAT.PortMapping.1.",
+      Alias: "cpe-1",
+      AllInterfaces: "0",
+      Description: "cat-1",
+      Enable: "0",
+      ExternalPort: "0",
+      ExternalPortEndRange: "0",
+      Interface: "",
+      InternalClient: "",
+      InternalPort: "0",
+      LeaseDuration: "0",
+      Protocol: "TCP",
+      RemoteHost: "",
+      Status: "Disabled",
+    },
+  ],
+};
+```
 
-  - get using pattern
+- get with error
 
-  ```javascript
-  await usp.get('Device.Ethernet.Interface.[Alias=="WAN"].CurrentBitRate'); // => 0
-  ```
+USP errors will be thrown from the get call, while connection errors can be caught using the onError handler when connecting (example above)
 
-  - resolve references in get
+```javascript
+await usp.get("Fake.Path").then(console.log).catch(console.error);
+```
 
-  ```javascript
-  await usp.get("Device.WiFi.Radio.1.").then(usp.resolve); // => { ... }
-  // or if deeper resolution is needed (be careful when using level, going above 3 often causes an infinite reference loop)
-  await usp
-    .get("Device.WiFi.Radio.1.")
-    .then((msg) => usp.resolve(msg, 3 /* level - defaults to 1 */)); // => { ... }
-  ```
+- get multiple paths
 
-- Set
+Can retrieve multiple paths at the same time, result will always be in an array.
 
-  - set object - does not need to have all attributes, but some may be required (check USP Reference)
+```javascript
+await usp.get(["Device.WiFi.Radio.1.Alias", "Device.WiFi.Radio.2.Alias"]);
+```
 
-  ```javascript
-  await usp.set("Device.WiFi.Radio.1.", { Name: "radio-1" }); // => void
-  ```
+- get using pattern
 
-  - set object with allowPartial and required attributes
+Can use search patterns to search for specific object, results are always in an array (patterns are based on USP implementation, they are not handled by usp-js).
 
-  ```javascript
-  await usp.set("Device.WiFi.Radio.1.", {
-    Name: { required: true, value: "radio-1" },
-    allowPartial: true,
-  }); // => void
-  ```
+```javascript
+await usp.get('Device.Ethernet.Interface.[Alias=="WAN"].CurrentBitRate');
+```
 
-  - set property
+- resolve references in get
 
-  ```javascript
-  await usp.set("Device.WiFi.Radio.1.Name", "radio-1"); // => void
-  ```
+Many objects in the model hold references to other objects. To resolve those quickly, use the resolve function. Same functionality can be achieved by manually retrieving references and using get on them, resolve is just for convenience. Resolve also provides an optional level argument (defaults to 1) which allows retrieving multiple levels of references. Can be useful for complicated objects but it is best to avoid setting it to values above 3 as there can be circular references.
 
-- Operate
+```javascript
+await usp.get("Device.NAT.InterfaceSetting.1.");
+const normalResult = {
+  Status: "Disabled",
+  Interface: "Device.IP.Interface.1",
+  Enable: "0",
+  Alias: "cpe-1",
+};
 
-  ```javascript
-  const [ping, cleanPing] = await usp.operate("Device.IP.Diagnostics.IPPing()");
-  const results = await ping({ Host: "iopsys.eu" });
-  await cleanPing(); // clears ping subscription (optional)
-  ```
+await usp.get("Device.NAT.InterfaceSetting.1.").then(usp.resolve);
+// some values skipped for brevity
+const resolvedResult = {
+  Status: "Disabled",
+  Interface: {
+    ULAEnable: "1",
+    Type: "Normal",
+    TWAMPReflectorNumberOfEntries: "0",
+    Status: "Down",
+    Enable: "1",
+    Alias: "cpe-1",
+  },
+  Enable: "0",
+  Alias: "cpe-1",
+};
+```
 
-- Add
+### set
 
-  - add with no arguments - adds a new default object
+- set object - does not need to have all attributes, but some may be required (check USP Reference)
 
-  ```javascript
-  await usp.add("Device.NAT.PortMapping."); // => "Device.NAT.PortMapping.3."
-  ```
+```javascript
+await usp.set("Device.NAT.PortMapping.1.", { Description: "a very nice port" });
+// returns array of affected paths
+const result = [
+  {
+    affectedPath: "Device.NAT.PortMapping.1.",
+    updatedParams: {
+      Description: "a very nice port",
+    },
+  },
+];
+```
 
-  - add with multiple responses
+- set object with allowPartial and required attributes
 
-  ```javascript
-  await usp.add("Device.IP.Interface.*.IPv4Address."); // => ['Device.IP.Interface.1.IPv4Address.2.', ... ]
-  ```
+```javascript
+await usp.set("Device.WiFi.Radio.1.", {
+  Name: { required: true, value: "radio-1" },
+  allowPartial: true,
+});
+```
 
-  - add with arguments - adds a new object with provided values
+- set property
 
-  ```javascript
-  await usp.add("Device.NAT.PortMapping.", {
-    Description: "webserver1-set",
-    ExternalPort: "80",
-    Protocol: "TCP",
-    Interface: "Device.IP.Interface.1",
-    Enable: "true",
-    InternalClient: "192.168.1.125",
-    InternalPort: "5000",
-  }); // => "Device.NAT.PortMapping.4."
-  ```
-
-  - add with with allowPartial and required attributes
-
-  ```javascript
-  await usp.add("Device.NAT.PortMapping.", {
-    allowPartial: true,
-    Description: {
-      required: true,
-      value: "webserver1-set",
-    }
-    ExternalPort: "80",
-    Protocol: "TCP",
-    Interface: "Device.IP.Interface.1",
-    Enable: "true",
-    InternalClient: "192.168.1.125",
-    InternalPort: "5000",
-  }); // => "Device.NAT.PortMapping.4."
-  ```
+```javascript
+await usp.set("Device.WiFi.Radio.1.Name", "radio-1");
+```
 
-- Delete
+- set multiple
 
 ```javascript
-await usp.del("Device.NAT.PortMapping.4."); // => void
+await usp.set(
+  ["Device.WiFi.Radio.1.Name", "Device.WiFi.Radio.2."],
+  ["radio-1", { Name: "radio-2" }]
+);
+```
+
+### operate
+
+- Async command
+
+Operate assumes the command is async by default.
+
+```javascript
+const [ping, cleanPing] = await usp.operate("Device.IP.Diagnostics.IPPing()");
+const results = await ping({ Host: "iopsys.eu" });
+await cleanPing(); // clears ping subscription (optional)
 ```
 
-- Get Supported DM
+- Sync command
+
+```javascript
+const [reset] = await usp.operate("Device.FactoryReset()", {
+  isAsync: false,
+});
+const results = await reset();
+```
+
+- Command with subscription
+
+The returned operate command provides a way to subscribe to call results.
+
+```javascript
+const [ping, cleanPing] = await usp.operate("Device.IP.Diagnostics.IPPing()");
+ping.subscribe((msg) => console.log(msg));
+await ping({ Host: "iopsys.eu" });
+await ping({ Host: "google.se" });
+await cleanPing();
+```
+
+### add
+
+- add with no arguments - adds a new default object
+
+```javascript
+await usp.add("Device.NAT.PortMapping."); // => "Device.NAT.PortMapping.3."
+```
+
+- add with multiple responses
+
+```javascript
+await usp.add("Device.IP.Interface.*.IPv4Address."); // => ['Device.IP.Interface.1.IPv4Address.2.', ... ]
+```
+
+- add with arguments - adds a new object with provided values
+
+```javascript
+await usp.add("Device.NAT.PortMapping.", {
+  Description: "webserver1-set",
+  ExternalPort: "80",
+  Protocol: "TCP",
+  Interface: "Device.IP.Interface.1",
+  Enable: "true",
+  InternalClient: "192.168.1.125",
+  InternalPort: "5000",
+}); // => "Device.NAT.PortMapping.4."
+```
+
+- add with with allowPartial and required attributes
+
+```javascript
+await usp.add("Device.NAT.PortMapping.", {
+  allowPartial: true,
+  Description: {
+    required: true,
+    value: "webserver1-set",
+  }
+  ExternalPort: "80",
+  Protocol: "TCP",
+  Interface: "Device.IP.Interface.1",
+  Enable: "true",
+  InternalClient: "192.168.1.125",
+  InternalPort: "5000",
+}); // => "Device.NAT.PortMapping.4."
+```
+
+### del
+
+```javascript
+await usp.del("Device.NAT.PortMapping.4.");
+// returns list of affected paths
+const result = ["Device.NAT.PortMapping.4."];
+```
+
+### supportedDM
+
+Retrieves supported domain model.
+
+- standard call
 
 ```javascript
 await usp.supportedDM("Device.WiFi.");
 ```
 
-- Get Supported Protocols
+- retrieve entire domain model
 
 ```javascript
-await usp.supportedProto("Device.WiFi.");
+await usp.supportedDM("Device.", {
+  firstLevelOnly: false,
+  returnCommands: true,
+  returnEvents: true,
+  returnParams: true,
+});
 ```
 
-- Get Instances
+### supportedProto
+
+Retrieves supported protocols.
 
 ```javascript
-await usp.instances("Device.WiFi.");
+await usp.supportedProto("Device.");
 ```
 
-- Subscribe
+### instances
 
 ```javascript
-const clearSub = await usp.subscribe(
-  { id: "1234", notif: "ObjectCreation", reference: "Device.NAT.PortMapping." },
-  console.log
-);
+await usp.instances("Device.WiFi.");
 ```
 
-- optional second argument to callback gives access to full message.
+### subscribe
+
+The subscription callback provides both the formatted message and the raw USP message. The returned function provides an easy way to clear the subscription.
 
 ```javascript
-await usp.subscribe(
+const clearSub = await usp.subscribe(
   { id: "1234", notif: "ObjectCreation", reference: "Device.NAT.PortMapping." },
-  (_, fullMsg) => console.log(fullMsg)
+  (msg, fullMsg) => console.log({ msg, fullMsg })
 );
+
+await clearSub();
 ```
 
-- On
+### on
 
-  Id can be a string or a regexp. Messages, generally, have their id in the form COMMAND@random_string (i.e. NOTIFY@12345).
-  (Note: does not add subscription to USP model, instead allows for internal monitoring of received messages)
-  Optional second argument to callback gives access to full message.
+The "on" function is internal to usp-js. It provides a way to subscribe to incoming messages without directly interacting with the device (it will not add a subscription to the local agent). It is mostly used for debugging purposes.
+The first argument is the id of the message to subscribe to. It can be a string or a regexp. The general message id has the shape: COMMAND@random_string (i.e. NOTIFY@12345).
+The second argument is a callback that optionally provides access to the raw USP message.
 
 ```javascript
-const clear = usp.on(/NOTIFY.*/, (data, msg) => console.log({ data, msg }));
+const clear = usp.on(/NOTIFY.*/, (msg, rawMsg) => console.log({ msg, rawMsg }));
 ```
 
-- Options
+### options
 
-  Options allows extending commands with additional functionality.
+Options allows extending commands with additional functionality:
 
-  Props:
-  timeout -> timeout after given ms (throws error)
-  preCall -> called before each command, provides command name and arguments
-  postCall -> called after each command, provides command name, arguments and result/error
+- timeout: timeout after given ms (throws error)
+- preCall: called before each command, provides command name and arguments
+- postCall: called after each command, provides command name, arguments and result/error
+- get.retainPath: sets "retainPath" option to true for all get calls, equivalent to replacing getNeset everywhere
 
 ```javascript
 await usp
@@ -243,6 +405,107 @@ await usp
     timeout: 1000,
     preCall: (name, args) => console.log(name, args),
     postCall: (name, args, result) => console.log(name, args, result),
+    get: { retainPath: true },
   })
   .get("Device.DeviceInfo.SerialNumber");
 ```
+
+To apply the options globally add the call to the connect function.
+
+```javascript
+const usp = (await connect(connectionOptions)).options({
+  timeout: 1000,
+  preCall: (name, args) => console.log(name, args),
+  postCall: (name, args, result) => console.log(name, args, result),
+  get: { retainPath: true },
+});
+```
+
+### getUSPVersion
+
+Retrieves which usp version is currently being used by usp-js.
+
+```javascript
+usp.getUSPVersion(); // => "1.2"
+```
+
+### setUSPVersion
+
+Change which usp version is currently being used. This will only change which version usp-js is using, not the usp agent (mostly for debugging purposes). Generally, it is preferable to use the "useLatestUspVersion" option when connecting instead of setting the version manually.
+
+```javascript
+usp.setUSPVersion("1.2");
+```
+
+### disconnect
+
+Close connection.
+
+```javascript
+await usp.disconnect();
+```
+
+# Making a new connect function
+
+To allow more customization, new connect functions can be made to fit specific environments. By default, usp-js provides a version of connect for mqtt, ws and quickjs. To create a new connect, use the buildConnect function:
+
+```typescript
+const myConnect = buildConnect({
+  connectClient,
+  decodeID,
+  loadProtobuf,
+});
+
+const usp = await myConnect(connectionOptions);
+```
+
+- connectClient
+  The connectClient function is an async function that accepts connection options and returns a connection object. Below is an example of how the websocket client can be adapted:
+
+```typescript
+const connectClient: ConnectClientFn = (opts: any) =>
+  new Promise((resolve, reject) => {
+    const client = new WebSocketClient();
+
+    client.on("connectFailed", (error) => {
+      reject(error);
+    });
+
+    client.on("connect", (ws) => {
+      resolve({
+        on: (key, responseFn) => ws.on(key, (data) => responseFn(key, data)),
+        subscribe: (to) => null,
+        unsubscribe: (from) => null,
+        publish: (endpoint, msg) => ws.send(msg),
+        end: ws.close,
+      });
+    });
+
+    client.connect(
+      `${opts.protocol}://${opts.host}:${opts.port}/usp/endpoint`,
+      "v1.usp"
+    );
+  });
+```
+
+- decodeID
+  The decodeID function is used when a message arrives to retrieve the id from the raw data. The default function simply converts the data to a string:
+
+```typescript
+const decodeID = (data: any) => String(data);
+```
+
+- loadProtobuf
+  The loadProtobuf function is used to load the protobuf files used to convert incoming messages into javascript legible data. It is provided as an option to buildConnect, since some environment can be restrictive about how files are loaded. It is possible to import the loadProtobuf function from configurations/mqtt if no alterations are required.
+
+
+# Quickjs
+usp-js provides a quickjs compatible version. To use it, simply import from usp-js/qjs. 
+
+
+### Quickjs development
+Run the following command after making any changes to the base code:
+```
+yarn run qjs
+```
+This script needs to be run because compilation for quickjs requires several direct changes to the code.
\ No newline at end of file
diff --git a/src/commands/1.0,1.1/index.ts b/src/commands/1.0,1.1/index.ts
index bc09adfdc7ce0b2570020efa0ae7402cd9a9f7f5..ff8b4c56321a3362fc00224b01800f62466f9a1f 100644
--- a/src/commands/1.0,1.1/index.ts
+++ b/src/commands/1.0,1.1/index.ts
@@ -1,21 +1 @@
-import get from "./get";
-import set from "./set";
-import add from "./add";
-import del from "./del";
-import operate from "./operate";
-import supported from "./supported";
-import proto from "./proto";
-import instances from "./instances";
-import notify from "./notify";
-
-export default {
-  GET: get,
-  ADD: add,
-  DELETE: del,
-  GET_INSTANCES: instances,
-  GET_SUPPORTED_DM: supported,
-  GET_SUPPORTED_PROTO: proto,
-  NOTIFY: notify,
-  OPERATE: operate,
-  SET: set,
-};
+export default {};
diff --git a/src/commands/1.0,1.1/operate.ts b/src/commands/1.0,1.1/operate.ts
deleted file mode 100644
index 1dbbbe7dd0e11ee5d3cd2df3fb7ac1744ba78756..0000000000000000000000000000000000000000
--- a/src/commands/1.0,1.1/operate.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from "../common/operate";
\ No newline at end of file
diff --git a/src/commands/1.0,1.1/proto.ts b/src/commands/1.0,1.1/proto.ts
deleted file mode 100644
index cc691e9264a7433d44094eb422307e23626851e6..0000000000000000000000000000000000000000
--- a/src/commands/1.0,1.1/proto.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { DecodeFn, EncodeFn } from "../../types";
-import * as util from "../util";
-
-const decode: DecodeFn = (msg) => {
-  const results = util.search(msg, "agentSupportedProtocolVersions");
-  return [results.split(",")];
-};
-
-const encode: EncodeFn = ({ proto }) => ({
-  lookup: "Msg",
-  header: {
-    msgId: util.uniq("GET_SUPPORTED_PROTO@"),
-    msgType: "GET_SUPPORTED_PROTO",
-    lookup: "Header",
-  },
-  body: {
-    lookup: "Body",
-    request: {
-      lookup: "Request",
-      getSupportedProtocol: {
-        controllerSupportedProtocolVersions: proto || "",
-      },
-    },
-  },
-});
-
-export default {
-  decode,
-  encode,
-};
diff --git a/src/commands/1.0,1.1/set.ts b/src/commands/1.0,1.1/set.ts
deleted file mode 100644
index 22d6ab3c1214e52682805875594624ba2226aa26..0000000000000000000000000000000000000000
--- a/src/commands/1.0,1.1/set.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import {
-  DecodeFn,
-  EncodeFn,
-  SetLookupUpdateObject,
-  SetLookupUpdateParamSetting,
-} from "../../types";
-import * as util from "../util";
-import { decode as commonDecode } from "../common/set";
-
-const decode = commonDecode;
-
-const isObject = (v) =>
-  typeof v === "object" && v.required !== undefined && v.value !== undefined;
-
-const makePairs = (path: string, value): [string, any, boolean][] =>
-  path.endsWith(".") && typeof value === "object" && value !== null
-    ? Object.entries(value as any[]).map(([k, v]) =>
-        isObject(v)
-          ? [k, v.value.toString(), "required" in v && Boolean(v.required)]
-          : [k, v.toString(), true]
-      )
-    : [[path.split(".").pop() || "", (value || "").toString(), true]];
-
-const encode: EncodeFn = ({ value, path: initialPath }) => {
-  const paths = Array.isArray(initialPath) ? initialPath : [initialPath];
-  const values = value ? (Array.isArray(value) ? value : [value]) : [];
-  const allowPartial =
-    (values && values.some((it) => it.allowPartial)) || false;
-
-  const updateObjs = paths.map((path, i) => ({
-    lookup: "Set.UpdateObject" as SetLookupUpdateObject,
-    objPath: path.endsWith(".")
-      ? path
-      : path.slice(0, path.lastIndexOf(".") + 1),
-    paramSettings: makePairs(path, values[i])
-      .filter(([k]) => k !== "allowPartial")
-      .map(([param, value, required]) => ({
-        lookup: "Set.UpdateParamSetting" as SetLookupUpdateParamSetting,
-        param,
-        value,
-        required,
-      })),
-  }));
-
-  return {
-    lookup: "Msg",
-    header: {
-      lookup: "Header",
-      msgId: util.uniq("SET@"),
-      msgType: "SET",
-    },
-    body: {
-      lookup: "Body",
-      request: {
-        lookup: "Request",
-        set: {
-          allowPartial,
-          updateObjs,
-        },
-      },
-    },
-  };
-};
-
-export default {
-  decode,
-  encode,
-};
diff --git a/src/commands/1.2/add.ts b/src/commands/1.2/add.ts
deleted file mode 100644
index 96a18df49998e6b23c8eacbd169cddc91c10a58d..0000000000000000000000000000000000000000
--- a/src/commands/1.2/add.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { DecodeFn, EncodeFn } from "../../types";
-import * as util from "../util";
-import { AddLookupCreateObject, AddLookupCreateParamSetting } from "../../types";
-
-const decode: DecodeFn = (msg) => {
-  const paths: string[] | undefined = util.searchAll(msg, "instantiatedPath");
-  if (paths && paths.length === 1) return [paths[0]];
-  return [paths];
-};
-
-const isObj = (v) =>
-  typeof v === "object" && v.required !== undefined && v.value !== undefined;
-
-const makePair = (value): [string, any, boolean][] =>
-  value
-    ? Object.entries(value as any[]).map(([k, v]) =>
-        isObj(v)
-          ? [k, v.value.toString(), "required" in v && Boolean(v.required)]
-          : [k, v.toString(), true]
-      )
-    : [];
-
-const encode: EncodeFn = ({ value, path }) => {
-  const paths = Array.isArray(path) ? path : [path];
-  const values = value ? (Array.isArray(value) ? value : [value]) : [];
-  const allowPartial = values && values.some((it) => it.allowPartial) || false; 
-
-  const createObjs = paths.map((path, i) => ({
-    lookup: "Add.CreateObject" as AddLookupCreateObject,
-    objPath: path,
-    paramSettings: makePair(values[i])
-      .filter(([k]) => k !== "allowPartial")
-      .map(([param, value, required]) => ({
-        lookup: "Add.CreateParamSetting" as AddLookupCreateParamSetting,
-        param,
-        value,
-        required,
-      })),
-  }));
-
-  return {
-    lookup: "Msg",
-    header: {
-      lookup: "Header",
-      msgId: util.uniq("ADD@"),
-      msgType: "ADD",
-    },
-    body: {
-      lookup: "Body",
-      request: {
-        lookup: "Request",
-        add: {
-          allowPartial,
-          createObjs,
-        },
-      },
-    },
-  };
-};
-
-export default {
-  decode,
-  encode,
-};
diff --git a/src/commands/1.2/del.ts b/src/commands/1.2/del.ts
deleted file mode 100644
index 77bab504e95f5506da03363c8e4d42bde5aa3e6a..0000000000000000000000000000000000000000
--- a/src/commands/1.2/del.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { DecodeFn, EncodeFn } from "../../types";
-import { uniq, searchAll } from "../util";
-
-const decode: DecodeFn = (msg) => {
-  const affectedPaths = searchAll(msg, "affectedPaths") || [];
-  return [affectedPaths];
-};
-
-const encode: EncodeFn = ({ paths, allowPartial }) => ({
-  lookup: "Msg",
-  header: {
-    msgId: uniq("DELETE@"),
-    msgType: "DELETE",
-    lookup: "Header",
-  },
-  body: {
-    lookup: "Body",
-    request: {
-      lookup: "Request",
-      delete: {
-        objPaths: Array.isArray(paths) ? paths : [paths],
-        allowPartial: allowPartial || false
-      },
-    },
-  },
-});
-
-export default {
-  decode,
-  encode
-};
diff --git a/src/commands/1.2/get.ts b/src/commands/1.2/get.ts
deleted file mode 100644
index 71e73225a179dd8a68df9ac872fc7dc616e03c29..0000000000000000000000000000000000000000
--- a/src/commands/1.2/get.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { DecodeFn, EncodeFn } from "../../types";
-import * as util from "../util";
-
-const decode: DecodeFn = (msg, decodeOptions) => {
-  const reqPathResults = util.search(msg, "reqPathResults");
-  if (decodeOptions?.raw) return [reqPathResults];
-  if (reqPathResults) {
-    return [
-      util.processGetResult(reqPathResults as util.PathResult[], decodeOptions),
-    ];
-  }
-  return [null];
-};
-
-const encode: EncodeFn = ({ paths, options }) => ({
-  lookup: "Msg",
-  header: {
-    msgId: util.uniq("GET@"),
-    msgType: "GET",
-    lookup: "Header",
-  },
-  body: {
-    lookup: "Body",
-    request: {
-      lookup: "Request",
-      get: {
-        paramPaths: Array.isArray(paths) ? paths : [paths],
-        maxDepth: options?.max_depth || 2,
-      },
-    },
-  },
-});
-
-export default {
-  decode,
-  encode,
-};
diff --git a/src/commands/1.2/index.ts b/src/commands/1.2/index.ts
index bc09adfdc7ce0b2570020efa0ae7402cd9a9f7f5..ff8b4c56321a3362fc00224b01800f62466f9a1f 100644
--- a/src/commands/1.2/index.ts
+++ b/src/commands/1.2/index.ts
@@ -1,21 +1 @@
-import get from "./get";
-import set from "./set";
-import add from "./add";
-import del from "./del";
-import operate from "./operate";
-import supported from "./supported";
-import proto from "./proto";
-import instances from "./instances";
-import notify from "./notify";
-
-export default {
-  GET: get,
-  ADD: add,
-  DELETE: del,
-  GET_INSTANCES: instances,
-  GET_SUPPORTED_DM: supported,
-  GET_SUPPORTED_PROTO: proto,
-  NOTIFY: notify,
-  OPERATE: operate,
-  SET: set,
-};
+export default {};
diff --git a/src/commands/1.2/instances.ts b/src/commands/1.2/instances.ts
deleted file mode 100644
index 11d274e4809b6034c9f188b5a93f95a6b99e30e6..0000000000000000000000000000000000000000
--- a/src/commands/1.2/instances.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { DecodeFn, EncodeFn } from "../../types";
-import * as util from "../util";
-
-const decode: DecodeFn = (msg) => {
-  const results = util.search(msg, "reqPathResults");
-  return [results];
-};
-
-const encode: EncodeFn = ({ paths, opts = {} }) => ({
-  lookup: "Msg",
-  header: {
-    msgId: util.uniq("GET_INSTANCES@"),
-    msgType: "GET_INSTANCES",
-    lookup: "Header",
-  },
-  body: {
-    lookup: "Body",
-    request: {
-      lookup: "Request",
-      getInstances: {
-        objPaths: Array.isArray(paths) ? paths : [paths],
-        firstLevelOnly: opts.firstLevelOnly || false
-      },
-    },
-  },
-});
-
-export default {
-  decode,
-  encode
-};
diff --git a/src/commands/1.2/notify.ts b/src/commands/1.2/notify.ts
deleted file mode 100644
index 25ac2f963cc6eb7d0422d59479771404d4980e5c..0000000000000000000000000000000000000000
--- a/src/commands/1.2/notify.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { DecodeFn, EncodeFn } from "../../types";
-import * as util from "../util";
-
-const parseInfo = (key: string, data: Record<string, any>) =>
-  util.unflatten(
-    util.search(data, key === "operComplete" ? "outputArgs" : key) || {}
-  );
-
-const decode: DecodeFn = (msg) => {
-  const parent = util.searchParent(msg, "subscriptionId");
-  if (parent) {
-    const id = parent.subscriptionId;
-    const relField = Object.keys(parent).find((k) => k !== "subscriptionId");
-    return id && relField
-      ? [parseInfo(relField, msg), id, null]
-      : [null, id, null];
-  }
-  return [null];
-};
-
-const encode: EncodeFn = ({ paths }) => ({
-  lookup: "Msg",
-  header: {
-    msgId: util.uniq("NOTIFY@"),
-    msgType: "GET",
-    lookup: "Header",
-  },
-  body: {
-    lookup: "Body",
-    request: {
-      lookup: "Request",
-      get: {
-        paramPaths: Array.isArray(paths) ? paths : [paths],
-      },
-    },
-  },
-});
-
-export default {
-  decode,
-  encode,
-};
diff --git a/src/commands/1.2/operate.ts b/src/commands/1.2/operate.ts
deleted file mode 100644
index 1dbbbe7dd0e11ee5d3cd2df3fb7ac1744ba78756..0000000000000000000000000000000000000000
--- a/src/commands/1.2/operate.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from "../common/operate";
\ No newline at end of file
diff --git a/src/commands/1.2/set.ts b/src/commands/1.2/set.ts
deleted file mode 100644
index 22d6ab3c1214e52682805875594624ba2226aa26..0000000000000000000000000000000000000000
--- a/src/commands/1.2/set.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import {
-  DecodeFn,
-  EncodeFn,
-  SetLookupUpdateObject,
-  SetLookupUpdateParamSetting,
-} from "../../types";
-import * as util from "../util";
-import { decode as commonDecode } from "../common/set";
-
-const decode = commonDecode;
-
-const isObject = (v) =>
-  typeof v === "object" && v.required !== undefined && v.value !== undefined;
-
-const makePairs = (path: string, value): [string, any, boolean][] =>
-  path.endsWith(".") && typeof value === "object" && value !== null
-    ? Object.entries(value as any[]).map(([k, v]) =>
-        isObject(v)
-          ? [k, v.value.toString(), "required" in v && Boolean(v.required)]
-          : [k, v.toString(), true]
-      )
-    : [[path.split(".").pop() || "", (value || "").toString(), true]];
-
-const encode: EncodeFn = ({ value, path: initialPath }) => {
-  const paths = Array.isArray(initialPath) ? initialPath : [initialPath];
-  const values = value ? (Array.isArray(value) ? value : [value]) : [];
-  const allowPartial =
-    (values && values.some((it) => it.allowPartial)) || false;
-
-  const updateObjs = paths.map((path, i) => ({
-    lookup: "Set.UpdateObject" as SetLookupUpdateObject,
-    objPath: path.endsWith(".")
-      ? path
-      : path.slice(0, path.lastIndexOf(".") + 1),
-    paramSettings: makePairs(path, values[i])
-      .filter(([k]) => k !== "allowPartial")
-      .map(([param, value, required]) => ({
-        lookup: "Set.UpdateParamSetting" as SetLookupUpdateParamSetting,
-        param,
-        value,
-        required,
-      })),
-  }));
-
-  return {
-    lookup: "Msg",
-    header: {
-      lookup: "Header",
-      msgId: util.uniq("SET@"),
-      msgType: "SET",
-    },
-    body: {
-      lookup: "Body",
-      request: {
-        lookup: "Request",
-        set: {
-          allowPartial,
-          updateObjs,
-        },
-      },
-    },
-  };
-};
-
-export default {
-  decode,
-  encode,
-};
diff --git a/src/commands/1.2/supported.ts b/src/commands/1.2/supported.ts
deleted file mode 100644
index cd3d9ce38eb389f5b7c6dbcf73aa59d90dc91988..0000000000000000000000000000000000000000
--- a/src/commands/1.2/supported.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { DecodeFn, EncodeFn } from "../../types";
-import * as util from "../util";
-
-const decode: DecodeFn = (msg) => {
-  const results = util.search(msg, "reqObjResults");
-  return [results];
-};
-
-const encode: EncodeFn = ({ paths, opts = {} }) => ({
-  lookup: "Msg",
-  header: {
-    msgId: util.uniq("GET_SUPPORTED_DM@"),
-    msgType: "GET_SUPPORTED_DM",
-    lookup: "Header",
-  },
-  body: {
-    lookup: "Body",
-    request: {
-      lookup: "Request",
-      getSupportedDm: {
-        objPaths: Array.isArray(paths) ? paths : [paths],
-        firstLevelOnly: opts.firstLevelOnly || false,
-        returnCommands: opts.returnCommands || false,
-        returnEvents: opts.returnEvents || false,
-        returnParams: opts.returnParams || false,
-      },
-    },
-  },
-});
-
-export default {
-  decode,
-  encode
-};
diff --git a/src/commands/1.0,1.1/add.ts b/src/commands/common/add.ts
similarity index 100%
rename from src/commands/1.0,1.1/add.ts
rename to src/commands/common/add.ts
diff --git a/src/commands/1.0,1.1/del.ts b/src/commands/common/del.ts
similarity index 100%
rename from src/commands/1.0,1.1/del.ts
rename to src/commands/common/del.ts
diff --git a/src/commands/1.0,1.1/get.ts b/src/commands/common/get.ts
similarity index 100%
rename from src/commands/1.0,1.1/get.ts
rename to src/commands/common/get.ts
diff --git a/src/commands/common/index.ts b/src/commands/common/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2414d099c317411af58673444880165fbc39ccc7
--- /dev/null
+++ b/src/commands/common/index.ts
@@ -0,0 +1,21 @@
+import get from "./get";
+import set from "./set";
+import add from "./add";
+import del from "./del";
+import operate from "./operate";
+import supported from "./supported";
+import proto from "./proto";
+import instances from "./instances";
+import notify from "./notify";
+
+export default {
+  GET: get,
+  GET_INSTANCES: instances,
+  GET_SUPPORTED_DM: supported,
+  GET_SUPPORTED_PROTO: proto,
+  NOTIFY: notify,
+  ADD: add,
+  DELETE: del,
+  OPERATE: operate,
+  SET: set,
+};
diff --git a/src/commands/1.0,1.1/instances.ts b/src/commands/common/instances.ts
similarity index 100%
rename from src/commands/1.0,1.1/instances.ts
rename to src/commands/common/instances.ts
diff --git a/src/commands/1.0,1.1/notify.ts b/src/commands/common/notify.ts
similarity index 100%
rename from src/commands/1.0,1.1/notify.ts
rename to src/commands/common/notify.ts
diff --git a/src/commands/1.2/proto.ts b/src/commands/common/proto.ts
similarity index 100%
rename from src/commands/1.2/proto.ts
rename to src/commands/common/proto.ts
diff --git a/src/commands/common/set.ts b/src/commands/common/set.ts
index 67ee6c4d3acb0cbefa0929df95d8896bb0a53689..31fede982d86b210395569ee840f6c7f53d3604f 100644
--- a/src/commands/common/set.ts
+++ b/src/commands/common/set.ts
@@ -1,8 +1,71 @@
 import { DecodeFn } from "../../types";
 import { searchAll } from "../util";
+import {
+  EncodeFn,
+  SetLookupUpdateObject,
+  SetLookupUpdateParamSetting,
+} from "../../types";
+import * as util from "../util";
 
-export const decode: DecodeFn = (msg, decodeOptions) => {
-  if (decodeOptions?.raw) return [msg]
+const decode: DecodeFn = (msg, decodeOptions) => {
+  if (decodeOptions?.raw) return [msg];
   const affected = searchAll(msg, "updatedInstResults");
   return [affected];
 };
+const isObject = (v) =>
+  typeof v === "object" && v.required !== undefined && v.value !== undefined;
+
+const makePairs = (path: string, value): [string, any, boolean][] =>
+  path.endsWith(".") && typeof value === "object" && value !== null
+    ? Object.entries(value as any[]).map(([k, v]) =>
+        isObject(v)
+          ? [k, v.value.toString(), "required" in v && Boolean(v.required)]
+          : [k, v.toString(), true]
+      )
+    : [[path.split(".").pop() || "", (value || "").toString(), true]];
+
+const encode: EncodeFn = ({ value, path: initialPath }) => {
+  const paths = Array.isArray(initialPath) ? initialPath : [initialPath];
+  const values = value ? (Array.isArray(value) ? value : [value]) : [];
+  const allowPartial =
+    (values && values.some((it) => it.allowPartial)) || false;
+
+  const updateObjs = paths.map((path, i) => ({
+    lookup: "Set.UpdateObject" as SetLookupUpdateObject,
+    objPath: path.endsWith(".")
+      ? path
+      : path.slice(0, path.lastIndexOf(".") + 1),
+    paramSettings: makePairs(path, values[i])
+      .filter(([k]) => k !== "allowPartial")
+      .map(([param, value, required]) => ({
+        lookup: "Set.UpdateParamSetting" as SetLookupUpdateParamSetting,
+        param,
+        value,
+        required,
+      })),
+  }));
+
+  return {
+    lookup: "Msg",
+    header: {
+      lookup: "Header",
+      msgId: util.uniq("SET@"),
+      msgType: "SET",
+    },
+    body: {
+      lookup: "Body",
+      request: {
+        lookup: "Request",
+        set: {
+          allowPartial,
+          updateObjs,
+        },
+      },
+    },
+  };
+};
+
+export default {
+  decode,
+  encode,
+};
diff --git a/src/commands/1.0,1.1/supported.ts b/src/commands/common/supported.ts
similarity index 100%
rename from src/commands/1.0,1.1/supported.ts
rename to src/commands/common/supported.ts
diff --git a/src/commands/index.ts b/src/commands/index.ts
index d38dec5ed39fb70f6081645719540c630601643e..100b8b5f98de64079ce2159b254f0ecd5a17cd24 100644
--- a/src/commands/index.ts
+++ b/src/commands/index.ts
@@ -2,7 +2,6 @@ import { extractCommand, makeBuffer, search, searchParent } from "./util";
 import {
   CommandType,
   CallFn,
-  CommandObject,
   PbRequestMessage,
   RecipeObject,
   OnFn,
@@ -10,6 +9,7 @@ import {
   Proto,
   DecodeOptions,
   USPVersion,
+  CommandObject,
 } from "../types";
 
 import resolve from "./recipes/resolve";
@@ -18,13 +18,25 @@ import subscribe from "./recipes/subscribe";
 
 import v10 from "./1.0,1.1";
 import v12 from "./1.2";
+import common from "./common";
+
+// Note: version files are empty for now, they used to be necessary but were later made obsolete. I still keep them around in case version specific changes crop up again.
 
-const commands: Record<USPVersion, Record<CommandType, CommandObject>> = {
+const versionSpecificCommands = {
   "1.0": v10,
   "1.1": v10,
   "1.2": v12,
 };
 
+const getCommand = (
+  version: USPVersion,
+  commandType: CommandType
+): CommandObject | null =>
+  version in versionSpecificCommands &&
+  commandType in versionSpecificCommands[version]
+    ? versionSpecificCommands[version][commandType]
+    : common[commandType] || null;
+
 const recipes: RecipeObject[] = [resolve as any, operateRecipe, subscribe];
 
 export const makeRecipes = (call: CallFn, on: OnFn): any =>
@@ -57,7 +69,7 @@ export const decode: DecodeFn = (parsedMsg, version: USPVersion) => {
   const err = searchParent(parsedMsg, "errMsg") || null;
   const command = extractCommand(parsedMsg);
   const foundId = search(parsedMsg, "msgId");
-  
+
   // if id is formatted by me (command@) then use, otherwise check for sub id
   const id = foundId.includes("@")
     ? foundId
@@ -67,7 +79,7 @@ export const decode: DecodeFn = (parsedMsg, version: USPVersion) => {
   if (!command) return unkownErr(parsedMsg);
   if (err) return [id, null, err, command];
 
-  const cmd: CommandObject | null = commands[version][command] || null;
+  const cmd: CommandObject | null = getCommand(version, command);
   if (!cmd) return unkownErr(parsedMsg);
 
   const [decodedData, decodedId, decodedErr] = cmd.decode(parsedMsg);
@@ -87,8 +99,7 @@ export const decodeWithOptions: DecodeWithOptionsFn = (
   options,
   version: USPVersion
 ) => {
-  const cmd: CommandObject | null =
-    commands[version][cmdType as CommandType] || null;
+  const cmd: CommandObject | null = getCommand(version, cmdType as CommandType);
   if (!cmd) return unkownErr(parsedMsg);
 
   if (options.raw) return parsedMsg;
@@ -104,7 +115,7 @@ export const makeEncode =
     version: USPVersion,
     args: Record<string, any>
   ): [string, any, string | null] => {
-    const cmd = commands[version][command] || null;
+    const cmd = getCommand(version, command);
     if (!cmd) return ["error", null, `Uknown command: ${command}`];
     return [...convert(proto, cmd.encode(args), options)];
   };
diff --git a/src/configurations/mqtt.ts b/src/configurations/mqtt.ts
index ddfe04938626868dcd284a5190fcd8039240ebde..7015f2d490deb6743a3cfd4e9b5a9881e128db01 100644
--- a/src/configurations/mqtt.ts
+++ b/src/configurations/mqtt.ts
@@ -13,10 +13,10 @@ import buildConnect from "./build";
 import protobuf from "protobufjs";
 import { jsonRoots } from "./util";
 
-const isURL = (opts: ConnectionOptions): opts is URLConnectionOptions =>
+export const isURL = (opts: ConnectionOptions): opts is URLConnectionOptions =>
   "url" in opts;
 
-const connectClient: ConnectClientFn = async (opts) =>
+export const connectClient: ConnectClientFn = async (opts) =>
   isURL(opts)
     ? (mqttAsync.connectAsync(
         opts.url,
@@ -32,7 +32,7 @@ const connectClient: ConnectClientFn = async (opts) =>
       } as any) as unknown as ConnectionClient)
     : (mqttAsync.connectAsync(opts) as unknown as ConnectionClient);
 
-const loadProtobuf = async (version: USPVersion): Promise<Proto> => {
+export const loadProtobuf = async (version: USPVersion): Promise<Proto> => {
   const [rootRecordJson, rootMsgJson] = jsonRoots[version];
   const rootRecord = protobuf.Root.fromJSON(rootRecordJson);
   const rootMsg = protobuf.Root.fromJSON(rootMsgJson);