diff --git a/Makefile.am b/Makefile.am
index 2b1f3ca..c4fdd2b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -53,7 +53,9 @@ DXGEN_ = $(DXGEN_@AM_DEFAULT_V@)
DXGEN_0 = @printf " DXGEN $<\n";
SYSCONFDIR=@sysconfdir@
+LOCALSTATEDIR=@localstatedir@
export SYSCONFDIR
+export LOCALSTATEDIR
docs/.stamp: Doxyfile
$(DXGEN)$(DOXYGEN) $<
$(AM_V_at)touch $@
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index 1184c19..557b8df 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -81,6 +81,7 @@ install-exec-hook:
EXTRA_DIST += \
daemon/daemon.dox \
+ daemon/protocol.dox \
daemon/x52d.service.in \
daemon/x52d_clock.h \
daemon/x52d_config.def \
diff --git a/daemon/daemon.dox b/daemon/daemon.dox
index 9b74a49..4ec4bf0 100644
--- a/daemon/daemon.dox
+++ b/daemon/daemon.dox
@@ -18,6 +18,7 @@ the Windows X52 driver. It currently manages the following:
- \c -c - Path to configuration file
- \c -p - Path to PID file
- \c -o - Configuration override - only applied during startup
+- \c -s - Path to command socket (see \ref x52d_protocol)
# Configuration file
diff --git a/daemon/protocol.dox b/daemon/protocol.dox
new file mode 100644
index 0000000..a58fe1a
--- /dev/null
+++ b/daemon/protocol.dox
@@ -0,0 +1,238 @@
+/**
+@page x52d_protocol X52 daemon socket communication protocol
+
+The X52 daemon creates a Unix domain stream socket, by default at
+`$(LOCALSTATEDIR)/run/x52d.cmd` and listens for connection requests from
+clients at this location. This can be overridden by passing the -s flag when
+starting the daemon.
+
+# Protocol Overview
+
+\b x52d requires that clients send it commands as a series of NUL terminated
+strings, without any interleaving space. The command should be sent in a
+single `send` call, and the client may expect a response in a single `recv`
+call.
+
+The `send` call must send exactly the number of bytes in the command text.
+Extra bytes will be treated as additional arguments, which would cause the
+command to fail. It is recommended that the `recv` call uses a 1024 byte buffer
+to read the data. Responses will never exceed this length.
+
+# Responses
+
+The daemon sends the response as a series of NUL terminated strings, without
+any interleaving space. The first string is always one of the following:
+
+- \c OK
+- \c ERR
+- \c DATA
+
+This determines whether the request was successful or not, and subsequent
+strings describe the action, error or requested data.
+
+# Examples
+
+## Reloading configuration
+
+- \b send config\0reload\0
+- \b recv OK\0config\0reload\0
+
+## Reading mouse speed
+
+- \b send config\0get\0mouse\0speed\0
+- \b recv DATA\0mouse\0speed\010\0
+
+## Sending an invalid command
+
+- \b send config reload
+- \b recv ERR\0Unknown command 'config reload'\0
+
+# Commands
+
+\b x52d commands are arranged in a hierarchical fashion as follows:
+
+```
+ [ [ [...]]] []
+```
+
+The list of supported commands are shown below:
+
+- @subpage proto_config
+*/
+
+/**
+@page proto_config Configuration management
+
+The \c config commands deal with \b x52d configuration subsystem, and have the
+following subcommands.
+
+@tableofcontents
+
+# Load configuration
+
+The `config load` subgroup allows you to load a configuration from a given file
+(discarding anything that was already in memory), or reload the configuration
+from the command-line specified configuration file (or default configuration
+file if no option was given on the command line.)
+
+## Load from file
+
+\b Arguments
+
+- `config`
+- `load`
+- \a path-to-file
+
+\b Returns
+
+- `OK`
+- `config`
+- `load`
+- \a path-to-file
+
+\b Error
+
+- `ERR`
+- Invalid file '/none' for 'config load' command
+
+## Reload system configuration
+
+\b Arguments
+
+- `config`
+- `reload`
+
+\b Returns
+
+- `OK`
+- `config`
+- `reload`
+
+# Save configuration
+
+The `config save` subgroup requests the \b x52d daemon to save the current state
+of the configuration to disk. This is either the system configuration file, or
+may be a user specified configuration file. Note that this will be created with
+the permissions of the running daemon, which may be running as root.
+
+## Dump configuration to file
+
+\b Arguments
+
+- `config`
+- `dump`
+- \a path-to-file
+
+\b Returns
+
+- `OK`
+- `config`
+- `dump`
+- \a path-to-file
+
+\b Error
+
+- `ERR`
+- Invalid file '/none' for 'config dump' command
+
+## Save system configuration
+
+\b Arguments
+
+- `config`
+- `save`
+
+\b Returns
+
+- `OK`
+- `config`
+- `save`
+
+# Retrieve configuration parameter
+
+The `config get` command requests a specific configuration value, given the
+section and the key. Refer to \ref x52d for the section and key names, as these
+are derived from the base configuration.
+
+\b Arguments
+
+- `config`
+- `get`
+- \a section
+- \a key
+
+\b Returns
+
+- `DATA`
+- \a section
+- \a key
+- \a value
+
+\b Example
+
+```
+DATA\0mouse\0enabled\0true\0
+```
+
+Error example
+
+```
+ERR\0Error getting 'foo.bar'\0
+```
+
+# Set configuration parameter
+
+The `config set` command requests the \b x52d daemon to set the given (section,
+key) parameter to the given value. The daemon will treat it the same way as if
+it was being read from the configuration file, i.e., it will follow identical
+parsing logic, including ignoring unknown keys and not reporting errors for them.
+
+A side effect of this is that the client could request a set for any arbitrary
+section and key pair, and if that pair was not recognized, it would be ignored,
+but the daemon would still send an `OK` response.
+
+Finally, this will only set the value within the configuration memory
+structures, and will not invoke any callback to update the rest of the threads
+or device state. The client will need to call the `apply` subcommand to actually
+invoke the necessary callbacks.
+
+\b Arguments
+
+- `config`
+- `set`
+- \a section
+- \a key
+- \a value
+
+\b Returns
+
+- `OK`
+- `config`
+- `set`
+- \a section
+- \a key
+- \a value
+
+Error example
+
+```
+ERR\0Error 22 setting 'led.fire'='none': Invalid argument\0
+```
+
+# Apply configuration
+
+The `config apply` command will invoke all the callbacks and ensure that the
+configuration is applied to the running state.
+
+\b Arguments
+
+- `config`
+- `apply`
+
+\b Returns
+
+- `OK`
+- `config`
+- `apply`
+
+*/
diff --git a/daemon/x52ctl.c b/daemon/x52ctl.c
index 5ab3fd7..c2b0c60 100644
--- a/daemon/x52ctl.c
+++ b/daemon/x52ctl.c
@@ -29,6 +29,15 @@ If not running interactively, then you must specify a command, or the program
will exit with a failure exit code. If running interactively, the program will
request input and send that to the daemon, until the user either enters the
string "quit", or terminates input by using Ctrl+D.
+
+# OPTIONS
+
+- \b -i
+ Run in interactive mode. Any additional non-option arguments are ignored.
+
+- \b -s < \a socket-path >
+ Use the socket at the given path. If this is not specified, then it uses a
+ default socket.
*/
#include "config.h"
diff --git a/daemon/x52dcomm.h b/daemon/x52dcomm.h
index b4bc4a7..612fe23 100644
--- a/daemon/x52dcomm.h
+++ b/daemon/x52dcomm.h
@@ -57,8 +57,8 @@ int x52d_dial_command(const char *sock_path);
/**
* @brief Send a command to the daemon and retrieve the response.
*
- * The client sends the command and parameters as a single NULL terminated
- * string, and retrieves the response in the same manner. Depending on the
+ * The client sends the command and parameters as a series of NUL terminated
+ * strings, and retrieves the response in the same manner. Depending on the
* result, the return status is either a positive integer or -1, and \c errno
* is set accordingly.
*
diff --git a/po/libx52.pot b/po/libx52.pot
index 575f08f..cbb796a 100644
--- a/po/libx52.pot
+++ b/po/libx52.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: libx52 0.2.3\n"
"Report-Msgid-Bugs-To: https://github.com/nirenjan/libx52/issues\n"
-"POT-Creation-Date: 2021-11-07 16:01-0800\n"
+"POT-Creation-Date: 2021-11-07 16:07-0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -866,17 +866,17 @@ msgstr ""
msgid "Error %d creating X52 virtual mouse: %s"
msgstr ""
-#: daemon/x52ctl.c:50
+#: daemon/x52ctl.c:59
#, c-format
msgid "Usage: %s [-i] [-s socket-path] [command]\n"
msgstr ""
-#: daemon/x52ctl.c:66
+#: daemon/x52ctl.c:75
#, c-format
msgid "Argument length too long\n"
msgstr ""
-#: daemon/x52ctl.c:138
+#: daemon/x52ctl.c:147
#, c-format
msgid "Running in interactive mode, ignoring extra arguments\n"
msgstr ""
diff --git a/po/xx_PL.po b/po/xx_PL.po
index dd32087..3a9c416 100644
--- a/po/xx_PL.po
+++ b/po/xx_PL.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: libx52 0.2.3\n"
"Report-Msgid-Bugs-To: https://github.com/nirenjan/libx52/issues\n"
-"POT-Creation-Date: 2021-11-07 16:01-0800\n"
+"POT-Creation-Date: 2021-11-07 16:07-0800\n"
"PO-Revision-Date: 2021-11-04 15:35-0700\n"
"Last-Translator: Nirenjan Krishnan \n"
"Language-Team: Dummy Language for testing i18n\n"
@@ -918,17 +918,17 @@ msgstr "Irtualvay ousemay otnay eatedcray. Ignoringa eadthray atestay angechay"
msgid "Error %d creating X52 virtual mouse: %s"
msgstr "Erroray %d eatingcray X52 irtualvay ousemay: %s"
-#: daemon/x52ctl.c:50
+#: daemon/x52ctl.c:59
#, c-format
msgid "Usage: %s [-i] [-s socket-path] [command]\n"
msgstr "Usageay: %s [-i] [-s ocketsay-athpay] [ommandcay]\n"
-#: daemon/x52ctl.c:66
+#: daemon/x52ctl.c:75
#, c-format
msgid "Argument length too long\n"
msgstr "Argumentay engthlay ootay onglay\n"
-#: daemon/x52ctl.c:138
+#: daemon/x52ctl.c:147
#, c-format
msgid "Running in interactive mode, ignoring extra arguments\n"
msgstr "Unningray inay interactiveay odemay, ignoringay extraay argumentsay\n"