
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Quantum Proximity Gateway]]></title><description><![CDATA[Quantum Proximity Gateway]]></description><link>https://qpg.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Mon, 14 Sep 2026 12:37:24 GMT</lastBuildDate><atom:link href="https://qpg.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[18/03/25 - Docker Containerization & IBM Cloud Deployment]]></title><description><![CDATA[In order to deploy our server, we had to first containerize it. At first, we thought this would be relatively straightforward, but as it turned out, it was a lot more complex than we initially anticipated. First, there was the problem of building for...]]></description><link>https://qpg.hashnode.dev/180325-docker-containerization-and-ibm-cloud-deployment</link><guid isPermaLink="true">https://qpg.hashnode.dev/180325-docker-containerization-and-ibm-cloud-deployment</guid><dc:creator><![CDATA[Raghav Awasthi]]></dc:creator><pubDate>Tue, 18 Mar 2025 00:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742936438990/c13ccded-7f69-4a21-9d95-b35fd9a31748.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In order to deploy our server, we had to first containerize it. At first, we thought this would be relatively straightforward, but as it turned out, it was a lot more complex than we initially anticipated. First, there was the problem of building for different architectures (AMD64 vs ARM64). Then, we had to try to meet all of the requirements needed to build a Tauri app - in particular, the dlib requirement was extremely hard to satisfy. There were also a few other problems, and we will delve into our entire journey with dockerization in this blog post.</p>
<h2 id="heading-architecture-arm64-vs-amd64">Architecture: ARM64 vs AMD64</h2>
<p>Since we were running and testing our server on an M-series mac, which have ARM-based chips, we already had our dependencies for an ARM64 device. Therefore, we decided to start with trying to build for this architecture.</p>
<p>The only problem was that some of the requirements on a linux system were slightly different, but after a bit of trial and error, we eventually ended up with the following Dockerfile:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3.12</span>-slim

 <span class="hljs-comment"># Add application sources with correct permissions for OpenShift</span>
 <span class="hljs-keyword">USER</span> <span class="hljs-number">0</span>
 <span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

 <span class="hljs-comment"># Copy requirements files</span>
 <span class="hljs-keyword">COPY</span><span class="bash"> requirements.txt Pipfile Pipfile.lock ./</span>

 <span class="hljs-comment"># Install ALL system dependencies in one layer to reduce image size</span>
 <span class="hljs-keyword">RUN</span><span class="bash"> apt-get update &amp;&amp; apt-get install -y --no-install-recommends \
     build-essential \
     cmake \
     git \
     libssl-dev \
     ninja-build \
     libgl1-mesa-glx \
     libglib2.0-0 \
     libsm6 \
     libxext6 \
     libxrender1 \
     ffmpeg \
     &amp;&amp; apt-get clean \
     &amp;&amp; rm -rf /var/lib/apt/lists/*</span>

 <span class="hljs-comment"># Install Python dependencies</span>
 <span class="hljs-keyword">RUN</span><span class="bash"> pip install --no-cache-dir --upgrade pip &amp;&amp; \
     pip install --no-cache-dir opencv-python-headless &amp;&amp; \
     pip install --no-cache-dir -r requirements.txt</span>

 <span class="hljs-comment"># Copy the rest of the application code</span>
 <span class="hljs-keyword">COPY</span><span class="bash"> . .</span>

 <span class="hljs-comment"># For security, run as non-root user</span>
 <span class="hljs-keyword">RUN</span><span class="bash"> useradd -m appuser &amp;&amp; chown -R appuser:appuser /app</span>
 <span class="hljs-keyword">USER</span> appuser

 <span class="hljs-comment"># Expose port for the Litestar application</span>
 <span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">8000</span>

 <span class="hljs-comment"># Start the Litestar application with uvicorn</span>
 <span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"uvicorn"</span>, <span class="hljs-string">"app:app"</span>, <span class="hljs-string">"--host"</span>, <span class="hljs-string">"0.0.0.0"</span>, <span class="hljs-string">"--port"</span>, <span class="hljs-string">"8000"</span>]</span>
</code></pre>
<p>With this, we were finally able to run the docker file on an ARM64 linux machine. However, after trying to deploy this to IBM Cloud, we realized that all of the IBM Cloud hosts use AMD architecture.</p>
<p>We thought that most of the requirements should still be the same - that they will just be built for AMD architecture computers, but we were sorely mistaken. Amongst missing dlib requirements, long build times (and failures), and mismatched versions, we had to spend a significant amount of time trying to build for an AMD Linux system. In fact, at one point, we put it on hold while focusing on other parts of the project, even looking for alternative deployment solutions.</p>
<p>However, in the end, we finally managed to downgrade the python version, match the other dependency versions, and install all the necessities for dlib to run, and got a working container with AMD64. The Dockerfile for this looked like the following:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3.9</span>-slim

<span class="hljs-comment"># Set up a working directory</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-comment"># Install system dependencies required for dlib, OpenCV, and general builds</span>
<span class="hljs-keyword">RUN</span><span class="bash"> apt-get update &amp;&amp; apt-get install -y --no-install-recommends \
    build-essential \
    cmake \
    wget \
    curl \
    g++ \
    gcc \
    make \
    git \
    libssl-dev \
    ninja-build \
    libgtk2.0-dev \
    libboost-all-dev \
    libgl1-mesa-glx \
    libglib2.0-0 \
    libsm6 \
    libxext6 \
    libxrender1 \
    ffmpeg \
    libopenblas-dev \
    liblapack-dev \
    libx11-dev \
    libjpeg-dev \
    libpng-dev \
    libtiff-dev \
    libavcodec-dev \
    libavformat-dev \
    libswscale-dev \
    libxext-dev \
    libatlas-base-dev \
    libhdf5-dev \
    libv4l-dev \
    libxvidcore-dev \
    libx264-dev \
    libjxl-dev \
    pkg-config \
    python3-dev \
    python3-pip \
    python3-setuptools \
    python3-wheel \
    gnupg \
    &amp;&amp; apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*</span>

<span class="hljs-comment"># Install a specific version of CMake (3.25.2)</span>
<span class="hljs-keyword">RUN</span><span class="bash"> wget https://github.com/Kitware/CMake/releases/download/v3.25.2/cmake-3.25.2-linux-x86_64.sh -O /tmp/cmake.sh &amp;&amp; \
    chmod +x /tmp/cmake.sh &amp;&amp; \
    /tmp/cmake.sh --skip-license --prefix=/usr/<span class="hljs-built_in">local</span> &amp;&amp; \
    rm /tmp/cmake.sh &amp;&amp; \
    cmake --version</span>

<span class="hljs-comment"># Install Pipenv</span>
<span class="hljs-keyword">RUN</span><span class="bash"> pip install --no-cache-dir --upgrade pip &amp;&amp; \
    pip install --no-cache-dir pipenv</span>

<span class="hljs-comment"># Set environment variable to force Pipenv to use the same directory for the virtual environment</span>
<span class="hljs-keyword">ENV</span> PIPENV_VENV_IN_PROJECT=<span class="hljs-number">1</span>

<span class="hljs-comment"># Copy only Pipenv files first for better caching</span>
<span class="hljs-keyword">COPY</span><span class="bash"> Pipfile Pipfile.lock ./</span>

<span class="hljs-comment"># Install dependencies inside the virtual environment</span>
<span class="hljs-keyword">ARG</span> BUILD_PKGS=<span class="hljs-string">"wget build-essential cmake"</span>
<span class="hljs-keyword">ARG</span> CLANG_DEPS=<span class="hljs-string">""</span>
<span class="hljs-keyword">ARG</span> CLANG_PKGS=<span class="hljs-string">"clang-15 lldb-15 lld-15"</span>
<span class="hljs-keyword">RUN</span><span class="bash"> pipenv install --deploy --ignore-pipfile --python 3.9 &amp;&amp; \
    pipenv run pip install --no-cache-dir <span class="hljs-string">"numpy&lt;2"</span> &amp;&amp; \
    pipenv run pip install --no-cache-dir cmake==3.25.2</span>

<span class="hljs-comment"># Add LLVM repository key safely (fixes gnupg error)</span>
<span class="hljs-keyword">RUN</span><span class="bash"> wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor | tee /usr/share/keyrings/llvm-snapshot-keyring.gpg &gt; /dev/null</span>

<span class="hljs-comment"># Install LLVM toolchain</span>
<span class="hljs-keyword">RUN</span><span class="bash"> <span class="hljs-built_in">echo</span> <span class="hljs-string">"deb [signed-by=/usr/share/keyrings/llvm-snapshot-keyring.gpg] http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm main"</span> &gt;&gt; /etc/apt/sources.list &amp;&amp; \
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"deb-src [signed-by=/usr/share/keyrings/llvm-snapshot-keyring.gpg] http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm main"</span> &gt;&gt; /etc/apt/sources.list &amp;&amp; \
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"deb [signed-by=/usr/share/keyrings/llvm-snapshot-keyring.gpg] http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-15 main"</span> &gt;&gt; /etc/apt/sources.list &amp;&amp; \
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"deb-src [signed-by=/usr/share/keyrings/llvm-snapshot-keyring.gpg] http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-15 main"</span> &gt;&gt; /etc/apt/sources.list &amp;&amp; \
    apt-get update &amp;&amp; apt-get install -y clang-15 lldb-15 lld-15</span>

<span class="hljs-keyword">RUN</span><span class="bash"> pipenv run pip install --no-cache-dir opencv-python-headless imutils face-recognition setuptools litestar</span>

<span class="hljs-comment"># Copy the rest of the application</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>

<span class="hljs-comment"># Ensure correct user permissions</span>
<span class="hljs-keyword">RUN</span><span class="bash"> useradd -m appuser &amp;&amp; chown -R appuser:appuser /app</span>
<span class="hljs-keyword">USER</span> appuser

<span class="hljs-comment"># Expose the required port</span>
<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">8000</span>

<span class="hljs-comment"># Start the Litestar application with pipenv</span>
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"pipenv"</span>, <span class="hljs-string">"run"</span>, <span class="hljs-string">"python"</span>, <span class="hljs-string">"-m"</span>, <span class="hljs-string">"litestar"</span>, <span class="hljs-string">"run"</span>, <span class="hljs-string">"--host"</span>, <span class="hljs-string">"0.0.0.0"</span>, <span class="hljs-string">"--port"</span>, <span class="hljs-string">"8000"</span>]</span>
</code></pre>
<p>As one can see, the number of requirements significantly increased when changing architectures, and we also needed to install the LLVM toolchain, which we will now talk about.</p>
<h2 id="heading-why-do-we-need-the-llvm-toolchain">Why do we need the LLVM Toolchain?</h2>
<p>Firstly, an LLVM toolchain is a collection of modular and reusable compiler and toolchain technologies, such as Clang and LLDB. The main reason we wanted to install the LLVM toolchain explicitly like this came down to 3 distinct reasons:</p>
<ol>
<li><p>Better build compatibility: with Clang, some of our dependencies compiled much more smoothly (and quickly) than with GCC.</p>
</li>
<li><p>Advanced options: in general, Clang offers better optimization flags and debugging features for particular special use-cases (such as with cryptographic libraries and code which is geared towards performance).</p>
</li>
<li><p>Future-proofing: in the case that we needed to compile additional code using newer versions of C or C++, we wanted to ensure we already had LLVM.</p>
</li>
</ol>
<h2 id="heading-dlib-and-dependencies">Dlib and Dependencies</h2>
<p>As we mentioned earlier, there were a lot of requirements that needed to be satisfied for dlib to run on the AMD architecture. Dlib is generally known to be like this (having a lot of dependencies), especially when it comes to GPU-intensive or image processing features.</p>
<p>From the second Dockerfile example above, all of the <code>lib*</code> dependencies were installed solely for dlib - generally, whenever there are missing packages for dlib, these end up being system dev libraries. We had to spend a lot of time going through the error logs and cross-referencing the package names that we needed to install (because the names sometimes differ between Linux flavours).</p>
<h2 id="heading-pre-building-liboqs-and-multi-stage-building">Pre-building Liboqs and Multi-stage Building</h2>
<p>While trying to deploy to IBM Cloud, we realized that, every time we try to run the docker container, we were rebuilding liboqs from scratch. Obviously, this meant longer deployment times, and also meant that if something ever changed in the liboqs git repo (since we were cloning from there and building), we might end up breaking old code.</p>
<p>To counter this, we decided to already build liboqs and have it pre-built in the container so it wouldn’t have to rebuild from scratch every time the docker container is run. In order to do this, we had to do a multi-stage approach: first, clone and compile liboqs; then add the command <code>COPY --from=builder /usr/local /usr/local</code>, which copies the compiled files to the shared libraries directory to be used by the environment.</p>
<p>Going into a bit more detail for the multi-stage build, in the first stage, we installed all the necessary compiler tools and libraries, and then installed liboqs. The Dockerfile section for this was something like the code below.</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3.9</span>-slim AS builder

<span class="hljs-keyword">RUN</span><span class="bash"> apt-get update &amp;&amp; \
    apt-get install -y --no-install-recommends \
    git \
    cmake \
    ninja-build \
    build-essential \
    libssl-dev \
    &amp;&amp; apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*</span>

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /opt</span>

<span class="hljs-keyword">RUN</span><span class="bash"> git <span class="hljs-built_in">clone</span> --depth=1 --branch main https://github.com/open-quantum-safe/liboqs.git</span>

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /opt/liboqs</span>
<span class="hljs-keyword">RUN</span><span class="bash"> mkdir build &amp;&amp; <span class="hljs-built_in">cd</span> build &amp;&amp; \
    cmake -GNinja \
    -DBUILD_SHARED_LIBS=ON \
    -DCMAKE_INSTALL_PREFIX=/usr/<span class="hljs-built_in">local</span> .. &amp;&amp; \
    ninja &amp;&amp; \
    ninja install</span>
</code></pre>
<p>For the second and final stage, as we said before, we only copied the compiled artifacts from the builder image into a slimmer version of Python. We then kept the environment variables for the newly copied libraries, and finally installed the dependencies via pipenv as before. The changes to the code in second section are also shown below.</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3.9</span>-slim

<span class="hljs-keyword">COPY</span><span class="bash"> --from=builder /usr/<span class="hljs-built_in">local</span> /usr/<span class="hljs-built_in">local</span></span>
<span class="hljs-keyword">ENV</span> LD_LIBRARY_PATH=<span class="hljs-string">"/usr/local/lib:${LD_LIBRARY_PATH:-}"</span>

<span class="hljs-comment"># the rest is the same as before</span>
</code></pre>
<p>At this point, we were done containerizing our server, but we now had to actually deploy the server to IBM Cloud, which was a massive problem in and of itself.</p>
<h1 id="heading-march-update-video">March Update Video</h1>
<p>Below is a video that summarizes the above information and what we have worked on in the last month or so.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=gIj8c0EoBAk">https://www.youtube.com/watch?v=gIj8c0EoBAk</a></div>
]]></content:encoded></item><item><title><![CDATA[07/03/25 - Granite Hallucinations: NLP Fix]]></title><description><![CDATA[Have you ever had a conversation with a chatbot, and it decides to just make up some random stuff and not make any sense? That’s what was happening with the IBM Granite model we were using when only feeding it a large JSON and expecting it to automat...]]></description><link>https://qpg.hashnode.dev/070325-granite-hallucinations-nlp-fix</link><guid isPermaLink="true">https://qpg.hashnode.dev/070325-granite-hallucinations-nlp-fix</guid><dc:creator><![CDATA[Raghav Awasthi]]></dc:creator><pubDate>Fri, 07 Mar 2025 00:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742936400581/9eb92cc3-9f03-4820-b414-35528e31f7d7.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you ever had a conversation with a chatbot, and it decides to just make up some random stuff and not make any sense? That’s what was happening with the IBM Granite model we were using when only feeding it a large JSON and expecting it to automatically search/sort through and filter out the unnecessary information, retrieving the correct settings (and therefore commands) relevant to a user’s prompt. As a team, we referred to these random changes in the settings retrieved as “hallucinations”, because we weren’t entirely sure where these changes were coming from.</p>
<p>To counter these hallucinations, we needed to ensure Granite’s responses only included commands that were located in our JSON. Even though we gave the model a system prompt saying something along the lines of “ensure that your response commands only include commands included in the large JSON”, we still had no luck.</p>
<p>We went to our IBM mentor, Shalini Harkar, with this problem and had a conversation about how we could tackle this problem. After a plethora of discussion and briefly delving into various approaches, we finally reached the conclusion of using NLP to create a similarity score between the user’s prompt and the JSON settings options.</p>
<h2 id="heading-parsing-the-json-for-the-commands">Parsing the JSON for the Commands</h2>
<p>So, the first thing we needed to do was to parse the JSON so that, rather than including a bunch of extra information, we could strip it down to only include the necessary setting name (e.g. zoom) and the relevant command for the current Operating System.</p>
<p>To find the Operating System, we used Rust and Tauri’s own system - using the config and <code>target_os</code> which was being built for. We implemented it using something along the lines of the following code:</p>
<pre><code class="lang-rust"><span class="hljs-meta">#[tauri::command]</span>
<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">get_platform_info</span></span>() -&gt; <span class="hljs-built_in">String</span> {
    <span class="hljs-meta">#[cfg(target_os = <span class="hljs-meta-string">"macos"</span>)]</span>
    {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"macos"</span>.into();
    }
    <span class="hljs-meta">#[cfg(target_os = <span class="hljs-meta-string">"windows"</span>)]</span>
    {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"windows"</span>.into();
    }
    <span class="hljs-meta">#[cfg(target_os = <span class="hljs-meta-string">"linux"</span>)]</span>
    {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"linux"</span>.into();
    }
}
</code></pre>
<p>There is one caveat with the above code, though. With Llinux, we actually needed to ensure that the GUI running is GNOME or GNOME-based. This is because the gsettings commands we had saved in our JSON (since this application in its entirety is simply a prototype and doesn’t cover every single case of various OSes and GUIs), only work for GNOME-based GUIs running on Linux systems. Therefore, we used the environment variable <code>XDG_CURRENT_DESKTOP</code> and <code>DESKTOP_SESSION</code> to find out if the current GUI used GNOME. The code for that was as follows:</p>
<pre><code class="lang-rust"><span class="hljs-meta">#[cfg(target_os = <span class="hljs-meta-string">"linux"</span>)]</span>
<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">get_linux_gui</span></span>() -&gt; <span class="hljs-built_in">Option</span>&lt;<span class="hljs-built_in">String</span>&gt; {
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-literal">Ok</span>(desktop) = std::env::var(<span class="hljs-string">"XDG_CURRENT_DESKTOP"</span>) {
        <span class="hljs-keyword">if</span> desktop.to_lowercase().contains(<span class="hljs-string">"gnome"</span>) {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">Some</span>(<span class="hljs-string">"gnome"</span>.to_string());
        }
        <span class="hljs-keyword">return</span> <span class="hljs-literal">Some</span>(desktop);
    }

    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-literal">Ok</span>(session) = std::env::var(<span class="hljs-string">"DESKTOP_SESSION"</span>) {
        <span class="hljs-keyword">if</span> session.to_lowercase().contains(<span class="hljs-string">"gnome"</span>) {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">Some</span>(<span class="hljs-string">"gnome"</span>.to_string());
        }
        <span class="hljs-keyword">return</span> <span class="hljs-literal">Some</span>(session);
    }

    <span class="hljs-literal">None</span>
}
</code></pre>
<p>The above function (<code>get_linux_gui()</code>) was actually used in the <code>#[cfg(target_os = "linux")]</code> part of the <code>get_platform_info()</code> method, so in reality, the final part of the <code>get_platform_info()</code> function looked like the following:</p>
<pre><code class="lang-rust">    <span class="hljs-meta">#[cfg(target_os = <span class="hljs-meta-string">"linux"</span>)]</span>
    {
        <span class="hljs-keyword">let</span> frontend_env = get_linux_gui();
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-literal">Some</span>(env) = frontend_env {
            <span class="hljs-keyword">if</span> env.to_lowercase().contains(<span class="hljs-string">"gnome"</span>) {
                <span class="hljs-keyword">return</span> <span class="hljs-string">"gnome"</span>.into();
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">return</span> <span class="hljs-built_in">format!</span>(<span class="hljs-string">"linux-{env}"</span>).into();
            }
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-string">"linux-unknown"</span>.into();
        }
    }
</code></pre>
<h2 id="heading-attempt-1-jaro-winkler-similarity">Attempt 1: Jaro-Winkler Similarity</h2>
<p>The quickest way to avoid random suggestions was to compare the user’s request to each key in our JSON one at a time, using a string similarity algorithm. The very first algorithm we tried was the Jaro-Winkler algorithm. This is becuase it is very good at quickly measuring how similar two small strings are, and we wanted something that would work quickly and give the LLM the result as soon as possible to reduce the latency of the application.</p>
<p>However, only after implementing the algorithm did we realize that it is not actually very good when it has to deal with multiple words or phrases, let alone sentences. For example, even “increase the text size” is too long for Jaro-Winkler to give a high similarity score when being compared with the word “zoom” or “text-scaling-factor”. The partial matches with the other settings made things quite messy, and we ended up with an extremely irregular distribution of similarity scores, which had no correlation to the input.</p>
<p>Of course, for simple inputs such as “zoom in”, Jaro-Winkler performed splendidly well, but overall, this wasn’t a good enough algorithm for our purposes (which we realized as a result of extensive testing - including user testing). So, we had to find a different algorithm that could still generate a score quickly, while supporting relatively long phrases/sentences.</p>
<h2 id="heading-attempt-2-cosine-similarity">Attempt 2: Cosine Similarity</h2>
<p>Assuming that every word or phrase (also known as lemma in computational linguistics) can be represented by a vector in some higher-dimensional space, this method tries to find the angle between the vectors and eventually calculate a value between -1 and 1 in order to classify the similarity between the two lemmas. Cosine similarity is known to be really good at capturing the meaning of sentences, hence why we chose this algorithm.</p>
<p>The basics steps we went through were quite similar to the steps during the Jaro-Winkler attempt, but for the sake of clarity, I will explicitly outline them below:</p>
<p>1) Embed the JSON keys: first, we had to convert each key in the JSON (e.g. “zoom”, “cursor-size”, etc.) into its vector representation using a word embedding model.</p>
<p>2) Embed the user prompt: Similarly, we had to convert the user’s query into its own vector. Since it’s mostly going to be made up of multiple words/lemmas, we needed to add all these separate vectors together, but this was done by the library that we chose to use to implement this algorithm (<a target="_blank" href="https://github.com/CurrySoftware/rust-stemmers">Rust-Stemmers</a>).</p>
<p>3) Compute the cosine similarity: this was probably the easiest step, as we simply needed to convert the mathematical formula into code.</p>
<p>4) Choose the highest score: again, this step was fairly straightforward. From the list of scores we generated for each JSON setting, we just needed to get the absolute value for each of them and then select the key with the highest magnitude.</p>
<p>5) Filter the JSON: After finding the most relevant key, we just had to filter out all of the other keys from the JSON and prepend this snippet to the beginning of the user prompt.</p>
<p>Overall, using this algorithm significantly reduced the hallucinations that Granite was having. This is because, unlike Jaro-Winkler, cosine similarity is able to handle multiple words, synonyms, and also look at contextual meaning.</p>
<h2 id="heading-final-steps">Final Steps</h2>
<p>Eventually, using NLP lead to a lot more accurate results with the commands being generated by the LLM. However, there were still a few instances of some hallucinations, but we reckon that, at this point, those are mostly due to the fact that we are using a relatively low-parameter model of Granite, so it has quite a small context-window (the length of lemmas that the LLM can ‘remember’).</p>
<p>To counteract these infrequent hallucinations, we decided to add a simple safeguard - if the cosine similarity score is below a given threshold, we simply respond to the user with “Sorry, I can’t match that request to a feature”. Of course, this isn’t the best way of doing things in production, but for a small LLM model and a prototype (i.e. for our needs) it would be sufficient.</p>
]]></content:encoded></item><item><title><![CDATA[25/02/25 - Facial Recognition & Encryption Clients]]></title><description><![CDATA[Another one of the most important features of our system was the facial recognition being used as 2-factor authentication on the Raspberry Pi 5. Initially, our plan seemed to be relatively straightforward and simple: we would just have to record a fi...]]></description><link>https://qpg.hashnode.dev/250225-facial-recognition-and-encryption-clients</link><guid isPermaLink="true">https://qpg.hashnode.dev/250225-facial-recognition-and-encryption-clients</guid><dc:creator><![CDATA[Raghav Awasthi]]></dc:creator><pubDate>Tue, 25 Feb 2025 12:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742936310265/406ac419-4712-410a-87fb-e8775cf3e7aa.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Another one of the most important features of our system was the facial recognition being used as 2-factor authentication on the Raspberry Pi 5. Initially, our plan seemed to be relatively straightforward and simple: we would just have to record a five second video, then train the model on that video, associated with the username, so the model would recognize that person’s face as their username. However, reality didn’t quite match our wishful thinking. In this blog post, we will talk about how we went from manually scraping a few images from the initial video recording of a user with a Bash script, to creating a fully privacy-friendly approach.</p>
<h2 id="heading-part-1-splicing-videos-amp-pushing-to-github">Part 1: Splicing Videos &amp; Pushing to GitHub</h2>
<p>Initially, we knew that we had a 5 second video recording from the user from when they registered themselves on the registration website. This video was being saved locally at the time, and the problem was that our facial recognition was in a different repository and wasn’t explicitly linked to the registration website or the server (via absolute directory paths), so there was no easy way to use the video to train the model.</p>
<p>Our first idea was to save the video on the server, but we realized that this would quickly take up our storage space with more users, so we decided against it. So, we decided on a simpler approach - using the 5-second video, take an image/snapshot of the video every second, so we end up with a total of 5 images to train the model with. Since it’s only 5 images, it wouldn’t take too much storage space, so we decided to go ahead with this attempt. Doing this was relatively simple using the <a target="_blank" href="https://opencv.org/">OpenCV</a> Python library. However, again, these images were stored in a different place to where they needed to be.</p>
<p>Our genius idea (NOTE: in retrospect, this was an extremely inefficient and quite simply, dumb, idea) was to save these images locally, then use a library such as <a target="_blank" href="https://github.com/PyGithub/PyGithub">PyGithub</a> to commit and push these images to the Raspberry Pi code repository, making it easier to train the model.</p>
<p>After implementing this, we realized that 5 images was simply too small of a dataset to train the model on - it wasn’t extremely accurate, and didn’t always recognize the user. This led us to our next step - obtaining 20 images from the 5 second video.</p>
<h2 id="heading-part-2-20-images-and-a-more-accurate-facial-recognition-model">Part 2: 20 Images and a More Accurate Facial Recognition Model</h2>
<p>Using a similar approach to before, we managed to splice 20 images form the 5 second video. However, this time, we decided to take these samples at random, to ensure that no errors could occur (for example, some cameras took some extra time to boot, giving us only 3 seconds of actual video footage of the user’s face). Using a random sampling method, we reduced these errors, so, rather than having potentially 8 unusable images, we could have less.</p>
<p>With this, we significantly improved our accuracy - the Raspberry Pi camera almost instantly recognized a user whenever they appeared in the frame when we tested with the improved 20-image version. However, there was a major downside to this change. Namely, it took much longer to push the images to the Github repository, and also added more of a time delay while waiting for the model to be trained on the images.</p>
<p>We obviously didn’t want these 2 things to be the case for the final version, so we had to find another way to do this.</p>
<h2 id="heading-part-3-local-training-amp-face-encodings">Part 3: Local Training &amp; Face Encodings</h2>
<p>Instead of going through this long-winded process with high latency, we realized that it would be easier to simply train the model on the server itself. So, we spent quite long trying to move this code to where our server code was. Eventually, this made our training much easier and quicker.</p>
<p>Additionally, rather than saving the images that we captured from the 5 second video, we thought that it would be better to just save the encodings of the face and the trained model. This benefitted us in 2 ways:</p>
<ol>
<li><p>Speed: with the server doing the majority of the strenuous computation (i.e. training the model), the training time was significantly reduced since the Raspberry Pi, with less RAM, and generally lower hardware specifications than the average portable computer, wasn’t being used for this.</p>
</li>
<li><p>Privacy: not storing the user’s faces is a critical upgrade in the privacy of our entire system. By storing only the encodings of the faces, which are basically just a bunch of numbers that are useful for comparison, no essential personal data or images are being stored about the user.</p>
</li>
</ol>
<p>With these encodings, the Raspberry Pi could now communicate with the server to request the necessary encodings, greatly reducing the latency of our entire system.</p>
<h1 id="heading-february-update-video">February Update Video</h1>
<p>Below is a video that summarizes the above information and what we have worked on in the last month or so.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/Lb6-CZsGs4E">https://youtu.be/Lb6-CZsGs4E</a></div>
]]></content:encoded></item><item><title><![CDATA[11/02/25 - HID Keyboard Simulation]]></title><description><![CDATA[While working on using the Raspberry Pi 5 to simulate a keyboard (in order to enter the user’s username and password into the computer), we came across a plethora of different challenges. Eventually, we finally got to a point where we finally realize...]]></description><link>https://qpg.hashnode.dev/110225-hid-keyboard-simulation</link><guid isPermaLink="true">https://qpg.hashnode.dev/110225-hid-keyboard-simulation</guid><dc:creator><![CDATA[Raghav Awasthi]]></dc:creator><pubDate>Tue, 11 Feb 2025 00:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742936253141/f7512b32-10be-4443-b87b-d4e64ed48ad3.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>While working on using the Raspberry Pi 5 to simulate a keyboard (in order to enter the user’s username and password into the computer), we came across a plethora of different challenges. Eventually, we finally got to a point where we finally realized it would be impossible to actually accomplish this with the device, so we had to pivot to using a different device that supports HID Keyboard simulation and has USB OTG - a Raspberry Pi Pico. In this post, we will cover what we initially tried to do, what went wrong, and how this was fixed (and why) with the Raspberry Pi Pico.</p>
<h2 id="heading-initial-attempt">Initial Attempt</h2>
<p>Originally, we planned to use the Raspberry Pi 5 to emulate a USB Human-Interface Device (HID) keyboard, which would allow the computer to be logged in into to recognize the Raspberry Pi as a keyboard, and receive keystrokes from it. Obviously, we had done our initial research - most Raspberry Pis have something called a “gadget mode” which allows for USB HID simulation, so we obviously thought that our Raspberry Pis could do the same.</p>
<p>It is important to note that, by default, Raspberry Pis are in host mode, i.e. the Pi itself enumerates and powers USB devices that are plugged into its USB-A ports. The problem, as it turned out, was that, although our model (Raspberry Pi 5) supports gadget mode, it unfortunately doesn’t support OTG mode, which allows the device to seamlessly switch between acting as a host and a gadget.</p>
<p>Due to the lack of this feature, we couldn’t continue to use the Raspberry Pi 5 to act as a HID keyboard - this is because we still needed the BLE and facial recognition scripts to run, which could only happen in host mode.</p>
<h2 id="heading-second-problem">Second Problem</h2>
<p>Additionally, the USB-A ports of the device didn’t support USB On-The-Go (OTG - different to OTG mode), but the USB-C port did. However, the USB-C port was how we powered the Raspberry Pi, and using an adapter on that port (in order to try to connect the device to power and to the computer to be logged in into) meant that the device didn’t actually receive enough power to support all of its functionalities.</p>
<p>We tried lots of things to make the Raspberry Pi work as a HID keyboard, and we did manage to register the Raspberry Pi as a keyboard eventually, but it wasn’t able to send keystrokes (due to the lack of support for USB OTG on the necessary USB ports).</p>
<p>Below is an image of when we got the Raspberry Pi 5 to be registered as an external USB device (keyboard):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742508852664/690a1d17-175b-4db6-a451-72d9f65c068a.jpeg" alt class="image--center mx-auto" /></p>
<p>USB OTG is what actually enables a device to act as a USB host/peripheral device, and because the Raspberry Pi 5 lacked the kind of direct OTG support we needed to emulate a USB keyboard in a fully functional manner, we weren’t able to send keystrokes as if they were coming from an actual keyboard peripheral.</p>
<h2 id="heading-raspberry-pi-pico">Raspberry Pi Pico</h2>
<p>Realizing that the Raspberry Pi 5 couldn’t be used as a HID Keyboard, we looked for the cheapest and best alternative, eventually finding the Raspberry Pi Pico. This is an incredibly small, microcontroller-based board that does support USB OTG, unlike the Raspberry Pi 5, and also widely supported by online communities.</p>
<p>In order to connect the 2 devices, we decided on a relatively simple method that is also robust: namely, UART (Universal Asynchronous Receiver/Transmitter). The setup for this was as follows:</p>
<p>1) Set up the hardware: this first mean connecting the Raspberry Pi Pico to a breadboard. Then, using GPIO wires, connecting the TX to the RX port on the opposite device, and finally having a wire connecting the ground pins on both devices.</p>
<p>2) Deciding the communication protocol: we decided to use a simple, straightforward text-based protocol over UART so that the Raspberry Pi 5 could easily tell the Raspberry Pi Pico which keystrokes to send to log the user in.</p>
<p>3) Setup up <a target="_blank" href="https://circuitpython.org/">CircuitPython</a> on the Raspberry Pi Pico: CircuitPython has a built-in library which allows for USB HID, letting us present the Pico to the host computer as a keyboard. Additionally, using the <a target="_blank" href="https://github.com/adafruit/Adafruit_BusIO">Busio</a> library, we were able to setup the UART connection on both sides of the device.</p>
<h2 id="heading-simulating-a-usb-hid-keyboard-with-the-raspberry-pi-pico">Simulating a USB HID Keyboard with the Raspberry Pi Pico</h2>
<p>Using the <code>usb_hid</code> library, we were easily able to define the Raspberry Pi Pico as a USB keyboard peripheral. The script for the UART and USB HID keyboard functionality looked something like the following:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> board
<span class="hljs-keyword">import</span> busio
<span class="hljs-keyword">import</span> usb_hid
<span class="hljs-keyword">from</span> adafruit_hid.keyboard <span class="hljs-keyword">import</span> Keyboard
<span class="hljs-keyword">from</span> adafruit_hid.keyboard_layout_us <span class="hljs-keyword">import</span> KeyboardLayoutUS
<span class="hljs-keyword">from</span> adafruit_hid.keycode <span class="hljs-keyword">import</span> Keycode

<span class="hljs-comment"># Initialize UART0 with baud rate 9600</span>
uart = busio.UART(board.GP0, board.GP1 ,baudrate=<span class="hljs-number">9600</span>)

<span class="hljs-comment"># Function to send data over UART (FOR DEBUGGING ONLY)</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">send_data</span>(<span class="hljs-params">data</span>):</span>
    uart.write(data)
    print(<span class="hljs-string">'Sent: '</span> + data)

<span class="hljs-comment"># Function to receive data over UART</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">receive_data</span>():</span>
    data = <span class="hljs-string">"Nothing"</span>
    <span class="hljs-keyword">if</span> uart.in_waiting &gt; <span class="hljs-number">0</span>:
        data = uart.read(uart.in_waiting).decode(<span class="hljs-string">'utf-8'</span>)
        <span class="hljs-keyword">try</span>:
            <span class="hljs-keyword">return</span> json.loads(data) <span class="hljs-comment"># parse json</span>
        <span class="hljs-keyword">except</span>:
            print(<span class="hljs-string">'invalid data'</span>)
    print(<span class="hljs-string">"DATA:"</span> + data)
    <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>

keyboard = Keyboard(usb_hid.devices)
keyboard_layout = KeyboardLayoutUS(keyboard)

<span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
    response = receive_data()
    <span class="hljs-keyword">if</span> response:
        keyboard_layout.write(username)
        keyboard.press(Keycode.ENTER)
        keyboard.release_all()
        time.sleep(<span class="hljs-number">2</span>)
        <span class="hljs-comment"># other values needed to be 'written' by the 'keyboard'</span>
    time.sleep(<span class="hljs-number">1</span>)
</code></pre>
<p>The code is quite simple and self-explanatory. Basically, after doing the initial UART and USB HID keyboard setup, we first check to see if the Pico has received any UART data (this happens again on each run). Then, if a valid JSON is found, the code uses the data in the JSON to decide which keystrokes need to be sent to the host computer. Finally, the Raspberry Pi Pico then emulates a USB keyboard for the computer it’s plugged into over the USB cable.</p>
]]></content:encoded></item><item><title><![CDATA[30/01/25 - Desktop App & RPi Code Troubleshooting and Progress]]></title><description><![CDATA[Since the last blog, we faced quite a few problems while working on the desktop application and the raspberry pi code, so we will discuss what these were and how we fixed them alongside some code snippets.
Desktop Application
Command Execution
One of...]]></description><link>https://qpg.hashnode.dev/300125-desktop-app-and-rpi-code-troubleshooting-and-progress</link><guid isPermaLink="true">https://qpg.hashnode.dev/300125-desktop-app-and-rpi-code-troubleshooting-and-progress</guid><dc:creator><![CDATA[Raghav Awasthi]]></dc:creator><pubDate>Thu, 30 Jan 2025 00:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742936197659/23cef0b4-38ab-417e-a387-4b357af7a68f.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Since the last blog, we faced quite a few problems while working on the desktop application and the raspberry pi code, so we will discuss what these were and how we fixed them alongside some code snippets.</p>
<h1 id="heading-desktop-application">Desktop Application</h1>
<h2 id="heading-command-execution">Command Execution</h2>
<p>One of our key functionalities for the desktop application was to be able to execute commands, as mentioned in the previous blog. The problem we faced was that the <code>shell.command()</code> function takes in only a single word (split by whitespace), whereas we were passing in the entire string. After thoroughly scouring the docs for this, we eventually realized that this is the case and we need to use the additional method <code>.args()</code> for additional whitespaced arguments of the bash command and, optionally, <code>.arg()</code> for the final argument.</p>
<p>The code for this ended up looking something like the following to get a function for executing commands:</p>
<pre><code class="lang-rust"><span class="hljs-keyword">match</span> shell.command(&amp;base_parts[<span class="hljs-number">0</span>]).args(&amp;base_parts[<span class="hljs-number">1</span>..]).arg(last_value).output().<span class="hljs-keyword">await</span> {
    <span class="hljs-literal">Ok</span>(output) =&gt; {
        <span class="hljs-keyword">if</span> output.status.success() {
            <span class="hljs-keyword">let</span> stdout_str = <span class="hljs-built_in">String</span>::from_utf8(output.stdout).unwrap_or_default();
            <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Command result: {:?}"</span>, stdout_str);

            <span class="hljs-keyword">if</span> update {
                <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-literal">Err</span>(err) = update_json_current_value(
                    &amp;username,
                    &amp;base_cmd_str,
                    last_value,
                    encryption_instance,
                    state,
                ).<span class="hljs-keyword">await</span> {
                    <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Warning: error updating JSON current value: {}"</span>, err);
                }
            }
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Exit with code: {}"</span>, output.status.code().unwrap_or_default());
        }
    }
    <span class="hljs-literal">Err</span>(e) =&gt; {
        <span class="hljs-built_in">println!</span>(<span class="hljs-string">"Failed to execute command: {} with error {}"</span>, command, e);
    }
}
</code></pre>
<h2 id="heading-ollama-amp-ngrok">Ollama &amp; Ngrok</h2>
<p>For the desktop application, we only managed to find accessibility commands through the terminal for GNOME-based GUIs. Therefore, we were running and testing this application on a Kali Linux VM (Virtual Machine) running on an M-series Mac. The problem with this was being able to access localhost through the VM, which wasn’t possible without changing some settings and bridging the connection (which couldn’t be changed due to some other variables).</p>
<p>We decided to use ollama to pull the Granite 3.0 model that we were going to use, and use the <code>ollama-rs</code> crate in Rust in order to access this model and use it - some of us had prior experience in doing this so we decided to go with this approach. The problem was, however, that running ollama and doing the inferencing on the virtual machine was extremely slow (due to the virtualization of the VM because of different architectures - ARM and AMD64).</p>
<p>So, we decided to use ngrok - a way to expose a local port to be accessed publicly. With this, ollama was now running on the Mac itself, with acceleration via the GPU on the Mac, and this was exposed on port <code>11434</code> , which was being called by the desktop application running in the VM. The problem with this was that we needed to run the server simultaneously as well, so we always needed another device using ngrok for the server in order to test the desktop application (as a premium ngrok subscription is required to expose multiple different ports over the internet).</p>
<pre><code class="lang-bash">ngrok http 11434 --host-header=<span class="hljs-string">"localhost:11434"</span>; <span class="hljs-comment"># ngrok for ollama</span>
ngrok http 8000; <span class="hljs-comment"># ngrok for server running on port 8000</span>
</code></pre>
<h1 id="heading-raspberry-pi-code">Raspberry Pi code</h1>
<h2 id="heading-ble-distance-calibration">BLE Distance Calibration</h2>
<p>To calculate the distance between the ESP32 and the Raspberry Pi, we used the following calculation:</p>
<p>$$\text{Distance}= 10^{\frac{\text{measured power} - \text{RSSI}}{10\text{N}}}$$</p><p>This requires calibrating <code>measured power</code> and <code>N</code> in order to get an accurate value. However, for an approximate distance of 25cm, our calculated distance varied between 10cm and 2.5 meters. This isn’t ideal, but we didn’t manage to recalibrate these values before today, so we will try to do this in one of the upcoming weeks.</p>
<h2 id="heading-facial-recognition-amp-ble-integration">Facial Recognition &amp; BLE Integration</h2>
<p>We had our facial recognition script running in a separate file from the main file with the BLE connectivity code. So, we tried to directly import the file and the methods from the facial recognition script to run it within the main file, however, there were some setup commands which clashed with each other and weren’t run. This meant that we had to rewrite the entire code so that they could all be run via methods from the imported script, and this took a while due to the variety of errors born from this.</p>
<h1 id="heading-serverdesktop-application">Server/Desktop Application</h1>
<h2 id="heading-verification-of-the-user">Verification of the User</h2>
<p>Since we don’t use BLE with the actual computer being logged in into, there is no explicit way to confirm that the user is who they claim to be. However, we assume that the Raspberry Pi correctly acts as a keyboard, entering the correct username and password into the computer. Therefore, in the Desktop application, instead of using BLE (as this would be some additional complexity), we simply use the <code>whoami</code> command to ensure that the user’s username matches what is stored in our server. This way, we ensure we do not respond with the user’s preferences JSON without it actually being them (this could happen in the case that the keyboard HID simulation fails and someone else enters their own username and password into the same computer, however this is an extremely unlikely case, as the HID keyboard simulation will almost never fail unless someone tries to disconnect the Raspberry Pi from the computer).</p>
<h1 id="heading-january-update-video">January Update Video</h1>
<p>Below is a video that summarizes the above information and what we have worked on in the last month or so.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/YmrEh3_8U_8">https://youtu.be/YmrEh3_8U_8</a></div>
]]></content:encoded></item><item><title><![CDATA[12/01/25 - Basic Functionalities of the 5 Main Components]]></title><description><![CDATA[In this second blog for the Quantum Proximity Gateway project, we will discuss the progress we made over the winter holidays, up to 12/01/2025. As of right now, the project has 5 main parts to it: the server, the registration website, the ESP32 code,...]]></description><link>https://qpg.hashnode.dev/120125-basic-functionalities-of-the-5-main-components</link><guid isPermaLink="true">https://qpg.hashnode.dev/120125-basic-functionalities-of-the-5-main-components</guid><dc:creator><![CDATA[Raghav Awasthi]]></dc:creator><pubDate>Sun, 12 Jan 2025 00:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742935992296/47b7a00b-4118-4a78-b43a-1611ff71bbcf.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this second blog for the Quantum Proximity Gateway project, we will discuss the progress we made over the winter holidays, up to 12/01/2025. As of right now, the project has 5 main parts to it: the server, the registration website, the ESP32 code, the Raspberry Pi code, and finally the desktop application. We will cover what each of these components is for, as well as what we have achieved so far for each part.</p>
<h1 id="heading-overall-workflow">Overall Workflow</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742294373692/40f646cb-f487-458a-9164-c4f9ef193b7a.png" alt class="image--center mx-auto" /></p>
<p>As illustrated in the diagram above, the very first step is to register the user. This is done by the ESP32 being connected to a computer, on which the registration website is opened. Then, the user can enter their credentials, and the website communicates with our server in order to store the MAC address of the ESP32 device, and relays the key back to it.</p>
<p>After being registered, when a user, either carrying or wearing the ESP32 device, walks up to the computer that they want to login into, the ESP32’s key will be transmitted via BLE (Bluetooth Low Energy) to the Raspberry Pi which is connected to that computer. The code on the Raspberry Pi then communicates with the server to ensure that the ESP32’s key is valid and correctly authenticated. Then, as a secondary authentication step (2-factor authentication), the camera module connected to the Raspberry Pi checks for the registered user’s face. Once this is found, the username and password of the user are entered into the device, logging the user in.</p>
<p>As soon as the computer loads up, the desktop application, with the AI Agent, is opened, and all saved preferences immediately load up. The user can then go on to prompt the application to make accessibility changes as necessary for them to feel comfortable using the device, and these commands are executed and the preferences are updated in the server so that they can be loaded immediately next time.</p>
<h1 id="heading-server">Server</h1>
<h2 id="heading-purpose">Purpose</h2>
<p>The server is where we handle all of the requests that the other components of the project make, as well as store our data. In our case, this means handling how to register an ESP32 device with a user (and their account details), as well as storing a user’s accessibility preferences which can be retrieved. This will also handle the majority of the encryption aspect, ensuring that we use a post-quantum secure encryption standard such as <a target="_blank" href="https://pq-crystals.org/kyber/">CRYSTALS-Kyber</a>.</p>
<h2 id="heading-progress">Progress</h2>
<p>So far, we have managed to integrate the basic functionality, such as registering a user, and retrieving/updating default preference options that are stored in the database as a JSON. We didn’t spend too much time on this - it was mostly updated with any changes that were required in the other modules, as those were our main focus up to now.</p>
<h1 id="heading-registration-website">Registration Website</h1>
<h2 id="heading-purpose-1">Purpose</h2>
<p>This website is how we register the ESP32 with a user (and their associated username and password). It scans the USB ports of the current device (the laptop on which you’ve opened up the website) and asks the user to select a device. In our demonstration video, we will explicitly go over which option needs to be selected, but any of the 2 options referring to the ESP32 device (connected via USB-C cable to the laptop) will work. The user will then be able to enter their username and password, and on clicking submit, the ESP32 should be registered to the user (the registration website sends requests to the server in order to make this happen).</p>
<h2 id="heading-progress-1">Progress</h2>
<p>As of the previous blog, most of this website was completed, but there were a few bugs, such as hydration errors. We managed to fix this after a lot of debugging to find the issue (there were some nested tags which didn’t match the format we were expecting), and we also cleaned-up the website to make it look better.</p>
<h1 id="heading-esp32-code">ESP32 Code</h1>
<h2 id="heading-purpose-2">Purpose</h2>
<p>This is the code needed on the ESP32 device which ensures that its MAC address is transmitted when necessary, and also handles the Bluetooth Low Energy communication with the Raspberry Pi as the users approaches the computer to be logged in into.</p>
<h2 id="heading-progress-2">Progress</h2>
<p>As of the previous blog, we had only managed to test the basic BLE functionality, but now, we have managed to actually communicate with the Raspberry Pi in a basic way that sends the MAC address to it from the ESP32, if the distance between the devices is sufficiently low.</p>
<h1 id="heading-raspberry-pi-code">Raspberry Pi Code</h1>
<h2 id="heading-purpose-3">Purpose</h2>
<p>This code checks for BLE signals from registered ESP32 devices and verifies their credentials, alongside constantly checking the distance from the computer. Once the distance is small enough, it uses the camera module attached to the Raspberry Pi and checks for the registered user, and if they are found, the Raspberry Pi acts as a HID (Human-Interface Device) Keyboard connected to the computer to be logged in into, and types out the user’s username and password.</p>
<h2 id="heading-progress-3">Progress</h2>
<p>So far, we have managed to get some basic functional communication with the ESP32 device via BLE. Additionally, we managed to write some code which allows us to get the encodings of a face and train a model on these encodings in order to recognize a person. The code that we’ve written so far for this facial recognition is relatively preliminary, and hasn’t really been integrated with the BLE right now - this is something we will be working on in the upcoming weeks.</p>
<h2 id="heading-facial-recognition-functionality">Facial Recognition Functionality</h2>
<p>It was quite tough to find a way to recognize people with the camera module, but after a lot of research, we stumbled across <a target="_blank" href="https://core-electronics.com.au/guides/raspberry-pi/face-recognition-with-raspberry-pi-and-opencv/">this</a> blog, which helped us formulate an idea on how to go about using the camera module and recognizing people’s faces.</p>
<p>The idea is that, when a user is registered, we ask them for some images of themselves, which will be used to automatically train the facial recognition model, which can then be used by any Raspberry Pi to check against the database. Of course, we don’t want to store images of anyone on the database, so we will only be storing the encodings related to a person’s face i.e. numbers that are used by the model to recognize individuals.</p>
<h1 id="heading-desktop-application">Desktop Application</h1>
<h2 id="heading-purpose-4">Purpose</h2>
<p>This is the main part of our project that relates to accessibility. The application opens as soon as a user is logged in and immediately fetches their preferences from the server and executes the commands necessary to have these preferences loaded. The application also has a chatbot, which the user can talk to, and the chatbot will automatically be able to determine how to create and execute a command so that the user’s needs are met.</p>
<h2 id="heading-progress-4">Progress</h2>
<p>We have managed to create a very basic frontend design that represents the chatbot, and we have also integrated fetching the preferences from the server based on the current user’s username. We also prototyped some code for the backend which tries to execute the commands, and this mostly works. This is another one of our main focuses in the weeks going forward, as it is one of the components that will require the most work overall.</p>
<h2 id="heading-command-execution-functionality">Command Execution Functionality</h2>
<p>For executing commands, we need to be able to make sure that they are safely done - for example, if someone were to be able to somehow change the command being executed, this shouldn’t be allowed. This is quite an obvious requirement since otherwise someone could install a key logger, or attempt to delete all the data on the device, amongst other things, so obviously this safety feature is extremely important.</p>
<p>After some brainstorming, we figured that the best way to go about this was to ensure that the application does not have admin access, and to also whitelist the possible commands that can be run. In other words, if a command is not included within the JSON, then it cannot be run. This is fine because our JSON is stored on the server, and we know for sure that the server is secure - this is one of the initial necessities for the entire project. We also do have some other failsafes, and, if we have enough time, we might try to implement a fully functional guardrails feature (although granite does already have some safety nets).</p>
<h2 id="heading-ai-agent-functionality">AI Agent Functionality</h2>
<p>For actually converting the user’s inputs into commands that can be executed, we had to try a plethora of different approaches. Eventually, the solution that seemed to work the best for us was to give the LLM a system prompt, telling it what kind of assistant it is, followed by a new user prompt for every message the user enters. The system prompt includes the entire JSON file, telling the LLM that it is supposed to search through these commands to always return a general response to the user, as well as a command to be run. This approach was quite effective, and allowed us to generally get a command that relates to what the user needs.</p>
]]></content:encoded></item><item><title><![CDATA[10/12/24 - Initial Blog Post]]></title><description><![CDATA[Welcome to our first blog post for the Quantum Proximity Gateway project. We’re excited to share everything we’ve accomplished from the start of Term 1 (25/09/2024) through to 10/12/2024. In this post, we’ll outline our client requirements, discuss o...]]></description><link>https://qpg.hashnode.dev/101224-initial-blog-post</link><guid isPermaLink="true">https://qpg.hashnode.dev/101224-initial-blog-post</guid><dc:creator><![CDATA[Raghav Awasthi]]></dc:creator><pubDate>Tue, 10 Dec 2024 00:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742935907492/b6cce276-d1e2-414c-bdd7-5e08d0d29e6a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to our first blog post for the Quantum Proximity Gateway project. We’re excited to share everything we’ve accomplished from the start of Term 1 (25/09/2024) through to 10/12/2024. In this post, we’ll outline our client requirements, discuss our system design, and highlight the initial work we’ve done on our MVP.</p>
<h1 id="heading-client-requirements">Client Requirements</h1>
<p>After multiple versions of reviewing our requirements with the client, we eventually settled on the MoSCoW requirements for our project which are outlined below.</p>
<h2 id="heading-must-have">Must Have ✅</h2>
<ul>
<li><p>BLE proximity connection to Raspberry Pi</p>
<ul>
<li>Integration with BLE connected to Raspberry Pi for detecting user proximity to a specific device (ESP32)</li>
</ul>
</li>
<li><p>Facial recognition via Raspberry Pi (Raspberry Pi camera module)</p>
<ul>
<li><p>Use of Raspberry Pi for real-time facial recognition, to authenticate users and link their profiles to devices</p>
</li>
<li><p>Ensure accuracy in low light conditions and secure handling of biometric data</p>
</li>
</ul>
</li>
<li><p>Accessibility settings fetching</p>
<ul>
<li>Automatically fetch user-specific accessibility settings when the user connects to a device e.g text size, contrast, voice support</li>
</ul>
</li>
<li><p>Server</p>
<ul>
<li>For hosting user profiles, managing proximity data and backend operations</li>
</ul>
</li>
<li><p>Post-quantum cryptography encryption for securing user data</p>
<ul>
<li><p>All data transmitted between devices should be encrypted using a PQC algorithm</p>
</li>
<li><p>Secures sensitive user data during transmission and storage</p>
</li>
</ul>
</li>
<li><p>AI chatbot for easy settings configurations</p>
<ul>
<li><p>Granite 3.0 IBM Model</p>
</li>
<li><p>Enable chatbot to help with troubleshooting/guidance</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-should-have">Should Have ❗</h2>
<ul>
<li><p>A website to configure settings initially</p>
<ul>
<li><p>Register new user profiles on website (facial recognition setup, preferences, etc.)</p>
</li>
<li><p>Save data to server</p>
</li>
<li><p>Secure login - 2 factor authentication</p>
</li>
<li><p>Responsive design for computer/tablet use</p>
</li>
</ul>
</li>
<li><p>Directional proximity management</p>
<ul>
<li>To identify a specific device a user intends to interact with when there are multiple devices nearby</li>
</ul>
</li>
</ul>
<h2 id="heading-could-have">Could Have ⚠️</h2>
<ul>
<li><p>API Layer to make system reproducible</p>
</li>
<li><p>Multiple languages support</p>
<ul>
<li>For the configuration website and IoT interactions</li>
</ul>
</li>
</ul>
<h2 id="heading-wont-have">Won’t Have ❌</h2>
<ul>
<li><p>Mobile application to remotely login and select device</p>
</li>
<li><p>Voice recognition/authentication</p>
<ul>
<li>Potential privacy concerns and complexity would be an issue</li>
</ul>
</li>
</ul>
<h1 id="heading-system-design">System Design</h1>
<p>We also created a system design diagram that briefly illustrates how the entire system should work.</p>
<h2 id="heading-diagram">Diagram</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737653722525/77c1ff8e-f6f2-40ee-a7a3-3a6d4722c870.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-workflow">Workflow</h2>
<p>Below is a simple representation of the general flow of events that will happen when the system is run and set up:</p>
<p>ESP32 (MAC Address) → Registered → ESP32 gets back its key → Broadcasts key → Raspberry Pi listens in → Raspberry Pi relays key to server → Credentials are relayed back to the Raspberry Pi → Raspberry Pi performs facial recognition with the camera module → Raspberry Pi simulates HDI inputs → AI Agent boots up on login → Preferences loaded</p>
<h1 id="heading-initial-work-on-mvp">Initial Work on MVP</h1>
<p>We finally received the last of the hardware that this project will require today, so we were unable to work on the actual code related to the BLE connection between the two devices before now. However, we instead worked on other parts of the MVP that didn't explicitly need the devices.</p>
<p>To start with, we spent the first few weeks of this term conducting as much research as we could to ensure that the project is actually feasible to be completed within our limited time frame. This included looking into Granite 3.0, since we need to use that for our AI Agent which helps users set their accessibility settings; BLE protocols (amongst other protocols to figure out which one would be the best for our use case); and GNOME settings commands to figure out how to automatically set the user's preferences on login for the Rocky Operating System.</p>
<p>Building on our research, we then started working on the device and user registration website (shown below), and, since receiving the Raspberry Pis as of last week, we have managed to achieve basic BLE connectivity on them.</p>
<h1 id="heading-december-update-video">December Update Video</h1>
<p>Below is a video that summarizes the above information and what we have worked on in the last month or so.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=2vZZyaepIAw">https://www.youtube.com/watch?v=2vZZyaepIAw</a></div>
]]></content:encoded></item></channel></rss>