<?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[Random Notes]]></title><description><![CDATA[Random Notes]]></description><link>https://ariesgun.xyz</link><generator>RSS for Node</generator><lastBuildDate>Sun, 13 Sep 2026 02:17:52 GMT</lastBuildDate><atom:link href="https://ariesgun.xyz/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Linux Asynchronous Communication epoll]]></title><description><![CDATA[Linux provides several alternatives to the traditional blocking and non-blocking I/O models. 
One of them is the epoll API. 
The epoll API
epoll() is a Linux kernel system call that provides an efficient way to monitor multiple file descriptors to ch...]]></description><link>https://ariesgun.xyz/linux-ipc-epoll</link><guid isPermaLink="true">https://ariesgun.xyz/linux-ipc-epoll</guid><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Fri, 06 Jun 2025 15:58:00 GMT</pubDate><content:encoded><![CDATA[<p>Linux provides several alternatives to the traditional blocking and non-blocking I/O models. </p>
<p>One of them is the <strong>epoll</strong> API. </p>
<h1 id="heading-the-epoll-api">The <strong>epoll</strong> API</h1>
<p><strong>epoll()</strong> is a Linux kernel system call that provides an efficient way to monitor multiple file descriptors to check if I/O is possible on any of them. It is a new mechanism and introduced in Linux 2.6. It is similar to other system calls, such as <strong>select()</strong> and <strong>poll(),</strong> but it is more performant. <strong>epoll()</strong> scales much better when monitoring large numbers of file descriptors. </p>
<p>Due to its performance and flexibility, <strong>epoll()</strong> is commonly used in modern network and server applications to manage a large number of concurrent connections with minimal latency. </p>
<p>An <strong>epoll</strong> instance maintains two lists: </p>
<ul>
<li><p>Interest list. Contains a list of file descriptors to be monitored. </p>
</li>
<li><p>Ready list. Contains a list of file descriptors that are ready for I/O. </p>
</li>
</ul>
<p>With this design, <strong>epoll</strong> is able to scale to thousand of file descriptors without any performance penalties. </p>
<h2 id="heading-notification-modes">Notification Modes</h2>
<p>epoll() supports two types of notification modes: </p>
<ul>
<li><p>Level-Triggered (LT). This is the default mode in which as long as a file descriptor is ready for I/O, subsequent calls to <strong>epoll_wait()</strong> will continue to report it. </p>
</li>
<li><p>Edge-Triggered(ET). In this mode, <strong>epoll_wait()</strong> only reports an event when a change occurs. The subsequent calls to <strong>epoll_wait()</strong> will block although there’s still data available on a file descriptor. </p>
</li>
</ul>
<h1 id="heading-how-it-works">How It Works</h1>
<p>This API consists of three system calls: </p>
<ul>
<li><p><strong>epoll_create().</strong> Create a new epoll instance and returns a file descriptor referring to the instance. </p>
</li>
<li><p><strong>epoll_ctl().</strong> Add, remove, or modify a file descriptor into the interest list associated with an epoll instance. </p>
</li>
<li><p><strong>epoll_wait().</strong> Wait until I/O is ready for any of the file descriptors in the interest list. </p>
</li>
</ul>
<p>To make it clear, we are going to implement a simple example of using the <strong>epoll</strong> API. </p>
<pre><code>#include &lt;sys/epoll.h&gt;
#include &lt;fcntl.h&gt;

extern <span class="hljs-string">"C"</span>
{
#include <span class="hljs-string">"lib/error_functions.h"</span>
#include <span class="hljs-string">"lib/tlpi_hdr.h"</span>
}

#define MAX_BUF <span class="hljs-number">1000</span> <span class="hljs-comment">// Maximum bytes fetched by a single read()</span>
#define MAX_EVENTS <span class="hljs-number">5</span> <span class="hljs-comment">// Maximum number of events to be returned from a single epoll_wait call</span>

int main(int argc, char *argv[])
{
    printf(<span class="hljs-string">"Run %d\n"</span>, argc);
    int epfd, ready, fd, s, j, numOpenFds;
    struct epoll_event ev;
    struct epoll_event evlist[MAX_EVENTS];
    char buf[MAX_BUF];

    epfd = epoll_create(argc - <span class="hljs-number">1</span>);
    <span class="hljs-keyword">if</span> (epfd == <span class="hljs-number">-1</span>)
    {
        errExit(<span class="hljs-string">"epoll_create"</span>);
    }

    <span class="hljs-comment">// Open each file and add it into the "interest list"</span>
    <span class="hljs-keyword">for</span> (j = <span class="hljs-number">1</span>; j &lt; argc; j++)
    {
        printf(<span class="hljs-string">"Opening file descriptor for \"%s\"\n"</span>, argv[j]);
        fd = open(argv[j], O_RDONLY);
        <span class="hljs-keyword">if</span> (fd == <span class="hljs-number">-1</span>)
        {
            errExit(<span class="hljs-string">"open"</span>);
        }
        printf(<span class="hljs-string">"Opened \"%s\" on fd %d\n"</span>, argv[j], fd);

        ev.events = EPOLLIN;
        ev.data.fd = fd;
        <span class="hljs-keyword">if</span> (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &amp;ev) == <span class="hljs-number">-1</span>)
        {
            errExit(<span class="hljs-string">"epoll_ctl"</span>);
        }
    }

    numOpenFds = argc - <span class="hljs-number">1</span>;

    <span class="hljs-keyword">while</span> (numOpenFds &gt; <span class="hljs-number">0</span>)
    {
        <span class="hljs-comment">// Fetch up to MAX_EVENTS items for the ready list</span>

        printf(<span class="hljs-string">"About to epoll_wait()\n"</span>);
        ready = epoll_wait(epfd, evlist, MAX_EVENTS, <span class="hljs-number">-1</span>);
        <span class="hljs-keyword">if</span> (ready == <span class="hljs-number">-1</span>)
        {
            <span class="hljs-keyword">if</span> (errno == EINTR)
            {
                <span class="hljs-keyword">continue</span>;
            }
            <span class="hljs-keyword">else</span>
            {
                errExit(<span class="hljs-string">"epoll_wait\n"</span>);
            }
        }
        printf(<span class="hljs-string">"Ready %d\n"</span>, ready);

        <span class="hljs-comment">// Deal with returned list of events</span>
        <span class="hljs-keyword">for</span> (j = <span class="hljs-number">0</span>; j &lt; ready; j++)
        {
            printf(<span class="hljs-string">" fd=%d; events: %s%s%s\n"</span>, evlist[j].data.fd,
                   (evlist[j].events &amp; EPOLLIN) ? <span class="hljs-string">"EPOLLIN "</span> : <span class="hljs-string">""</span>,
                   (evlist[j].events &amp; EPOLLHUP) ? <span class="hljs-string">"EPOLLHUP "</span> : <span class="hljs-string">""</span>,
                   (evlist[j].events &amp; EPOLLERR) ? <span class="hljs-string">"EPOLLERR "</span> : <span class="hljs-string">""</span>);

            <span class="hljs-keyword">if</span> (evlist[j].events &amp; EPOLLIN)
            {

                s = read(evlist[j].data.fd, buf, MAX_BUF);
                <span class="hljs-keyword">if</span> (s == <span class="hljs-number">-1</span>)
                {
                    errExit(<span class="hljs-string">"read"</span>);
                }
                printf(<span class="hljs-string">"  read %d bytes: %.*s\n"</span>, s, s, buf);
            }
            <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (evlist[j].events &amp; (EPOLLHUP | EPOLLERR))
            {
                printf(<span class="hljs-string">"Closing fd %d\n"</span>, evlist[j].data.fd);
                <span class="hljs-keyword">if</span> (close(evlist[j].data.fd) == <span class="hljs-number">-1</span>)
                {
                    errExit(<span class="hljs-string">"close"</span>);
                }
                numOpenFds--;
            }
        }
    }

    printf(<span class="hljs-string">"All file descriptors closed; bye\n"</span>);
    exit(EXIT_SUCCESS);
}
</code></pre>]]></content:encoded></item><item><title><![CDATA[Linux IPC: UNIX Domain Socket]]></title><description><![CDATA[In the previous article , we have discussed several facilities or mechanisms available on Linux to enable inter-process communications. One of them is Sockets. Due to its flexibility and performance, socket has been widely used in most IPC applicatio...]]></description><link>https://ariesgun.xyz/linux-ipc-unix-domain-socket</link><guid isPermaLink="true">https://ariesgun.xyz/linux-ipc-unix-domain-socket</guid><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Thu, 05 Jun 2025 12:00:08 GMT</pubDate><content:encoded><![CDATA[<p>In the previous <a target="_blank" href="https://ariesgun.xyz/linux-ipc-api">article</a> , we have discussed several facilities or mechanisms available on Linux to enable inter-process communications. One of them is Sockets. Due to its flexibility and performance, socket has been widely used in most IPC applications. </p>
<p>In this article, we will explore the UNIX Domain Socket which is used for inter-process communication on the same host system. We will focus on stream sockets. </p>
<h1 id="heading-unix-domain-socket">UNIX Domain Socket</h1>
<p>The socket address structure for UNIX Domain Socket is as follow: </p>
<pre><code>struct sockaddr_un {
\tsa_family_t sun_family;    <span class="hljs-comment">// Always AF_UNIX</span>
\tchar sun_path[<span class="hljs-number">108</span>];        <span class="hljs-comment">// NULL-terminated socket pathname</span>
};
</code></pre><p><img src="https://mymakebucket1242.s3.eu-north-1.amazonaws.com/work/hashnode/2088e113-8dc3-8061-a434-f6265b49068a/image-2098e113-8dc3-8041-9de5-fbff772e5e57.png" alt /></p>
<p>The operation flow will be as follows. </p>
<p>11111</p>
<p>The operation flow from the client side is as follows: </p>
<p>11# Simple Server-Client Implementation </p>
<p>Server </p>
<pre><code>#include &lt;sys/un.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;ctype.h&gt;

...

#define BUF_SIZE <span class="hljs-number">512</span>
#define SV_SOCK_PATH <span class="hljs-string">"/tmp/unix_socket"</span>

int main()
{
    <span class="hljs-attr">std</span>::cout &lt;&lt; <span class="hljs-string">"Hello, Linux IPC and Socket Domain!\n"</span>;

    struct sockaddr_un addr;
    int sfd, cfd;
    ssize_t numRead;
    char buf[BUF_SIZE];

    sfd = socket(AF_UNIX, SOCK_STREAM, <span class="hljs-number">0</span>);
    <span class="hljs-keyword">if</span> (sfd == <span class="hljs-number">-1</span>)
    {
        <span class="hljs-comment">// Exit error</span>
    }

    <span class="hljs-keyword">if</span> (remove(SV_SOCK_PATH) == <span class="hljs-number">-1</span> &amp;&amp; errno != ENOENT)
    {
        <span class="hljs-comment">// Exit error</span>
    }

    memset(&amp;addr, <span class="hljs-number">0</span>, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX; <span class="hljs-comment">// UNIX Domain address</span>
    strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path) - <span class="hljs-number">1</span>);

    <span class="hljs-keyword">if</span> (bind(sfd, (struct sockaddr *)&amp;addr, sizeof(struct sockaddr_un)) == <span class="hljs-number">-1</span>)
    {
        <span class="hljs-comment">// Exit error</span>
    }
    <span class="hljs-keyword">if</span> (listen(sfd, BACKLOG) == <span class="hljs-number">-1</span>)
    {
        <span class="hljs-comment">// Exit error</span>
    }

    <span class="hljs-comment">// Handle client connections iteratively</span>
    <span class="hljs-keyword">for</span> (;;)
    {
        <span class="hljs-attr">std</span>::cout &lt;&lt; <span class="hljs-string">"Waiting for a connection...\n"</span>;
        cfd = accept(sfd, NULL, NULL);
        <span class="hljs-keyword">if</span> (cfd == <span class="hljs-number">-1</span>)
        {
            <span class="hljs-comment">// Exit error;</span>
        }

        <span class="hljs-comment">// Transfer data</span>
        <span class="hljs-keyword">while</span> ((numRead = read(cfd, buf, BUF_SIZE)) &gt; <span class="hljs-number">0</span>)
        {
            <span class="hljs-keyword">if</span> (write(STDOUT_FILENO, buf, numRead) != numRead)
            {
                <span class="hljs-comment">// Exit error</span>
            }
        }

        <span class="hljs-keyword">if</span> (numRead == <span class="hljs-number">-1</span>)
        {
            <span class="hljs-comment">// Exit error</span>
        }
        close(cfd);
    }

    <span class="hljs-attr">std</span>::cout &lt;&lt; <span class="hljs-string">"Client disconnected.\n"</span>;

    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre><p>Client </p>
<pre><code>#include &lt;sys/un.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;ctype.h&gt;

...

#define BUF_SIZE <span class="hljs-number">512</span>
#define SV_SOCK_PATH <span class="hljs-string">"/tmp/unix_socket"</span>

int main(int argc, char *argv[])
{
    struct sockaddr_un addr;
    int sfd;
    ssize_t numRead;
    char buf[BUF_SIZE];

    sfd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, <span class="hljs-number">0</span>); <span class="hljs-comment">/* Create client socket */</span>
    <span class="hljs-keyword">if</span> (sfd == <span class="hljs-number">-1</span>) {
        <span class="hljs-comment">// Error exit </span>
    }

    <span class="hljs-comment">/* Construct server address, and make the connection */</span>
    memset(&amp;addr, <span class="hljs-number">0</span>, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path) - <span class="hljs-number">1</span>);

    <span class="hljs-keyword">if</span> (connect(sfd, (struct sockaddr *)&amp;addr,
                sizeof(struct sockaddr_un)) == <span class="hljs-number">-1</span>) {
        <span class="hljs-comment">// Error connect exit</span>
    }

    <span class="hljs-comment">/* Copy stdin to socket */</span>
    <span class="hljs-keyword">while</span> ((numRead = read(STDIN_FILENO, buf, BUF_SIZE)) &gt; <span class="hljs-number">0</span>) {
        <span class="hljs-keyword">if</span> (write(sfd, buf, numRead) != numRead) {
\t\t        <span class="hljs-comment">// error write</span>
        }

        ssize_t numBytes = read(sfd, buf, BUF_SIZE<span class="hljs-number">-1</span>);
        printf(<span class="hljs-string">"Read %zd bytes from server: "</span>, numBytes);
        <span class="hljs-keyword">if</span> (numBytes == <span class="hljs-number">-1</span>)
            <span class="hljs-keyword">continue</span>;
        buf[numBytes] = <span class="hljs-string">'\0'</span>; <span class="hljs-comment">/* Null-terminate the string */</span>
        printf(<span class="hljs-string">"Received from server: %s\n"</span>, buf);
    }
    <span class="hljs-keyword">if</span> (numRead == <span class="hljs-number">-1</span>)
        <span class="hljs-comment">// exit error read</span>
    exit(EXIT_SUCCESS); <span class="hljs-comment">/* Closes our socket; server sees EOF */</span>
}
</code></pre><h1 id="heading-concurrent-server-implementation">Concurrent Server Implementation</h1>
<p>The previous example is only a simple server-client implementation. It supports only one client connection at one time. Of course, this is usable in the real-world. In practice, we expect the server to be able to accept multiple connections at the same time. This also applies on communication between processes on the same host. So, how can we achieve this? </p>
<p>There are multiple implementation variants to build a concurrent server. </p>
<p>111You can find the implementation examples below. </p>
<h2 id="heading-pre-created-threads-in-server-pool">Pre-created Threads in Server Pool</h2>
<pre><code><span class="hljs-comment">// main.cpp</span>
#include &lt;iostream&gt;

#include &lt;sys/un.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;ctype.h&gt;

#include &lt;atomic&gt;
#include &lt;thread&gt;
#include &lt;vector&gt;
#include &lt;mutex&gt;
#include &lt;queue&gt;
#include &lt;condition_variable&gt;
#include &lt;functional&gt;

extern <span class="hljs-string">"C"</span>
{
#include <span class="hljs-string">"lib/error_functions.h"</span>
#include <span class="hljs-string">"lib/tlpi_hdr.h"</span>
}

#define BUF_SIZE <span class="hljs-number">10</span>
#define SV_SOCK_PATH <span class="hljs-string">"/tmp/ud_ucase"</span>
#define BACKLOG <span class="hljs-number">5</span>
#define MAX_THREADS <span class="hljs-number">5</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ServerPool</span> </span>{
<span class="hljs-attr">public</span>:
    ServerPool(size_t num_threads = MAX_THREADS)
        : running(<span class="hljs-literal">true</span>), active_threads(<span class="hljs-number">0</span>) 
    {
        threads.reserve(num_threads);
        <span class="hljs-keyword">for</span> (int i = <span class="hljs-number">0</span>; i &lt; MAX_THREADS; ++i) {
            threads.emplace_back(&amp;ServerPool::worker_loop, <span class="hljs-built_in">this</span>);
        }
    }

    ~ServerPool() {
        running = <span class="hljs-literal">false</span>;
        condition.notify_all();
        <span class="hljs-keyword">for</span> (auto &amp;thread: threads) {
            <span class="hljs-keyword">if</span> (thread.joinable()) {
                thread.join();
            }
        }
    }

    <span class="hljs-keyword">void</span> add_task(std::<span class="hljs-function"><span class="hljs-keyword">function</span>&lt;<span class="hljs-title">void</span>(<span class="hljs-params"></span>)&gt; <span class="hljs-title">task</span>) </span>{
        {
            <span class="hljs-attr">std</span>::cout &lt;&lt; <span class="hljs-string">"Adding task to the queue. Current active threads: "</span> &lt;&lt; active_threads.load() &lt;&lt; std::endl;
            std::unique_lock&lt;std::mutex&gt; lock(threads_mutex);
            tasks.push(std::move(task));

            std::cout &lt;&lt; <span class="hljs-string">"Task added. Total tasks: "</span> &lt;&lt; tasks.size() &lt;&lt; std::endl;  
        }
        condition.notify_one();
    }

    int get_active_threads() <span class="hljs-keyword">const</span> {
        <span class="hljs-keyword">return</span> active_threads.load();
    }

    <span class="hljs-keyword">void</span> stop() {
        running = <span class="hljs-literal">false</span>;
        condition.notify_all();
    }

    <span class="hljs-keyword">void</span> wait_for_completion() {
        <span class="hljs-attr">std</span>::unique_lock&lt;std::mutex&gt; lock(threads_mutex);
        condition.wait(lock, [<span class="hljs-built_in">this</span>] { <span class="hljs-keyword">return</span> tasks.empty() &amp;&amp; active_threads.load() == <span class="hljs-number">0</span>; });
    }

    <span class="hljs-keyword">void</span> reset() {
        <span class="hljs-attr">std</span>::unique_lock&lt;std::mutex&gt; lock(threads_mutex);
        <span class="hljs-keyword">while</span> (!tasks.empty()) {
            tasks.pop();
        }
        active_threads = <span class="hljs-number">0</span>;
    }

<span class="hljs-attr">private</span>:
    <span class="hljs-keyword">void</span> worker_loop() {
        <span class="hljs-keyword">while</span>(<span class="hljs-literal">true</span>) {
            <span class="hljs-attr">std</span>::<span class="hljs-function"><span class="hljs-keyword">function</span>&lt;<span class="hljs-title">void</span>(<span class="hljs-params"></span>)&gt; <span class="hljs-title">task</span>;
            </span>{
                <span class="hljs-attr">std</span>::unique_lock&lt;std::mutex&gt; lock(threads_mutex);
                condition.wait(lock, [<span class="hljs-built_in">this</span>] {
                    <span class="hljs-keyword">return</span> !tasks.empty() || !running;
                });

                std::cout &lt;&lt; <span class="hljs-string">"Thread "</span> &lt;&lt; std::this_thread::get_id() &lt;&lt; <span class="hljs-string">" checking for tasks. Active threads: "</span> &lt;&lt; active_threads.load() &lt;&lt; std::endl;

                <span class="hljs-keyword">if</span> (!running &amp;&amp; tasks.empty()) {
                    <span class="hljs-keyword">return</span>; <span class="hljs-comment">// Exit if no tasks and not running</span>
                }

                task = std::move(tasks.front());
                tasks.pop();
                active_threads++;
            }

            <span class="hljs-keyword">try</span> {
                <span class="hljs-attr">std</span>::cout &lt;&lt; <span class="hljs-string">"Thread "</span> &lt;&lt; std::this_thread::get_id() &lt;&lt; <span class="hljs-string">" executing task. Active threads: "</span> &lt;&lt; active_threads.load() &lt;&lt; std::endl;
                task();
            } <span class="hljs-keyword">catch</span> (<span class="hljs-keyword">const</span> std::exception&amp; e) {
                <span class="hljs-attr">std</span>::cerr &lt;&lt; <span class="hljs-string">"Task execution error: "</span> &lt;&lt; e.what() &lt;&lt; std::endl;
            }

            active_threads--;
        }
    }

    <span class="hljs-attr">std</span>::vector&lt;std::thread&gt; threads;
    std::queue&lt;std::<span class="hljs-function"><span class="hljs-keyword">function</span>&lt;<span class="hljs-title">void</span>(<span class="hljs-params"></span>)&gt;&gt; <span class="hljs-title">tasks</span>;
    <span class="hljs-title">std</span>::<span class="hljs-title">condition_variable</span> <span class="hljs-title">condition</span>;
    <span class="hljs-title">std</span>::<span class="hljs-title">mutex</span> <span class="hljs-title">threads_mutex</span>;
    <span class="hljs-title">std</span>::<span class="hljs-title">atomic</span>&lt;<span class="hljs-title">bool</span>&gt; <span class="hljs-title">running</span></span>{<span class="hljs-literal">true</span>};
    std::atomic&lt;int&gt; active_threads{<span class="hljs-number">0</span>};
};



int main()
{
    <span class="hljs-attr">std</span>::cout &lt;&lt; <span class="hljs-string">"Hello, Linux IPC and Socket Domain!\n"</span>;

    struct sockaddr_un addr;
    int sfd, cfd;
    ssize_t numRead;
    char buf[BUF_SIZE];

    sfd = socket(AF_UNIX, SOCK_STREAM, <span class="hljs-number">0</span>);
    <span class="hljs-keyword">if</span> (sfd == <span class="hljs-number">-1</span>)
    {
        errExit(<span class="hljs-string">"socket"</span>);
    }

    <span class="hljs-keyword">if</span> (remove(SV_SOCK_PATH) == <span class="hljs-number">-1</span> &amp;&amp; errno != ENOENT)
    {
        errExit(<span class="hljs-string">"remove-%s"</span>, SV_SOCK_PATH);
    }

    memset(&amp;addr, <span class="hljs-number">0</span>, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX; <span class="hljs-comment">// UNIX Domain address</span>
    strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path) - <span class="hljs-number">1</span>);

    <span class="hljs-keyword">if</span> (bind(sfd, (struct sockaddr *)&amp;addr, sizeof(struct sockaddr_un)) == <span class="hljs-number">-1</span>)
    {
        errExit(<span class="hljs-string">"bind"</span>);
    }
    <span class="hljs-keyword">if</span> (listen(sfd, BACKLOG) == <span class="hljs-number">-1</span>)
    {
        errExit(<span class="hljs-string">"listen"</span>);
    }

    ServerPool server_pool;

    <span class="hljs-comment">// Handle client connections iteratively</span>
    <span class="hljs-keyword">for</span> (;;)
    {
        <span class="hljs-attr">std</span>::cout &lt;&lt; <span class="hljs-string">"Waiting for a connection...\n"</span>;
        cfd = accept(sfd, NULL, NULL);
        <span class="hljs-keyword">if</span> (cfd == <span class="hljs-number">-1</span>)
        {
            errExit(<span class="hljs-string">"accept"</span>);
        }

        <span class="hljs-attr">std</span>::cout &lt;&lt; <span class="hljs-string">"Client connected.\n"</span>;
        server_pool.add_task([cfd]() {
            ssize_t numRead;
            char buf[BUF_SIZE];

            <span class="hljs-comment">// Read data from the client</span>
            <span class="hljs-keyword">while</span> ((numRead = read(cfd, buf, BUF_SIZE)) &gt; <span class="hljs-number">0</span>)
            {
                <span class="hljs-attr">std</span>::cout &lt;&lt; <span class="hljs-string">"Received "</span> &lt;&lt; numRead &lt;&lt; <span class="hljs-string">" bytes: "</span>;
                <span class="hljs-keyword">for</span> (ssize_t i = <span class="hljs-number">0</span>; i &lt; numRead; ++i)
                {
                    <span class="hljs-attr">std</span>::cout &lt;&lt; static_cast&lt;char&gt;(toupper(buf[i]));
                }
                <span class="hljs-attr">std</span>::cout &lt;&lt; std::endl;

                <span class="hljs-comment">// Echo back to the client</span>
                <span class="hljs-keyword">if</span> (write(cfd, buf, numRead) != numRead)
                {
                    errMsg(<span class="hljs-string">"partial/failed write"</span>);
                }
            }

            <span class="hljs-keyword">if</span> (numRead == <span class="hljs-number">-1</span>)
            {
                errExit(<span class="hljs-string">"read"</span>);
            }

            close(cfd); <span class="hljs-comment">// Close the client socket</span>
        });
        std::cout &lt;&lt; <span class="hljs-string">"Task added to server pool. Active threads: "</span> &lt;&lt; server_pool.get_active_threads() &lt;&lt; std::endl;
    }

    <span class="hljs-attr">std</span>::cout &lt;&lt; <span class="hljs-string">"Client disconnected.\n"</span>;

    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre><h2 id="heading-single-process-concurrent-server-using-epoll">Single Process Concurrent Server using <strong>epoll()</strong></h2>
<pre><code><span class="hljs-comment">// main.cpp</span>
#include &lt;iostream&gt;

#include &lt;sys/un.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;ctype.h&gt;

#include &lt;atomic&gt;
#include &lt;thread&gt;
#include &lt;vector&gt;
#include &lt;mutex&gt;
#include &lt;queue&gt;
#include &lt;condition_variable&gt;
#include &lt;functional&gt;

extern <span class="hljs-string">"C"</span>
{
#include <span class="hljs-string">"lib/error_functions.h"</span>
#include <span class="hljs-string">"lib/tlpi_hdr.h"</span>
}

#include &lt;sys/epoll.h&gt;

#define BUF_SIZE <span class="hljs-number">512</span>
#define SV_SOCK_PATH <span class="hljs-string">"/tmp/ud_ucase"</span>
#define BACKLOG <span class="hljs-number">5</span>
#define MAX_THREADS <span class="hljs-number">5</span>
#define MAX_BUF <span class="hljs-number">512</span>

int main()
{
    <span class="hljs-attr">std</span>::cout &lt;&lt; <span class="hljs-string">"Hello, Linux IPC and Socket Domain!\n"</span>;

    struct sockaddr_un addr;
    int sfd, cfd;
    ssize_t numRead;
    char buf[BUF_SIZE];

    sfd = socket(AF_UNIX, SOCK_STREAM, <span class="hljs-number">0</span>);
    <span class="hljs-keyword">if</span> (sfd == <span class="hljs-number">-1</span>)
    {
        errExit(<span class="hljs-string">"socket"</span>);
    }

    <span class="hljs-keyword">if</span> (remove(SV_SOCK_PATH) == <span class="hljs-number">-1</span> &amp;&amp; errno != ENOENT)
    {
        errExit(<span class="hljs-string">"remove-%s"</span>, SV_SOCK_PATH);
    }

    memset(&amp;addr, <span class="hljs-number">0</span>, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX; <span class="hljs-comment">// UNIX Domain address</span>
    strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path) - <span class="hljs-number">1</span>);

    <span class="hljs-keyword">if</span> (bind(sfd, (struct sockaddr *)&amp;addr, sizeof(struct sockaddr_un)) == <span class="hljs-number">-1</span>)
    {
        errExit(<span class="hljs-string">"bind"</span>);
    }
    <span class="hljs-keyword">if</span> (listen(sfd, BACKLOG) == <span class="hljs-number">-1</span>)
    {
        errExit(<span class="hljs-string">"listen"</span>);
    }

    int connected_clients = <span class="hljs-number">0</span>;
    <span class="hljs-comment">// Create an epoll</span>
    int epfd = epoll_create(<span class="hljs-number">1</span>);
    <span class="hljs-keyword">if</span> (epfd == <span class="hljs-number">-1</span>)
    {
        errExit(<span class="hljs-string">"epoll_create"</span>);
    }

    int epfd2 = epoll_create(<span class="hljs-number">5</span>);
    <span class="hljs-keyword">if</span> (epfd2 == <span class="hljs-number">-1</span>) {
        errExit(<span class="hljs-string">"epoll_create2"</span>);
    }

    struct epoll_event ev;
    ev.events = EPOLLIN; <span class="hljs-comment">// Interested in read events</span>
    ev.data.fd = sfd; <span class="hljs-comment">// Monitor the server socket</span>

    <span class="hljs-keyword">if</span> (epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &amp;ev) == <span class="hljs-number">-1</span>)
    {
        errExit(<span class="hljs-string">"epoll_ctl"</span>);
    }
    <span class="hljs-attr">std</span>::cout &lt;&lt; <span class="hljs-string">"Server is listening on "</span> &lt;&lt; SV_SOCK_PATH &lt;&lt; std::endl;

    #define MAX_EVENTS <span class="hljs-number">10</span>
    struct epoll_event evlist[MAX_EVENTS];

    <span class="hljs-comment">// Handle client connections iteratively</span>
    <span class="hljs-keyword">for</span> (;;)
    {
        <span class="hljs-attr">std</span>::cout &lt;&lt; <span class="hljs-string">"Waiting for a connection...\n"</span>;
        int ready = epoll_wait(epfd, evlist, MAX_EVENTS, <span class="hljs-number">-1</span>);
        <span class="hljs-keyword">if</span> (ready == <span class="hljs-number">-1</span>)
        {
            <span class="hljs-keyword">if</span> (errno == EINTR)
            {
                <span class="hljs-keyword">continue</span>; <span class="hljs-comment">// Interrupted by a signal, retry</span>
            }
            errExit(<span class="hljs-string">"epoll_wait"</span>);
        }

        printf(<span class="hljs-string">"Ready %d events\n"</span>, ready);

        <span class="hljs-keyword">for</span> (int j=<span class="hljs-number">0</span>; j&lt;ready; j++) {
            <span class="hljs-attr">std</span>::cout &lt;&lt; <span class="hljs-string">"Event detected on fd: "</span> &lt;&lt; evlist[j].data.fd &lt;&lt; <span class="hljs-string">" "</span> &lt;&lt; evlist[j].events &lt;&lt; <span class="hljs-string">"\n"</span>;
            <span class="hljs-keyword">if</span> (evlist[j].events &amp; EPOLLIN) <span class="hljs-comment">// Check if the event is for reading</span>
            {
                <span class="hljs-keyword">if</span> (evlist[j].data.fd != sfd) {
                    int numRead = read(evlist[j].data.fd, buf, MAX_BUF);
                    <span class="hljs-keyword">if</span> (numRead == <span class="hljs-number">-1</span>)
                      errExit(<span class="hljs-string">"read"</span>);

                    std::cout &lt;&lt; <span class="hljs-string">"Received "</span> &lt;&lt; numRead &lt;&lt; <span class="hljs-string">" bytes: "</span>;
                    <span class="hljs-keyword">for</span> (ssize_t i = <span class="hljs-number">0</span>; i &lt; numRead; ++i)
                    {
                        buf[i] = toupper(buf[i]);
                    }

                    <span class="hljs-comment">// Echo back to the client</span>
                    <span class="hljs-keyword">if</span> (write(evlist[j].data.fd, buf, numRead) != numRead)
                    {
                        errMsg(<span class="hljs-string">"partial/failed write"</span>);
                    }
                } <span class="hljs-keyword">else</span> {
                    <span class="hljs-attr">std</span>::cout &lt;&lt; <span class="hljs-string">"New connection detected.\n"</span>;
                    int cfd = accept(sfd, NULL, NULL);
                    <span class="hljs-keyword">if</span> (cfd == <span class="hljs-number">-1</span>)
                    {
                        errExit(<span class="hljs-string">"accept"</span>);
                    }

                    <span class="hljs-comment">// Add the new client socket to the epoll instance</span>
                    struct epoll_event clientEv;
                    clientEv.events = EPOLLIN; <span class="hljs-comment">// Interested in read events </span>
                    clientEv.data.fd = cfd;
                    <span class="hljs-keyword">if</span> (epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &amp;clientEv) == <span class="hljs-number">-1</span>)
                    {
                        errExit(<span class="hljs-string">"epoll_ctl"</span>);
                    }
                    connected_clients++;
                }
            }

            <span class="hljs-keyword">if</span> (evlist[j].events &amp; (EPOLLHUP | EPOLLERR)) <span class="hljs-comment">// Check for hangup or error</span>
            {
                <span class="hljs-attr">std</span>::cout &lt;&lt; <span class="hljs-string">"Closing fd "</span> &lt;&lt; evlist[j].data.fd &lt;&lt; <span class="hljs-string">"\n"</span>;

                <span class="hljs-comment">// Remove the client socket from the epoll instance</span>
                <span class="hljs-keyword">if</span> (epoll_ctl(epfd, EPOLL_CTL_DEL, evlist[j].data.fd, NULL) == <span class="hljs-number">-1</span>)
                {
                    errExit(<span class="hljs-string">"epoll_ctl-del"</span>);
                }
                <span class="hljs-keyword">if</span> (close(evlist[j].data.fd) == <span class="hljs-number">-1</span>)
                {
                    errExit(<span class="hljs-string">"close"</span>);
                }
                connected_clients--;
            }
            <span class="hljs-keyword">else</span>
            {
                <span class="hljs-attr">std</span>::cerr &lt;&lt; <span class="hljs-string">"Unexpected event detected.\n"</span>;
                <span class="hljs-keyword">continue</span>; <span class="hljs-comment">// Skip unexpected events</span>
            }
        }

        printf(<span class="hljs-string">"Connected Device %d\n"</span>, connected_clients);
    }

    <span class="hljs-attr">std</span>::cout &lt;&lt; <span class="hljs-string">"Client disconnected.\n"</span>;

    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre><p># </p>
]]></content:encoded></item><item><title><![CDATA[Linux Inter Process Communication API]]></title><description><![CDATA[A few weeks ago, I attended a local meetup where someone presented on asynchronous Inter-Process Communication (IPC) using Linux IPC APIs [1]. The speaker showcased different Linux APIs for IPC, such as FIFO, Message Queue, and Ethernet Socket. I fin...]]></description><link>https://ariesgun.xyz/linux-ipc-api</link><guid isPermaLink="true">https://ariesgun.xyz/linux-ipc-api</guid><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Wed, 04 Jun 2025 15:13:31 GMT</pubDate><content:encoded><![CDATA[<p>A few weeks ago, I attended a local meetup where someone presented on asynchronous Inter-Process Communication (IPC) using Linux IPC APIs [1]. The speaker showcased different Linux APIs for IPC, such as FIFO, Message Queue, and Ethernet Socket. I find this talk quite interesting and inspire me to explore these features that enable asynchronous inter-process communication on Linux. </p>
<p>Actually this is not my first time I encounter this. In my previous project, the company built their own Remote Procedure Call (RPC) framework. The framework supports both synchronous and asynchronous inter-process communication between processes. It supports not only the request-reply pattern but also notification (subscribe and publish) pattern. </p>
<p>I know how to use the framework to facilitate IPC for our applications. After using it for a while, it made me wonder how it works and how it was built. Admittedly I did not spend time to fully understand how the framework was built. However, after attending the meetup, it becomes clear to me how the puzzle pieces are connected. Now, with the fresh insights from the meetup, I would like to document what I learned in this and upcoming articles. </p>
<h1 id="heading-linux-inter-process-communication">Linux Inter-Process Communication</h1>
<p>Linux provides facilities to enable processes and threads to exchange data with one another and to synchronize their actions. In [2], the author describes the communication facilities available on Linux: Pipe, FIFO, Message Queue, stream socket, shared memory, etc. </p>
<p>I won’t discuss all available facilities in this article and focus only on FIFO, Message Queue, and Stream Socket. I think these are the options commonly used in real world applications. These are also the ones presented in the meetup. </p>
<p><img src="https://mymakebucket1242.s3.eu-north-1.amazonaws.com/work/hashnode/1f98e113-8dc3-8097-9ffc-f9201b6bc93b/image-2088e113-8dc3-80da-85fb-ed3da430a499.png" alt /></p>
<h2 id="heading-fifo">FIFO</h2>
<p>FIFO (First-In First-Out) is a mechanism to allow a process to communicate with another process. It operates as a un-directional data channel. A process writes data into the FIFO and another process reads the data from it. This mechanism is suitable in a simple producer-consumer scenario. In order to allow bidirectional communications, two FIFOs will be needed. </p>
<p>A FIFO can be created by calling <strong>mkfifo</strong> given the path name. Upon created, the FIFO can be found in the file system. </p>
<pre><code>#include &lt;sys/stat.h&gt;

int mkfifo(<span class="hljs-keyword">const</span> char *pathname, mode_t mode);
</code></pre><h2 id="heading-message-queue">Message Queue</h2>
<p>Message Queue provides a more structured form of communication between two processes. It allows messages which consist of a numeric type plus a body containing data, to be exchanged. Hence, the message boundaries can be preserved between calls. </p>
<p>Compared to FIFO, message queue supports asynchronous messaging. The messages sent by the producer can be queued, and the receiver can retrieve the messages later. </p>
<p>The Message Queue APIs are as follows. </p>
<pre><code>#include &lt;fcntl.h&gt; <span class="hljs-comment">/* Defines O_* constants */</span>
#include &lt;sys/stat.h&gt; <span class="hljs-comment">/* Defines mode constants */</span>
#include &lt;mqueue.h&gt;

mqd_t mq_open(<span class="hljs-keyword">const</span> char *name, int oflag, ...
 <span class="hljs-comment">/* mode_t mode, struct mq_attr *attr */</span>);

int mq_send(mqd_t mqdes, <span class="hljs-keyword">const</span> char *msg_ptr, size_t msg_len,
 unsigned int msg_prio);

ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len,
 unsigned int *msg_prio);

int mq_close(mqd_t mqdes);

int mq_unlink(<span class="hljs-keyword">const</span> char *name);
</code></pre><h2 id="heading-socket">Socket</h2>
<p>Socket is a method of IPC that allow data to be exchanged bidirectionally either on the same host (UNIX Domain Socket) or on different hosts connected by a network (IPv4 or IPv6). Every sockets implementation provides at least two types: stream and datagram sockets. In the Internet domain, the stream socket uses TCP and the datagram socket uses UDP. The first one is reliable while the second one is not. </p>
<p>The Linux Programming Interface book by Michael Kerrisk shows the overview of system calls used in both types of sockets. </p>
<p><img src="https://mymakebucket1242.s3.eu-north-1.amazonaws.com/work/hashnode/1f98e113-8dc3-8097-9ffc-f9201b6bc93b/image-2088e113-8dc3-80da-85fb-ed3da430a499.png" alt /></p>
<p>The main advantage of sockets is it accepts multiple client connections on a single server socket. This is useful in server environments where scalability and simultaneous connections are important. </p>
<p>As you can guess, sockets are commonly used in most real-world applications. They provide a robust solution when you need both the reliability of structured communication and the flexibility of managing multiple concurrent connections. </p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>In this article, I have shown some of the commonly used Linux mechanisms for inter-process communication. In the upcoming articles, we will see how to implement IPC using UNIX domain socket and how to use <strong>epoll</strong> system call to allow asynchronous communication calls. </p>
<h1 id="heading-references">References</h1>
<p>[1] Exploring Linux API slide by Henrique Marks. </p>
<p>[2] The Linux Programming Interface by Michael Kerrisk. </p>
]]></content:encoded></item><item><title><![CDATA[BLE: A Deep Dive into GATT]]></title><description><![CDATA[In the previous article , we explored the basics of Bluetooth Low Energy (BLE). In this article, we will take a closer look at BLE GATT (Generic Attribute Profile) — unpacking what it is and how it works. 
BLE and GATT
BLE is a wireless technology de...]]></description><link>https://ariesgun.xyz/bluetooth-low-energy-gatt</link><guid isPermaLink="true">https://ariesgun.xyz/bluetooth-low-energy-gatt</guid><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Fri, 30 May 2025 13:49:00 GMT</pubDate><content:encoded><![CDATA[<p>In the previous <a target="_blank" href="https://ariesgun.xyz/bluetooth-low-energy-101">article</a> , we explored the basics of Bluetooth Low Energy (BLE). In this article, we will take a closer look at BLE GATT (Generic Attribute Profile) — unpacking what it is and how it works. </p>
<h2 id="heading-ble-and-gatt">BLE and GATT</h2>
<p>BLE is a wireless technology designed for short-range communication between devices while keeping power consumption to a minimum. Hence, it is commonly used in the Internet of Things (IoT) landscape. It does not only manages how data is organized and exchanged between devices, but also provides a standardized and power efficient way for BLE devices to communicate. </p>
<p>GATT, short for Generic Attribute Profile, is the protocol that defines the structure and behavior for communication between BLE-enabled devices. It extends the Attribute Protocol (ATT) by defining several key concepts: </p>
<ul>
<li>Characteristics - Individual data elements. This is where the actual data is stored. </li>
</ul>
<ul>
<li>Services - Collections of related characteristics that group data into logical entities. </li>
</ul>
<ul>
<li>Profile - High level definition of how a device should behave to enable a specific application. It contains one or more services. </li>
</ul>
<p><img src="https://mymakebucket1242.s3.eu-north-1.amazonaws.com/work/hashnode/1f38e113-8dc3-8097-91eb-e449b78c1b60/image-2018e113-8dc3-80d4-a0e2-c49958d6d89f.png" alt /></p>
<p>A profile sits on top of the hierarchy. It contains a pre-defined collection of services. Profiles can be either pre-defined by the Bluetooth SIG ( <a target="_blank" href="https://www.bluetooth.com/specifications/specs/">Profiles Overview)</a> or by the peripheral designers. One example is the <a target="_blank" href="https://www.bluetooth.com/specifications/specs/heart-rate-profile-1-0/">Heart Rate Profile,</a> which combines the mandatory Heart Rate Service and the optional Device Information Service. </p>
<p>Services are used to break data up into logical entities (characteristics).  A service can be identified by either a 16-bit UUID (officially adopted BLE services) or a 128-bit UUID (for custom services).  If we look at the official <a target="_blank" href="https://www.bluetooth.com/specifications/specs/heart-rate-service-1-0/">Heart Rate Service,</a> we can see that the service has one mandatory characteristic (i.e., Heart Rate Measurement) and two optional ones (i.e., Body Sensor Location and Heart Rate Control Point). Each characteristics then contains a single data point. </p>
<h2 id="heading-gatt-operations">GATT Operations</h2>
<p>As mentioned before, data is stored within characteristics. Once the data has been structured, clients interact with the data through several operations: </p>
<ul>
<li><em>Read</em> . </li>
</ul>
<p>The client requests to read the value of a Characteristic from the server. </p>
<ul>
<li><em>Write</em> . </li>
</ul>
<p>The client can also send data to the server to update the value of a characteristic. There are two variants. The first one is a write that requires an acknowledgment from the server and the second one is a write command without a response/ acknowledgement. </p>
<ul>
<li><em>Notify</em> . </li>
</ul>
<p>The server can be configured to automatically send updates of a characteristic’s value to the client whenever the value changes. To configure the server, the client needs to send a subscription request. </p>
<ul>
<li><em>Indicate</em> . </li>
</ul>
<p>This is similar to notify; the difference is that the client must send an acknowledgement back to the server to confirm it received the data. </p>
<h2 id="heading-example-using-nimble">Example using NimBLE</h2>
<p>NimBLE is a lightweight, open-source Bluetooth Low Energy (BLE) stack designed especially for resource-constrained devices. It has been integrated into ESP-IDF framework for ESP32 devices. The main advantages of NimBLE are its modular architecture and small memory footprint. This makes NimBLE an excellent choice for building BLE applications. </p>
<h3 id="heading-write-and-read-operations">Write and Read Operations</h3>
<p>In NimBLE, all GATT services and characteristics can be defined in the gatt_svr_svcs_service table. For each characteristics, we can specific where it supports read, write, notify, and indicate operations. We also need to assign callback in the access_cb variable and the characteristic handle in the val_handle field.  Below is an example showing a Heart Rate service definition: </p>
<pre><code><span class="hljs-comment">/* Heart rate service UUID */</span>
<span class="hljs-keyword">static</span> <span class="hljs-keyword">const</span> ble_uuid16_t heart_rate_svc_uuid = BLE_UUID16_INIT(<span class="hljs-number">0x180D</span>);

<span class="hljs-comment">/* Characteristics Handle */</span>
<span class="hljs-keyword">static</span> uint16_t heart_rate_chr_val_handle;
<span class="hljs-keyword">static</span> uint16_t body_sensor_loc_chr_val_handle;

<span class="hljs-comment">/* Callback function */</span>
<span class="hljs-keyword">static</span> int gatt_svr_chr_access_heart_rate(uint16_t conn_handle, uint16_t attr_handle,
                                          struct ble_gatt_access_ctxt *ctxt, <span class="hljs-keyword">void</span> *arg);

<span class="hljs-comment">/* GATT services table */</span>
<span class="hljs-keyword">static</span> <span class="hljs-keyword">const</span> struct ble_gatt_svc_def gatt_svr_svcs[] = {
    { <span class="hljs-comment">/* Service: Heart-rate */</span>
     .type = BLE_GATT_SVC_TYPE_PRIMARY,
     .uuid = &amp;heart_rate_svc_uuid.u,
     .characteristics = (struct ble_gatt_chr_def[]){
         {
             <span class="hljs-comment">/* Characteristic: Heart-rate measurement */</span>
             .uuid = BLE_UUID16_DECLARE(GATT_HRS_MEASUREMENT_UUID),
             .access_cb = gatt_svr_chr_access_heart_rate,
             .val_handle = &amp;heart_rate_chr_val_handle,
             .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
         },
         {
             <span class="hljs-comment">/* Characteristic: Body sensor location */</span>
             .uuid = BLE_UUID16_DECLARE(GATT_HRS_BODY_SENSOR_LOC_UUID),
             .access_cb = gatt_svr_chr_access_heart_rate,
             .val_handle = &amp;body_sensor_loc_chr_val_handle,
             .flags = BLE_GATT_CHR_F_READ,
         },
         {
             <span class="hljs-number">0</span>, <span class="hljs-comment">/* No more characteristics in this service */</span>
         },
     }
    },
    {
     <span class="hljs-number">0</span>, <span class="hljs-comment">/* No more services */</span>
      },
};
</code></pre><p>The accompanying callback function handles events. In this example, it handles only read operations because the characteristic only supports read operations. </p>
<pre><code><span class="hljs-keyword">static</span> int gatt_svr_chr_access_heart_rate(uint16_t conn_handle, uint16_t attr_handle,
                                  struct ble_gatt_access_ctxt *ctxt, <span class="hljs-keyword">void</span> *arg)
{
    <span class="hljs-comment">/* Local variables */</span>
    int rc;

    <span class="hljs-comment">/* Handle access events */</span>
    <span class="hljs-comment">/* Note: LED characteristic is write only */</span>
    <span class="hljs-keyword">switch</span> (ctxt-&gt;op)
    {

    <span class="hljs-comment">/* Read characteristic event */</span>
    <span class="hljs-keyword">case</span> BLE_GATT_ACCESS_OP_READ_CHR:
        <span class="hljs-comment">/* Verify connection handle */</span>
        <span class="hljs-keyword">if</span> (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
            ESP_LOGI(TAG, <span class="hljs-string">"characteristic read; conn_handle=%d attr_handle=%d"</span>,
                    conn_handle, attr_handle);
        } <span class="hljs-keyword">else</span> {
            ESP_LOGI(TAG, <span class="hljs-string">"characteristic read by nimble stack; attr_handle=%d"</span>,
                    attr_handle);
        }

        <span class="hljs-comment">/* Verify attribute handle */</span>
        <span class="hljs-keyword">if</span> (attr_handle == heart_rate_chr_val_handle) {
            <span class="hljs-comment">/* Update access buffer value */</span>
            heart_rate_chr_val[<span class="hljs-number">1</span>] = get_heart_rate();
            rc = os_mbuf_append(ctxt-&gt;om, &amp;heart_rate_chr_val,
                                sizeof(heart_rate_chr_val));
            <span class="hljs-keyword">return</span> rc == <span class="hljs-number">0</span> ? <span class="hljs-number">0</span> : BLE_ATT_ERR_INSUFFICIENT_RES;
        }
        goto error;

    <span class="hljs-comment">/* Write characteristic event */</span>
    <span class="hljs-keyword">case</span> BLE_GATT_ACCESS_OP_WRITE_CHR:
    <span class="hljs-comment">/* Unknown event */</span>
    <span class="hljs-keyword">default</span>:
        goto error;
    }

<span class="hljs-attr">error</span>:
    ESP_LOGE(tag,
             <span class="hljs-string">"unexpected access operation to led characteristic, opcode: %d"</span>,
             ctxt-&gt;op);
    <span class="hljs-keyword">return</span> BLE_ATT_ERR_UNLIKELY;
}
</code></pre><h3 id="heading-notification-and-indication">Notification and Indication</h3>
<p>The NimBLE library provides APIs to handle notification and indication operations. </p>
<pre><code>int ble_gatts_notify_custom(uint16_t conn_handle, uint16_t chr_val_handle,
                            struct os_mbuf *txom);

int ble_gatts_indicate_custom(uint16_t conn_handle, uint16_t chr_val_handle,
                              struct os_mbuf *txom);
</code></pre><p>These operations will be performed only if the client specifies that it would like to subscribe to any changes. The subscription event will be handled at the GAP layer. This can be done by handling the BLE_GAP_EVENT_SUBSCRIBE event. </p>
<pre><code><span class="hljs-keyword">static</span> int gap_event_handler(struct ble_gap_event *event, <span class="hljs-keyword">void</span> *arg) {
    ...

    <span class="hljs-comment">/* Subscribe event */</span>
    <span class="hljs-keyword">case</span> BLE_GAP_EVENT_SUBSCRIBE:
        <span class="hljs-comment">/* Print subscription info to log */</span>
        ESP_LOGI(TAG,
                <span class="hljs-string">"subscribe event; conn_handle=%d attr_handle=%d "</span>
                <span class="hljs-string">"reason=%d prevn=%d curn=%d previ=%d curi=%d"</span>,
                event-&gt;subscribe.conn_handle, event-&gt;subscribe.attr_handle,
                event-&gt;subscribe.reason, event-&gt;subscribe.prev_notify,
                event-&gt;subscribe.cur_notify, event-&gt;subscribe.prev_indicate,
                event-&gt;subscribe.cur_indicate);

        <span class="hljs-comment">/* GATT subscribe event callback */</span>
        <span class="hljs-comment">/* Check connection handle */</span>
            <span class="hljs-keyword">if</span> (event-&gt;subscribe.conn_handle != BLE_HS_CONN_HANDLE_NONE) {
                ESP_LOGI(TAG, <span class="hljs-string">"subscribe event; conn_handle=%d attr_handle=%d"</span>,
                        event-&gt;subscribe.conn_handle, event-&gt;subscribe.attr_handle);
            } <span class="hljs-keyword">else</span> {
                ESP_LOGI(TAG, <span class="hljs-string">"subscribe by nimble stack; attr_handle=%d"</span>,
                        event-&gt;subscribe.attr_handle);
            }

            <span class="hljs-comment">/* Check attribute handle */</span>
            <span class="hljs-keyword">if</span> (event-&gt;subscribe.attr_handle == heart_rate_chr_val_handle) {
                <span class="hljs-comment">/* Update heart rate subscription status */</span>
                heart_rate_chr_conn_handle = event-&gt;subscribe.conn_handle;
                heart_rate_chr_conn_handle_inited = <span class="hljs-literal">true</span>;
                heart_rate_ind_status = event-&gt;subscribe.cur_indicate;
            }
}
</code></pre><p>This handle logs subscription events and updates internal state, ensuring the server sends notifications or indications only when appropriate. </p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>BLE GATT’s structured approach—organizing data into characteristics, services, and profiles—is foundational for efficient and interoperable BLE communication in IoT devices. Whether you’re building wearable health monitors, smart home gadgets, or industrial sensors, understanding and leveraging GATT operations can greatly enhance your application’s performance and reliability. </p>
<p>NimBLE, with its lightweight design and efficient API set, stands out as a powerful tool for implementing these concepts on resource-constrained devices like the ESP32. Its modular architecture ensures that you can easily scale, customize, and optimize your BLE applications as your requirements evolve. </p>
<h2 id="heading-references">References</h2>
<p>[1] <a target="_blank" href="https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/ble/get-started/ble-data-exchange.html">ESP-IDF BLE Data Exchange</a> </p>
<p>[2] <a target="_blank" href="https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/ble/get-started/ble-data-exchange.html">Adafruit GATT</a> </p>
]]></content:encoded></item><item><title><![CDATA[Bluetooth Low Energy (BLE) 101]]></title><description><![CDATA[Bluetooth is a short-range wireless technology standard that was introduced in 1998. It is mainly used for exchanging data between portable devices over short distances, such as wireless headphones, wireless speakers, etc.
Bluetooth Low Energy (BLE) ...]]></description><link>https://ariesgun.xyz/bluetooth-low-energy-101</link><guid isPermaLink="true">https://ariesgun.xyz/bluetooth-low-energy-101</guid><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Wed, 28 May 2025 22:06:29 GMT</pubDate><content:encoded><![CDATA[<p>Bluetooth is a short-range wireless technology standard that was introduced in 1998. It is mainly used for exchanging data between portable devices over short distances, such as wireless headphones, wireless speakers, etc.</p>
<p>Bluetooth Low Energy (BLE) is a low-power variant of Bluetooth Classic standard. Originally developed by Nokia under the name WIbree in 2006, this solution tried to address the scenarios that contemporary wireless technologies did not address at that time. Some targeted applications or scenarios are in the healthcare, fitness, smart home, and home entertainment industries. These applications generally have low cost and low power requirements where the devices are able to operate for months or years on a button cell.</p>
<p>As the smartphones, tablets, mobile computing, and Internet-of-Thing (IoT) grow rapidly, the BLE has also grown fast and has been widely adopted.</p>
<p>One of the strong advantage of BLE or Bluetooth is its interoperability. It means that it is easy to connect devices and smartphones from different vendors together. It is becoming so common in the Internet of Things (IoT) field to connect embedded devices, such as ESP32, with a smartphone. This opens a unique opportunity for new innovative solutions that might not possible before.</p>
<p>That is why being able to develop a BLE-powered application might be an important skill to have for (embedded) software engineers. In this article, I will cover the basic concepts and architecture of BLE.</p>
<h2 id="heading-architecture">Architecture</h2>
<p>The Bluetooth Low Energy protocol stack is divided into three layers: Application, Host, and Controller.</p>
<p><img src="https://mymakebucket1242.s3.eu-north-1.amazonaws.com/work/hashnode/1f28e113-8dc3-80b3-a31c-f234c57d4f7e/image-1f28e113-8dc3-80ed-a781-d74540bafd74.png" alt /></p>
<h3 id="heading-application-layer">Application Layer</h3>
<p>This is the highest layer where applications are built using BLE as the underlying communication protocol. It contains the logic and user interface related to the actual use-case of the application. The application layer relies on the API interfaces provided by the Host Layer.</p>
<h3 id="heading-host-layer">Host Layer</h3>
<p>The Host Layer defines the BLE protocol itself. It consists of Generic Access Profile (GAP), Generic Attribute Profile (GATT), Attribute Protocol (ATT), Security Manager (SM), and Logical Link Control and Adaptation Protocol (L2CAP). GAP and GATT are the backbone of BLE data transfer protocol and it is imperative for application developers to understand these modules to successfully build a BLE-powered application. Both of them will be covered in more detail in the next section.</p>
<h3 id="heading-host-controller-interface-hci">Host Controller Interface (HCI)</h3>
<p>HCI is a standard protocol that acts as an interface between the host and controller layer. The connection generally takes place via a UART or USB interface. If the host and controller layer are implemented on the same chip (a system-on-chip or SoC), HCI is referred to as Virtual Host Controller Interface (VHCI).</p>
<h3 id="heading-controller-layer">Controller Layer</h3>
<p>The Controller Layer consists of the Physical Layer (PHY) and Link Layer (LL). The Physical Layer interacts with the actual hardware (analog communication circuitry) for signal modulation and demodulation.</p>
<p>The Link Layer is implemented as a combination of custom hardware and software. The hardware side implements functionalities that are computationally expensive on separate hardware to avoid overloading the CPU running the software stack.</p>
<p>The software side manages the connection state between devices and device roles. A BLE device can have the following roles: Advertiser, Scanner, Master, and Slave.</p>
<p>User applications are not able to use the controller layer directly.</p>
<h2 id="heading-protocol-basics">Protocol Basics</h2>
<p>This sections describes the basics of BLE protocol within the Host Layer.</p>
<h3 id="heading-generic-access-profile-gap">Generic Access Profile (GAP)</h3>
<p>The Generic Access Profile (GAP) defines how devices interact with each other at a lower level. It defines the roles (i.e., Advertiser, Scanner, and Initiator) and the connection states (i.e., Idle, Device Discovery, and Connection). Note that a BLE device can have more than one role.</p>
<p>In Idle mode, the device is in a state without any role.</p>
<p>In device discovery mode, a device can become an advertiser indicating its presence to other devices. It can also adopt the scanner role. As a scanner, it continuously scans the environment and detects the presence of connectable advertisers. If the scanner wants to establish a connection with a advertiser, it switches its role to Initiator.</p>
<p>Once a device is connected to another device, it can adopt a peripheral role (slave) or a central role (master).</p>
<p><img src="https://mymakebucket1242.s3.eu-north-1.amazonaws.com/work/hashnode/1f28e113-8dc3-80b3-a31c-f234c57d4f7e/image-1f28e113-8dc3-80ed-a781-d74540bafd74.png" alt /></p>
<h3 id="heading-generic-attribute-profile-gatt">Generic Attribute Profile (GATT)</h3>
<p>GATT defines the hierarchical data structure and data exchange mechanism between connected devices. It is built on top of Attribute Protocol (ATT), which defines the basic data structure called <em>Attribute</em> and data access method based on a server/ client architecture.</p>
<p>An attribute data structure typically consists of:</p>
<ul>
<li>Handle. A unique 16-bit identifier for each attribute. </li>
<li>Type. An UUID.</li>
<li>Value.</li>
<li>Permissions. A metadata specifying whether the attribute is readable or writable or both.</li>
</ul>
<p>GATT extends ATT by defining hierarchical data structure which consists of three concepts:</p>
<ul>
<li>Characteristics</li>
<li>Service</li>
<li>Profile</li>
</ul>
<p><img src="https://mymakebucket1242.s3.eu-north-1.amazonaws.com/work/hashnode/1f28e113-8dc3-80b3-a31c-f234c57d4f7e/image-1f28e113-8dc3-80ed-a781-d74540bafd74.png" alt /></p>
<p>Profile sits on the highest level in the hierarchy. It consists of a predefined set of service. The service itself contains a set of characteristics. Both service and characteristic are based on attributes (i.e., handle, type, value, and permissions). A characteristics itself contains the actual user data that the client can read from and write to.</p>
<p>This marks the end of the article. GATT is a big subject and I will cover this in more detail in the next article. I hope that you can learn something new about BLE from this article.</p>
<h2 id="heading-references">References</h2>
<p>[Book] Getting Started with Bluetooth Low Energy by Kevin Townsend et. al.<br />[ESP-IDF] <a target="_blank" href="https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/ble/get-started/ble-introduction.html#nimble-gatt-server-practice">BLE-Introduction</a></p>
]]></content:encoded></item><item><title><![CDATA[AI-Powered Youtube Video Summarizer]]></title><description><![CDATA[In this article, we'll explore AI Agent, Telegram, and Discord nodes on n8n by building an AI-powered YouTube Video Summarizer automation workflow. This solution is perfect for people who enjoy learning from YouTube videos but don't have time to watc...]]></description><link>https://ariesgun.xyz/ai-powered-youtube-video-summarizer</link><guid isPermaLink="true">https://ariesgun.xyz/ai-powered-youtube-video-summarizer</guid><category><![CDATA[n8n]]></category><category><![CDATA[Gemini integration]]></category><category><![CDATA[automation]]></category><category><![CDATA[youtube]]></category><category><![CDATA[Hashnode]]></category><category><![CDATA[notion]]></category><category><![CDATA[telegram bot]]></category><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Thu, 24 Apr 2025 10:44:37 GMT</pubDate><content:encoded><![CDATA[<p>In this article, we'll explore AI Agent, Telegram, and Discord nodes on n8n by building an AI-powered YouTube Video Summarizer automation workflow. This solution is perfect for people who enjoy learning from YouTube videos but don't have time to watch full-length content. The workflow generates video summaries, helping users decide whether watching the complete video would be worthwhile or not.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p>YouTube Data API credentials</p>
</li>
<li><p>AI model API key</p>
</li>
<li><p>Notion API integration</p>
</li>
<li><p>n8n installation</p>
</li>
<li><p>Telegram API key</p>
</li>
<li><p>Heroku.</p>
</li>
</ul>
<h2 id="heading-setup">Setup</h2>
<p>Here's how the workflow functions: When a user sends a YouTube video URL to a Telegram Bot, it triggers the workflow. First, an Information extractor LLM parses the video ID from the chat message. Using this ID, we retrieve the video's transcript by sending an HTTP request to a server that is responsible for retrieving the transcript.. Then, an AI model analyzes and summarizes the transcript content. The summary is saved to a Notion database, and finally, we send the results back to the user through the Telegram API.</p>
<p>Here is the complete picture of the workflow.</p>
<p><img src="https://mymakebucket1242.s3.eu-north-1.amazonaws.com/work/hashnode/1dc8e113-8dc3-8063-ad9d-cfc291d538f3/image-1df8e113-8dc3-8080-af56-f1e2e7494712.png" alt /></p>
<h3 id="heading-setting-up-telegram-bot">Setting up Telegram Bot</h3>
<p>Setting up the telegram bot is a straightforward process though Telegram’s bot father. n8n has native support for the Telegram node, so we only need to provide the API key to start using it.</p>
<h3 id="heading-setting-up-notion">Setting up Notion</h3>
<p>n8n also natively supports the Notion node. We need to create a new Notion database to store the video summaries. Don’t forget to active the API connection to the database so that n8n can access the database through API.</p>
<h3 id="heading-setting-up-ai-prompts">Setting up AI Prompts</h3>
<p>For this setup, I am using Gemini AI model. However, it is not limited to Gemini AI. It is easy to change the model to any AI model.</p>
<p>I created two AI nodes in my workflow. The first node is a simple LLM to extract a YouTube video ID from the chat message. The second one is an AI Agent node. This node allows us to specify the model, memory, and tools. We use Gemini AI model as well to summarize the video using the transcript content passed from the previous. The node also connects to the YouTube API tool so that it is able to retrieve the video’s description and title, enhancing the result.</p>
<p>Here is the prompt I used for the first node.</p>
<pre><code class="lang-plaintext">You are an expert extraction algorithm.
Extract only the YouTube video ID from the provided text.
If you do not know the value of an attribute asked to extract, you may omit the attribute's value.
</code></pre>
<p>Here is the second prompt.</p>
<pre><code class="lang-plaintext">Role: You are an expert AI assistant specialized in analyzing video transcripts and generating concise, informative summaries.

Goal: Your primary goal is to create a clear and accurate summary of the provided video transcript, presented as a bulleted list. The summary should capture the main topics and key points of the video content, including references to important timestamps.

Context: You will receive a block of text representing the full transcript or captions of a YouTube video. This transcript may contain timestamps (e.g., [00:01:23], 0:05:10.234 --&gt; 0:08:567) and the spoken words.

Instructions:

Get information regarding the Youtube Video. Use the Youtube API tool to retrieve the video title and description. 

Identify Main Topics: Read through the entire transcript to understand the core subjects discussed.

Extract Key Points: Pinpoint the most important statements, arguments, findings, or conclusions presented in the video.

Identify Key Timestamps: Note the approximate starting timestamps associated with the key points or main topic shifts you identify. Use the timestamps provided in the transcript.

Synthesize Information: Combine the main topics and key points into a coherent summary.

Include Timestamps: When mentioning a key point or topic in the summary, include its corresponding approximate start timestamp in parentheses (e.g., (0:02:15)).

Maintain Neutrality: Summarize the content objectively, without adding personal opinions or interpretations unless explicitly asked.

Focus on Content: Ignore conversational filler words (e.g., "um," "uh," "like," "you know") and repetitive phrases where possible, unless they are crucial to the meaning or tone. Focus on the substance of what is being said.

Desired Length (Optional - Adapt): Adjust the number of bullet points based on the desired level of detail (e.g., 3-5 points for a standard summary, more for detailed).

Output Format: Starts from the video Title followed by summarized video description. Then present the summary as a bulleted list. Each bullet point should represent a key topic or finding. Ensure timestamps are included parenthetically where relevant points are mentioned. Start directly with the bulleted list.

Input Data:

The video transcript will be provided below, enclosed in triple backticks or following a specific marker (e.g., "TRANSCRIPT START").

{{ $json.transcript_data }}

(Adjust the placeholder {{ $json.transcript_data }} based on how the transcript data is passed from the previous node in your n8n workflow. It might be $json.text, $input.item.json.transcript, etc.)

Example Interaction:

Input: (A full video transcript about making pasta, with timestamps)

Output (Bulleted List Summary):

The video provides a step-by-step guide on making fresh pasta from scratch (0:00:15).

It details the necessary ingredients, primarily flour and eggs (0:01:05).

The process involves mixing, kneading (0:02:30), and resting the dough (0:05:10).

Key techniques like using the right flour and sufficient kneading are emphasized for texture (0:04:00).

Demonstrates how to roll and cut the pasta into desired shapes, such as fettuccine (0:06:45).

Concludes by showing the final cooked pasta served with sauce (0:08:20).

Now, analyze the following transcript and generate the summary based on these instructions.
</code></pre>
<h2 id="heading-retrieving-youtube-video-transcript">Retrieving YouTube Video Transcript</h2>
<p>Retrieving YouTube Video transcript is quite challenging. YouTube Data API does not allow public users to retrieve video transcript unless you are the creator of the video. I have come across some outdated solutions which do not work anymore. So, this means I need to build my own custom solution.</p>
<p>One approach is to use Gemini AI directly by giving the video URL and asking the model to summarize it. This works in chat mode but not via an API call. For some reasons, the Gemini models gives the summary of a wrong video, and I don’t quite understand why this happens. So, I have to find another solution.</p>
<p>Fortunately, there are libraries that can retrieve transcripts or captions given a video ID. There is one library written in <a target="_blank" href="https://www.npmjs.com/package/youtube-transcript">JavaScript</a> and another library in <a target="_blank" href="https://pypi.org/project/youtube-transcript-api/">Python</a> . Since these are external libraries, it is not easy to use them within the n8n environment using the custom code node. It is not possible in Python because n8n uses <a target="_blank" href="https://pyodide.org/en/stable/">Pyodide</a> to provide Python support. This limits the available Python packages that can be used. It is possible though in JavaScript but it only works on a self-hosted n8n instance. This entails updating the environment variables and a Dockerfile, which is very technical.</p>
<p>So, I’ve come up with an idea to host a server on Heroku and install the python library. This approach does not require any changes on the n8n instance and it also works on both self-hosted and cloud-hosted n8n instances.</p>
<p>Another challenge is that YouTube bans IPs if there’s too many requests from those IPs. Fortunately, we can use a Proxy server as a workaround. One Proxy server provider that is recommended by the library’s author is WebShare. So I decide to try it and it works quite well.</p>
<p>If you want to setup this workflow yourself, you should try WebShare. I appreciate if you can use my referral <a target="_blank" href="https://www.webshare.io/?referral_code=ncry8m43hhfc">link</a> .</p>
<p>If you want to know the implementation detail, you can check my repository <a target="_blank" href="https://github.com/ariesgun/heroku-youtube-transcript-downloader">here</a> .</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The AI-powered YouTube Video Summarizer workflow using n8n offers a practical solution for individuals who want to efficiently learn from YouTube videos without watching them in full. By integrating various tools such as Telegram, Notion, and AI models, this workflow automates the process of extracting, summarizing, and storing video content. This not only saves time but also can be useful for future references. The flexibility of the system allows for further customization and extension, opening up possibilities for further creative applications.</p>
<h2 id="heading-whats-next">What’s Next</h2>
<p>We now have a automation workflow that lets you get a video summary and store it on Notion’s database. There are lots of possibilities for what you can do next. You can of course consume the information yourself, or feed the information to another AI agent or n8n workflow to perform other creative tasks, like recommending next topics for your social media posts or generating new articles. The sky is the limit here.</p>
<p>Let me know if you have some other ideas in the comments section below. If you have suggestions for new automation workflows that could be useful, I’d love to hear them.</p>
]]></content:encoded></item><item><title><![CDATA[Getting Started with n8n]]></title><description><![CDATA[In today’s fast pace digital world, it is getting more and more important to deliver results fast. Yet the productivity of individuals and businesses are often bogged-downed by repetitive manual tasks. These tasks are superfluous and they drain time,...]]></description><link>https://ariesgun.xyz/getting-started-with-n8n</link><guid isPermaLink="true">https://ariesgun.xyz/getting-started-with-n8n</guid><category><![CDATA[n8n]]></category><category><![CDATA[automation]]></category><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Tue, 22 Apr 2025 08:28:38 GMT</pubDate><content:encoded><![CDATA[<p>In today’s fast pace digital world, it is getting more and more important to deliver results fast. Yet the productivity of individuals and businesses are often bogged-downed by repetitive manual tasks. These tasks are superfluous and they drain time, resources, and productivity. That’s where workflow automation software can help solve this challenge. </p>
<p>Workflow automation tools are software platforms that allow users to create automated sequences of actions across various services and applications with a set of predefined rules. By using workflow automation, the teams can focus on the most important things they need to get done, and not on repetitive administrative tasks.  With the right tool and setup, you can reduce errors, speed up processes, and increase productivity. </p>
<p>There are lots of workflow automation tools out there. In this article, I am going to describe one of the most popular and the most promising platform, <em>n8n</em> . </p>
<h2 id="heading-about-n8n">About n8n</h2>
<p>n8n is an open-source workflow automation platform that was launched in 2019 by Jan Oberhauser. Similar to other automation platforms, it offers a drag-and-drop interface, third-party applications or services (including AI) integration, and custom code extension. </p>
<p><img src="https://mymakebucket1242.s3.eu-north-1.amazonaws.com/work/hashnode/1dc8e113-8dc3-80f3-b16f-ca567689bfa1/image-1dc8e113-8dc3-80fc-80e4-d084de9050af.png" alt /></p>
<p><img src="https://mymakebucket1242.s3.eu-north-1.amazonaws.com/work/hashnode/1dc8e113-8dc3-80f3-b16f-ca567689bfa1/image-1dc8e113-8dc3-8019-93bf-d7c9e6da2ced.png" alt /></p>
<h2 id="heading-advantages-over-other-platforms">Advantages over other platforms</h2>
<p>n8n has some advantages and unique features compared to other platforms. </p>
<ul>
<li>Visual workflow builder. It offers an drag-and-drop interface to design workflows. </li>
</ul>
<ul>
<li>Self-Hosting. It is also possible to self-host n8n on your own server for free. With self-hosting, it is possible to install custom community nodes. </li>
</ul>
<ul>
<li>Cost Efficiency. Compared to other platforms, n8n cloud costs around $20 per month for 2500 executions with unlimited steps. </li>
</ul>
<ul>
<li>Extensibility. n8n supports custom code that can be written in either JavaScript or Python. It is also possible to connect to any API. Hence, n8n can support virtually unlimited integration. </li>
</ul>
<ul>
<li>AI Integration. Easy to build a AI-powered automation workflow. </li>
</ul>
<ul>
<li>Strong community support with 600+ pre-built workflow templates. </li>
</ul>
<h2 id="heading-learn-more">Learn More</h2>
<p>If you are thinking about automating your workflow, it might be worthwhile to try n8n. I myself have built a workflow using n8n to automate my blogging workflow using Hashnode and Notion. You can read more in this <a target="_blank" href="https://ariesgun.xyz/automate-blog-n8n">article</a> . If cost is your main concern, don’t worry because it is possible to self-host n8n. If you want to a quick start, you can try n8n using n8n directly and you will get a 14-day trial period. </p>
<h2 id="heading-summary">Summary</h2>
<p>Modern workflow automation tools have evolved from simple script-based automation to sophisticated platforms with visual interfaces and advanced features. Whether you're a small business owner, developer, or enterprise solution architect, workflow automation tools can significantly improve your operational efficiency and help you focus on more strategic tasks rather than routine operations. </p>
<p>These tools now incorporate artificial intelligence and machine learning capabilities, making them more intelligent and adaptive to business needs. </p>
]]></content:encoded></item><item><title><![CDATA[Automate Blogging Workflow using Notion + Hashnode + n8n]]></title><description><![CDATA[In the previous article , I have described how to build an automated blogging workflow using Hashnode and Notion on Make platform. It has been tremendously helpful for bloggers in managing their blog posts, allowing them to focus on content creation ...]]></description><link>https://ariesgun.xyz/automate-blog-n8n</link><guid isPermaLink="true">https://ariesgun.xyz/automate-blog-n8n</guid><category><![CDATA[n8n]]></category><category><![CDATA[automation]]></category><category><![CDATA[Hashnode]]></category><category><![CDATA[notion]]></category><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Sun, 20 Apr 2025 15:37:11 GMT</pubDate><content:encoded><![CDATA[<p>In the previous <a target="_blank" href="https://ariesgun.xyz/make-notion-hashnode">article</a> , I have described how to build an automated blogging workflow using Hashnode and Notion on Make platform. It has been tremendously helpful for bloggers in managing their blog posts, allowing them to focus on content creation rather than boring administrative tasks. From the functional perspective, it has been doing fine.</p>
<p>However, there’s lots of room for improvement. If we look at the structure of the workflow that has been built using Make, it looks quite complex and not so intuitive. That makes things if there is anything I can do to improve it. When I discovered n8n, I immediately could see that I can build a better automated blogging workflow using this platform. And I will describe the setup in this article.</p>
<h2 id="heading-automate-blogging-workflow">Automate Blogging Workflow</h2>
<p>Imagine managing blogs with numerous posts. As a content creator, you want to focus on creating posts for your blogs. The process of writing and updating previously uploaded blog posts involves tedious navigation through the blogging platform, like Hashnode. Wouldn't it be more efficient to manage all your posts within Notion and having them automatically uploaded to the blogging platform? This automation workflow aims to boost your productivity by streamlining the process, allowing you to focus more on content creation and less on the administrative tasks of blogging.</p>
<h2 id="heading-about-n8n">About n8n</h2>
<p>n8n is an open-source workflow automation platform that allows users to connect various applications and automate tasks with a drag-and-drop interface. It offers an intuitive interface to easily build a workflow automation with the possibility to insert custom code. This gives the flexibility of code with the speed of no-code.</p>
<p>Compared to Zapier, IFTTT, and Make, n8n offer several unique advantages:</p>
<ul>
<li><p>Intuitive interface. It is very easy to specify trigger options and to connect different applications and tools using a drag-and-drop interface.</p>
</li>
<li><p>Custom-code. It supports custom codes written in JavaScript and Python. This enables technical users to build powerful automatic workflows, giving more flexibility.</p>
</li>
<li><p>Self-hosted option. Hence, it is possible to run n8n totally free.</p>
</li>
</ul>
<h2 id="heading-building-the-workflow">Building the Workflow</h2>
<p>Building a workflow on n8n is much easier. It is pretty intuitive to connect the apps, tools, and supporting logic nodes (such as If-else, filter, merge, and so on). It is similar to a flow-chart.</p>
<p>The components needed for this workflow are as follows.</p>
<ul>
<li><p>Notion, where the contents of the blog post are stored.</p>
</li>
<li><p>Hashnode.</p>
</li>
<li><p>AWS S3, to store images.</p>
</li>
<li><p>Code node for custom codes.</p>
</li>
</ul>
<p>Here is the complete setup of the workflow. As you can see, the workflow looks simpler compared to the one I built on Make. It is also easier to understand, in my opinion. There is no weird construct in the workflow.</p>
<p><img src="https://mymakebucket1242.s3.eu-north-1.amazonaws.com/work/hashnode/1db8e113-8dc3-8041-a4aa-ef3a04d30867/image-1db8e113-8dc3-804e-9ffc-e446584d55ca.png" alt /></p>
<p>The workflow consists of two parts. The first part is the trigger. The workflow is triggered when there’s some updates (i.e., a new page has been created or updated) in the table.</p>
<p>The second one is the main part for the workflow. It retrieves Notion pages and for each page, it will parse the full contents, upload images to AWS S3, convert the contents to Markdown format, and publish the contents to Hashnode using GraphQL.</p>
<p>The conversion from Notion blocks to Markdown has to be manually implemented in a code node. This is how it looks like.</p>
<pre><code class="lang-plaintext"># Loop over input items and add a new field called 'myNewField' to the JSON of each one
output = {}
output["markdown"] = ""
output["items"] = []

def check_annotate(item, type):
  parsed_text = ""
  rich_texts = item.json[type]["text"]
  for rich_text in rich_texts:
    text = rich_text["plain_text"].strip().rstrip()
    if rich_text["annotations"]["bold"]:
      text = "*" + text + "*"
    if rich_text["annotations"]["italic"]:
      text = "**" + text + "**"
    if rich_text["href"]:
      text = "[" + text + "](" + rich_text["href"] + ")"

    parsed_text += text + " "

  parsed_text = parsed_text.replace('    ', '\t').replace('"', '\"').replace('
', '\n')
  parsed_text += "\n\n"
  return parsed_text

for item in _input.all():
  parsed_text = ""
  if (item.json["type"] == "paragraph"):
    parsed_text = check_annotate(item, item.json["type"])
    output["items"].append(parsed_text)
  elif (item.json["type"] == "image"):
    parsed_text = "![](" + item.json["image"]["file"]["url"] + ")\n\n"
    output["items"].append(parsed_text)
  elif (item.json["type"] == "divider"):
    output["items"].append("---

")
  elif ("heading_" in item.json["type"]):
    parsed_text = check_annotate(item, item.json["type"])
    if item.json["type"] == "heading_1":
      parsed_text = "# " + parsed_text
    elif item.json["type"] == "heading_2":
      parsed_text = "## " + parsed_text
    elif item.json["type"] == "heading_3":
      parsed_text = "### " + parsed_text
    output["items"].append(parsed_text)
  elif (item.json["type"] == "bulleted_list_item"):
    parsed_text = check_annotate(item, item.json["type"])
    parsed_text = "- " + parsed_text + "\n"
    output["items"].append(parsed_text)
  elif (item.json["type"] == "numbered_list_item"):
    parsed_text = "1"
    output["items"].append(parsed_text)
  elif (item.json["type"] == "code"):
    parsed_text = check_annotate(item, item.json["type"])
    parsed_text = "```\n" + parsed_text + "```\n\n"
    output["items"].append(parsed_text)

  output["markdown"] += parsed_text
return output
</code></pre>
<h3 id="heading-pricing-models">Pricing Models</h3>
<p>Compared to Make’s pricing model which is based on the number of operations performed, n8n uses a different pricing model. The price depends solely on your hosting solution and not the number of operations. If you self-host n8n, you can run it for free. n8n also offers a n8n cloud-host option, it will cost you around $20 per month. Hence, it is much cheaper than Make.</p>
<h1 id="heading-summary">Summary</h1>
<p>n8n offers a very intuitive, easy-to-use interface to build any automation workflow. Compared to Make, it also offers a better pricing model. By using Hashnode, Notion, and n8n, we are able to build a automatic blogging workflow that can significantly enhance your productivity and streamline your blog management process. Having this automation allows content creators to focus on content creation rather than mundane administrative tasks. I hope this article can be helpful!. Happy automating.</p>
]]></content:encoded></item><item><title><![CDATA[Automate Blogging Workflow using Notion + Hashnode + Make]]></title><description><![CDATA[If you're a fan of Notion and have been thinking about using it to manage your blog posts while automatically publishing them to Hashnode whenever they're ready, this article is for you.
Before we dive into the specifics, let's take a moment to under...]]></description><link>https://ariesgun.xyz/make-notion-hashnode</link><guid isPermaLink="true">https://ariesgun.xyz/make-notion-hashnode</guid><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Sat, 13 Jul 2024 22:00:00 GMT</pubDate><content:encoded><![CDATA[<p>If you're a fan of Notion and have been thinking about using it to manage your blog posts while automatically publishing them to Hashnode whenever they're ready, this article is for you.</p>
<p>Before we dive into the specifics, let's take a moment to understand what Make is and how it compares to other popular automation tools like Zapier and IFTTT.</p>
<p>Make, formerly known as Integromat, is a versatile automation platform that allows users to design, build, and automate workflows called "scenarios." These scenarios can connect various apps and services, enabling seamless data transfer and task execution. Make stands out for its visual approach to automation, representing workflows as a series of interconnected modules.</p>
<p>Compared to Zapier and IFTTT, Make offer several unique advantages:</p>
<ul>
<li><p>Visual Interface. Make provides a flowchart-like UI interface to easily visualize and create a complex workflow at glance.</p>
</li>
<li><p>Flexibility. It allows for branching paths, loops, and conditional logic, giving users more control over their automations.</p>
</li>
<li><p>Better pricing model. Make’s pricing model is based on the number of operations performed. This is more cost-effective for users with complex but infrequently run automations.</p>
</li>
</ul>
<p>In this article, we'll harness the power of Make to create an automated workflow that retrieves your blog posts from Notion and publishes them directly to Hashnode. This integration will save you valuable time and ensure a smooth, consistent publishing process.</p>
<h1 id="heading-building-hashnode-custom-app">Building Hashnode Custom App</h1>
<p>The official Hashnode module is unfortunately not available on Make yet. The good news is that it is possible to create our own custom app on Make. This custom app relies on Hashnode GraphQL API to perform query or mutation actions.</p>
<p>For this use case, I have built the custom Hashnode app which supports modules to create a new post and update an existing post. In the future, I might add other modules as I see fit for my use cases.</p>
<p>If you want to try it, here is the link to the custom app →  <a target="_blank" href="https://www.make.com/en/hq/app-invitation/79cf079ec259c08600332f402d8c1a73">Hashnode Make Module</a> .</p>
<h1 id="heading-building-the-scenario">Building the Scenario</h1>
<p>Now it is time to create the scenario. Make makes it possible to easily create a scenario with a very easy-to-use UI. It might be awkward to use at first, but I think the learning curve is not steep.</p>
<p>Integrating Notion with Hashnode is not as easy as I thought. Hashnode accepts content in Markdown format, while Notion uses a different kind of data format. It uses the concept of blocks, and each block represents a different content type. For example, a block can be a paragraph, an image, a list, or a heading. Hence, before we are able to publish the content from Notion to Hashnode, we have to convert Notion’s blocks to Markdown. That’s where the complexity of the scenario comes from.</p>
<p>Here is the complete setup of my scenario. Currently, it only supports blocks with heading, paragraph, image, list items, and link types. Of course, I will update the scenario to support different types of blocks.</p>
<p><img src="https://mymakebucket1242.s3.eu-north-1.amazonaws.com/work/hashnode/4274fa91-bb70-4b99-8284-6cafcf0186c6/image1.png" alt /></p>
<h1 id="heading-notes">Notes</h1>
<p>Make’s pricing model is based on the number of operations performed. Right now, the scenario is not very efficient in terms of the number of operations. Each block of Notion is going to consume around 8 operations (and can be more if it is a paragraph that has lots of annotations) in this scenario. If you have a blog post with many blocks, the number of operations consumed can be very high. That’s why it is important to combine several paragraphs or blocks into one block on Notion to reduce the number of operations. (Always  <strong>use SHIFT+ENTER when writing a new paragraph</strong>  on Notion).</p>
<h1 id="heading-how-to-use">How to Use</h1>
<p>Simply create a Database and connect it to Make. Here is the template of the database →  <a target="_blank" href="/908c0dbba55f43a09f55229e01c3524d?v=1012f8922136443eba4b4b627b639711">Link</a> .</p>
<h1 id="heading-whats-next">What’s Next</h1>
<p>I will keep updating the scenario to support different types of blocks while trying to optimize the number of operations. I also plan to extend the scenario to support other tasks such as automatically sharing new posts on social media.</p>
<hr />
<p>Thanks to Make, automating my blogging workflow has never been easier. Using a low-code approach and drag-and-drop operations, I was able to streamline my blogging workflow by automatically publishing my blog posts on Hashnode from Notion. Having this robust automation allows you to focus on what matters most - creating great content - while the technical aspects of publishing are handled seamlessly in the background. I hope that this article can be helpful!</p>
]]></content:encoded></item><item><title><![CDATA[Command]]></title><description><![CDATA[A pattern that encapsulate a request as a stand-alone object. This object contains all information about request. As a result, it is possible to pass the requests as a method argument,  delay  or  queue  or  group  (think of bank execution) a request...]]></description><link>https://ariesgun.xyz/command-design-pattern</link><guid isPermaLink="true">https://ariesgun.xyz/command-design-pattern</guid><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Thu, 11 Jul 2024 22:00:00 GMT</pubDate><content:encoded><![CDATA[<p>A pattern that encapsulate a request as a stand-alone object. This object contains all information about request. As a result, it is possible to pass the requests as a method argument,  <strong>delay</strong>  or  <strong>queue </strong> or  <strong>group</strong>  (think of bank execution) a request’s execution, and support undoable operations. </p>
<p>With this design pattern, we can achieve:</p>
<ol>
<li>Separation of concerns, e.g. separate UI from the business logic. A button UI can be reused for other use cases.</li>
<li>Decoupled code.</li>
<li>Highly extensible, e.g. add new commands.</li>
<li>Testable and maintainable code.<h2 id="heading-structure">Structure</h2>
</li>
</ol>
<p>It consists of 5 components:</p>
<ul>
<li><p>Command . An interface for executing an operation</p>
</li>
<li><p>ConcreteCommand . Connects receiver with an action and implements Execute</p>
</li>
<li><p>Client . Creates and schedules  ConcreteCommands .</p>
</li>
<li><p>Invoker.  Runs the commands, typically using callback. For example when a button is pressed.</p>
</li>
<li><p>Receiver . Performs the operation requested by the command.</p>
</li>
</ul>
<p><img src="https://mymakebucket1242.s3.eu-north-1.amazonaws.com/work/hashnode/e19c170c-e309-442c-9299-6a9078c06ebb/image5.png" alt /></p>
<h2 id="heading-implementation">Implementation</h2>
<h3 id="heading-classic-example">Classic Example</h3>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Command</span> </span>{
<span class="hljs-attr">public</span>:
    virtual <span class="hljs-keyword">void</span> Execute() <span class="hljs-keyword">const</span> = <span class="hljs-number">0</span>;
    virtual ~Command() = <span class="hljs-keyword">default</span>;
};

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BookVenueCommand</span>: <span class="hljs-title">public</span> <span class="hljs-title">Command</span> </span>{
    Venue* _venue;
    int _remainingSeats, _numberOfSeatstToBook;
    ...
    public:
        BookVenueCommand(Venue* venue) : _venue(venue) {}

        virtual <span class="hljs-keyword">void</span> Execute() override {
                _remainingSeats = _venue-&gt;BookSeats(_numberOfSeatsToBook);
        }

        BookVenueCommand(Venue* venue, int numberSeatsToBook, TicketType ticketType) {
            ...
        }

        int GetNumberOfRemainingSeats() { <span class="hljs-keyword">return</span> _remainingSeats; }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ReverseTicketCommand</span>: <span class="hljs-title">publicCommand</span> </span>{
    PriceHandler* _priceHandler;
    VneueType _venueType;
    ...
    public:
        virtual <span class="hljs-keyword">void</span> Execute() override {
            ...
            double ticketPrice = _priceHandler-&gt;handlePrice(ticket);
            _tickets-&gt;push_back(ticket);

            std::cout &lt;&lt; fmt::format(<span class="hljs-string">"You reserved a ticket for {0} ..."</span>, ticket.getNumberOfSeats();
        }
}

int main() {
    BookVenueCommand* bookHugeTheather = <span class="hljs-keyword">new</span> BookVenueCommand(_hugeTheatre, <span class="hljs-number">10</span>, ticketType);
    bookHugeTheather-&gt;Execute();

}
</code></pre><h3 id="heading-macro-command">Macro Command</h3>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MacroCommand</span> : <span class="hljs-title">public</span> <span class="hljs-title">Command</span> </span>{

    <span class="hljs-attr">std</span>::list&lt;Command*&gt;* _commands;

    public:
        MacroCommand() { _commands = <span class="hljs-keyword">new</span> std::list&lt;Command*&gt;(); }

        virtual <span class="hljs-keyword">void</span> Add(Command* command) {
            _commands-&gt;push_back(command);
        }

        virtual <span class="hljs-keyword">void</span> Remove(Command* command) {
            _commands-&gt;remove(command);
        }

        virtual <span class="hljs-keyword">void</span> Execute() {
            <span class="hljs-attr">std</span>::list&lt;Command&amp;&gt;::iterator iterator;

            <span class="hljs-keyword">for</span>(iterator=_commands-&gt;begin(); iterator != _commands-&gt;end(); ++iterator)
            {
                (*iterator)-&gt;Execute();
            }
        }    
}

int main()
{
    MacroCommand* bookingMacroCommand = <span class="hljs-keyword">new</span> MacroCommand;

    BookVenueCommand* command1 = <span class="hljs-keyword">new</span> BookVenueCommand(...);
    bookingMacroCommand-&gt;Add(command1);
    BookVenueCommand* command2 = <span class="hljs-keyword">new</span> BookVenueCommand(...);
    bookingMacroCommand-&gt;Add(command2);

    bookingMacroCommand-&gt;Execute();

}
</code></pre><h3 id="heading-undo-command">Undo Command</h3>
<pre><code><span class="hljs-comment">// Simply adding a new method in the command class</span>
virtual <span class="hljs-keyword">void</span> Undo() = <span class="hljs-number">0</span>;

<span class="hljs-comment">// Save the commands into history.</span>

<span class="hljs-comment">// Can be combined with Memento design pattern and/or Prototype to handle history</span>
</code></pre><h2 id="heading-notes">Notes</h2>
<p>Chain Responsibility passes a request sequentially along a dynamic chain of potential receivers until one of them handles it. </p>
<p>Handlers in Chain Responsibility can be implemented as  <strong><em>Commands. </em></strong> Execute different operations over the same context object/ request.</p>
<p>Requests in Chain Responsibility can be implemented as  <strong><em>Commands. </em></strong> Execute the same operation in a series of different contexts linked into a chain.</p>
<p>Command establishes unidirectional connection between senders and receivers.</p>
<p>Mediator eliminates direct connections between senders and receivers</p>
<p>Observers lets receivers to dynamically subscribe to and unsubscribe from receiving requests.</p>
<h2 id="heading-credits">Credits</h2>
<p><a target="_blank" href="https://refactoring.guru/design-patterns/command">Command in C++ / Design Patterns (refactoring.guru)</a></p>
]]></content:encoded></item><item><title><![CDATA[Strategy]]></title><description><![CDATA[With strategy design pattern, it is possible do define a family of algorithms and choose the appropriate one to use at runtime. Since the algorithm is implemented in a separate class, it makes them interchangeable. 
Strategy design pattern uses compo...]]></description><link>https://ariesgun.xyz/strategy-design-pattern</link><guid isPermaLink="true">https://ariesgun.xyz/strategy-design-pattern</guid><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Thu, 11 Jul 2024 22:00:00 GMT</pubDate><content:encoded><![CDATA[<p>With strategy design pattern, it is possible do define a family of algorithms and choose the appropriate one to use at runtime. Since the algorithm is implemented in a separate class, it makes them interchangeable. </p>
<p>Strategy design pattern uses composition approach. </p>
<p>It separates the business logic of a class from the implementation details of algorithms (SRP). </p>
<p>Adding a new algorithm won’t affect the client code (Open/Close Principle). </p>
<h2 id="heading-structure">Structure</h2>
<p><img src="https://mymakebucket1242.s3.eu-north-1.amazonaws.com/work/hashnode/7acc632a-85b3-4103-a6c7-c3b981d5876b/image-f0b9d713-6672-4178-8eca-340811cdc36f.png" alt /></p>
<h2 id="heading-implementation">Implementation</h2>
<h3 id="heading-classic-example-dynamic">Classic Example (Dynamic)</h3>
<pre><code><span class="hljs-comment">// Define the interface common to all supported versions of some algorithms</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Strategy</span> </span>{
<span class="hljs-attr">public</span>:
    virtual ~Strategy() = <span class="hljs-keyword">default</span>;
    virtual std::string doAlgorithm(std::string_view data) <span class="hljs-keyword">const</span> = <span class="hljs-number">0</span>;
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ConcreteStrategyA</span> : <span class="hljs-title">public</span> <span class="hljs-title">Strategy</span> </span>{
<span class="hljs-attr">public</span>:
    std::string doAlgorithm(std::string_view data) <span class="hljs-keyword">const</span> override
    {
        <span class="hljs-attr">std</span>::string result(data);
        std::sort(std::begin(result), <span class="hljs-attr">std</span>::end(result));
        <span class="hljs-keyword">return</span> result;
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ConcreteStrategyB</span> : <span class="hljs-title">public</span> <span class="hljs-title">Strategy</span> </span>{
<span class="hljs-attr">public</span>:
    std::string doAlgorithm(std::string_view data) <span class="hljs-keyword">const</span> override
    {
        <span class="hljs-attr">std</span>::string result(data);
        std::sort(std::begin(result), <span class="hljs-attr">std</span>::end(result), <span class="hljs-attr">std</span>::greater&lt;&gt;());
        <span class="hljs-keyword">return</span> result;
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Context</span>
</span>{
<span class="hljs-attr">private</span>:
    std::unique_ptr&lt;Strategy&gt; m_strategy;

public:
    explicit Context(std::unique_ptr&lt;Strategy&gt;&amp;&amp; strategy = {}): strategy_(std::move(strategy)) {}

    <span class="hljs-comment">// ALternatively use factory pattern</span>
    <span class="hljs-keyword">void</span> set_strategy(std::unique_ptr&lt;Strategy&gt;&amp;&amp; strategy) {
        m_strategy = std::move(strategy);
    }

    <span class="hljs-keyword">void</span> doSomething() <span class="hljs-keyword">const</span> {
        <span class="hljs-keyword">if</span> (m_strategy) {
            <span class="hljs-attr">std</span>::string result = m_strategy-&gt;doAlgorithm(<span class="hljs-string">"abcde"</span>);
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-attr">std</span>::cout &lt;&lt; <span class="hljs-string">"Context Strategy is not set
"</span>;
        }
    }
}

int main() {
    Context context(std::make_unique&lt;ConcreteStrategyA&gt;());
    context.doSomething();
}
</code></pre><h3 id="heading-static-strategy-template">Static Strategy (Template)</h3>
<p>If you want to avoid using vtable. </p>
<pre><code>template&lt;typename LS&gt;
struct TextProcessor {
    <span class="hljs-keyword">void</span> append_list(<span class="hljs-keyword">const</span> vector&lt;string&gt; &amp;items) {
        m_list_strategy.start(m_oss);
        <span class="hljs-keyword">for</span> (auto &amp; item: items)
            m_list_strategy.add_list_item(m_oss, item);
        m_list_strategy.end(m_oss);
    }

    string str() <span class="hljs-keyword">const</span> { <span class="hljs-keyword">return</span> m_oss.str(); }
<span class="hljs-attr">private</span>:
    ostringstream       m_oss;
    LS                  m_list_strategy;
};

int main() {
    <span class="hljs-comment">// markdown</span>
    TextProcessor&lt;MarkdownListStrategy&gt; tp1;
    tp1.append_list({ <span class="hljs-string">"foo"</span>, <span class="hljs-string">"bar"</span>, <span class="hljs-string">"baz"</span> });
    cout &lt;&lt; tp1.str() &lt;&lt; endl;

    <span class="hljs-comment">// html</span>
    TextProcessor&lt;HtmlListStrategy&gt; tp2;
    tp2.append_list({ <span class="hljs-string">"foo"</span>, <span class="hljs-string">"bar"</span>, <span class="hljs-string">"baz"</span> });
    cout &lt;&lt; tp2.str() &lt;&lt; endl;

    <span class="hljs-keyword">return</span> EXIT_SUCCESS;
}
</code></pre><h3 id="heading-functional-approach-using-lambda">Functional approach using Lambda</h3>
<p>The strategy can be implemented as an anonymous function instead. With this approach, we can avoid bloating the code with extra classes and interfaces </p>
<h2 id="heading-credits">Credits</h2>
<p><a target="_blank" href="https://refactoring.guru/design-patterns/strategy">https://refactoring.guru/design-patterns/strategy</a> </p>
<p><a target="_blank" href="https://vishalchovatiya.com/posts//strategy-design-pattern-in-modern-cpp/">https://vishalchovatiya.com/posts//strategy-design-pattern-in-modern-cpp/</a> </p>
]]></content:encoded></item><item><title><![CDATA[Chain of Responsibilty]]></title><description><![CDATA[A behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides whether to process the request or to pass it to the next handler in the chain. 
It achieves loose coupling between the s...]]></description><link>https://ariesgun.xyz/chain-of-responsibility-design-pattern</link><guid isPermaLink="true">https://ariesgun.xyz/chain-of-responsibility-design-pattern</guid><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Tue, 09 Jul 2024 22:00:00 GMT</pubDate><content:encoded><![CDATA[<p>A behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides whether to process the request or to pass it to the next handler in the chain. </p>
<p>It achieves loose coupling between the sender of a request and its receiver. </p>
<p>It enforces separation of concerns between the sender/ client and the handlers.</p>
<h2 id="heading-use-cases">Use Cases</h2>
<p>This pattern is suitable when there is a need to process different kinds of requests in a various way.</p>
<p>You can implement this design patterns in the following use-cases.</p>
<ul>
<li>Filtering requests as in an authentication process.</li>
</ul>
<p><img src="https://mymakebucket1242.s3.eu-north-1.amazonaws.com/work/hashnode/9ae78b41-9e83-46d2-859c-294582e02922/image2.png" alt /></p>
<p>In this case, the request will be passed to the receiver if the validation process in each handler passes.</p>
<ul>
<li>Finding a suitable handler as used in the customer service workflow.</li>
</ul>
<p><img src="https://mymakebucket1242.s3.eu-north-1.amazonaws.com/work/hashnode/9ae78b41-9e83-46d2-859c-294582e02922/image3.png" alt /></p>
<p>In this case, the correct handler will pick up, process, and drop the request.</p>
<h2 id="heading-structure">Structure</h2>
<p><img src="https://mymakebucket1242.s3.eu-north-1.amazonaws.com/work/hashnode/9ae78b41-9e83-46d2-859c-294582e02922/image4.png" alt /></p>
<h2 id="heading-implementation">Implementation</h2>
<h3 id="heading-classic-example">Classic Example</h3>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Handler</span>
</span>{
    Handler* _nextHandler;
public:
    virtual Handler* SetNext(Handler *handler) = <span class="hljs-number">0</span>;
    virtual std::string Handle(std::string request) = <span class="hljs-number">0</span>;
};

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DefaultAbstractHandler</span> : <span class="hljs-title">public</span> <span class="hljs-title">Handler</span> </span>{
    <span class="hljs-attr">std</span>::string Handle(std::string request) override {
        <span class="hljs-keyword">if</span> (_nextHandler != nullptr) {
                <span class="hljs-keyword">return</span> _nextHandler-&gt;Handle(request);
        }

        <span class="hljs-keyword">return</span> {};
    }

    Handler* SetNext(Handler* handler) override {
        _nextHandler = handler;
        <span class="hljs-keyword">return</span> handler; <span class="hljs-comment">// so you can link the next handler in a convenient way.</span>
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SmallHandler</span>: <span class="hljs-title">public</span> <span class="hljs-title">DefaultAbstractHandler</span> </span>{
    <span class="hljs-attr">std</span>::string Handle(std::string request) override {
        <span class="hljs-keyword">if</span> (request == <span class="hljs-string">"small"</span>) {
            <span class="hljs-keyword">return</span> <span class="hljs-string">"Small handler\n"</span>;
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">return</span> DefaultAbstractHandler::Handle(request);
        }    
    }
}

...

int main() {
    SmallHandler* smallHandler = <span class="hljs-keyword">new</span> SmallHandler;
    MediumHandler* mediumHandler = <span class="hljs-keyword">new</span> mediumHandler;
    smallHandler-&gt;SetNext(mediumHandler);

    std::vector&lt;std::string&gt; requests {<span class="hljs-string">"small"</span>, <span class="hljs-string">"medium"</span>, <span class="hljs-string">"large"</span>};
    <span class="hljs-keyword">for</span> (auto&amp; inp: requests) {
        smallHandler-&gt;Handle(inp);
    }
}
</code></pre><h3 id="heading-improving-classic-example-using-stdvector">Improving Classic Example using  std::vector</h3>
<p>Improve further decoupling → Handlers are not aware of each other anymore</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PriceHandler</span> </span>{
    <span class="hljs-attr">std</span>::vector&lt;PriceReceiver*&gt; _receivers;

public:
    virtual PriceHandler* SetNext(PriceReceiver* handler) {
        _receivers.push_back(handler);
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>;
    }

    virtual double HandlePrice(Ticket ticket) {
        <span class="hljs-keyword">for</span> (auto receiver: _receivers) {
            double res = ...;

            <span class="hljs-keyword">if</span> (res) {
                <span class="hljs-keyword">return</span> res;
            }
        }

        <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
    }

    virtual double HandleTotalPrice(std::vector&lt;Ticket&gt; tickets) {
        <span class="hljs-keyword">for</span> (Ticket ticket: tickets) {
            totalPrice += HandlePrice(ticket);
        }

        <span class="hljs-keyword">return</span> totalPrice;
    }
}
</code></pre><h3 id="heading-using-boost-event">Using Boost Event</h3>
<p>Mentioned  <a target="_blank" href="https://vishalchovatiya.com/posts/chain-of-responsibility-design-pattern-in-modern-cpp/">here</a> .</p>
<p>This implementation, however, does not allow you to drop a request if it has been handled.</p>
<h2 id="heading-image-credits">Image Credits</h2>
<p><a target="_blank" href="https://refactoring.guru/design-patterns/chain-of-responsibility/cpp/example">Chain of Responsibility in C++ / Design Patterns (refactoring.guru)</a></p>
]]></content:encoded></item><item><title><![CDATA[Design Patterns Revisited]]></title><description><![CDATA[Design Patterns are standard solutions to common design problems in object-oriented applications. They act as guidelines that can be used to solve recurring design problems in your code. If you have been in the software industry for a while, it is po...]]></description><link>https://ariesgun.xyz/design-patterns-revisited</link><guid isPermaLink="true">https://ariesgun.xyz/design-patterns-revisited</guid><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Tue, 09 Jul 2024 22:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Design Patterns are standard solutions to common design problems in object-oriented applications. They act as guidelines that can be used to solve recurring design problems in your code. If you have been in the software industry for a while, it is possible that you have already implemented some of these  patterns without realizing it.</p>
<p>Note that design patterns are not ready-to-use code solutions. They describe or suggest how the solution should look like.</p>
<h2 id="heading-benefits-of-design-patterns">Benefits of Design Patterns</h2>
<ul>
<li><p>Teach how to solve all sorts of problems using principles of Object-Oriented Design</p>
</li>
<li><p>Adhere to SOLID principles</p>
</li>
<li><p>Achieve highly cohesive modules with minimal coupling (Extensibility and reusability).</p>
</li>
<li><p>Communication tools between designers and developers</p>
</li>
</ul>
<h2 id="heading-types-of-design-patterns">Types of Design Patterns</h2>
<h3 id="heading-creational-design-patterns">Creational Design Patterns</h3>
<ul>
<li><p>Factory/ Abstract Factory</p>
</li>
<li><p>Builder</p>
</li>
<li><p>Prototype</p>
</li>
<li><p>Singleton</p>
</li>
</ul>
<h3 id="heading-structural-design-patterns">Structural Design Patterns</h3>
<ul>
<li><p>Adapter</p>
</li>
<li><p>Bridge</p>
</li>
<li><p>Composite</p>
</li>
<li><p>Decorator</p>
</li>
<li><p>Facade</p>
</li>
<li><p>Flyweight</p>
</li>
<li><p>Proxy</p>
</li>
</ul>
<h3 id="heading-behavioral-design-patterns">Behavioral Design Patterns</h3>
<ul>
<li><p><a target="_blank" href="https://ariesgun.xyz/chain-of-responsibility-design-pattern">Chain of Responsibility</a></p>
</li>
<li><p><a target="_blank" href="https://ariesgun.xyz/command-design-pattern">Command</a></p>
</li>
<li><p>Interpreter</p>
</li>
<li><p>Iterator</p>
</li>
<li><p>Mediator</p>
</li>
<li><p>Memento</p>
</li>
<li><p>Observer</p>
</li>
<li><p>State</p>
</li>
<li><p><a target="_blank" href="https://ariesgun.xyz/strategy-design-pattern">Strategy</a></p>
</li>
<li><p>Template Method</p>
</li>
<li><p>Visitor</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Airdrop April 2024]]></title><description><![CDATA[Kuartal pertama tahun 2024 sudah lewat dan banyak sekali proyek-proyek kripto yang melakukan token airdrop. Beberapa proyek yang memberikan token airdrop secara royal adalah StarkNet, Wormhole, dan lain sebagainya. Jangan merasa ketinggalan karena ma...]]></description><link>https://ariesgun.xyz/airdrop-april-2024</link><guid isPermaLink="true">https://ariesgun.xyz/airdrop-april-2024</guid><category><![CDATA[zerolend]]></category><category><![CDATA[kamino]]></category><category><![CDATA[joltify]]></category><category><![CDATA[elys]]></category><category><![CDATA[koii]]></category><category><![CDATA[airdrop]]></category><category><![CDATA[crypto]]></category><category><![CDATA[linea]]></category><category><![CDATA[scroll]]></category><category><![CDATA[berachain]]></category><category><![CDATA[Taiko]]></category><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Sun, 14 Apr 2024 16:22:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/IQVFVH0ajag/upload/31578f47c0835b55f00c71543bbc2376.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Kuartal pertama tahun 2024 sudah lewat dan banyak sekali proyek-proyek kripto yang melakukan token airdrop. Beberapa proyek yang memberikan token airdrop secara royal adalah StarkNet, Wormhole, dan lain sebagainya. Jangan merasa ketinggalan karena masih banyak proyek yang bisa digarap dan berpotensi melakukan airdrop.</p>
<p>Berikut daftar proyek yang sedang saya garap per April 2024.</p>
<h2 id="heading-linea">Linea</h2>
<p><img src="https://images.mirror-media.xyz/publication-images/wsvi0NgXtKEGB7YteJac9.png?height=960&amp;width=1920" alt /></p>
<p>Proyek L2 layer yang masih <em>tokenless</em> yang dibuat oleh ConsenSys, perusahaan yang membuat Metamask. Program <em>airdrop</em>-nya sudah berjalan cukup lama sehingga kalau kamu belum menggarap sama sekali sepertinya sudah ketinggalan.</p>
<p>Program Linea Surge baru akan mulai dan membutuhkan likuiditas minimal 0.1 ETH untuk mendapatkan L-XP multipier.</p>
<h2 id="heading-scroll">Scroll</h2>
<p><img src="https://pbs.twimg.com/profile_banners/1361909631187001350/1692891403/1500x500" alt="Image" /></p>
<p>Proyek L2 layer <em>tokenless</em> yang masih <em>underfarmed</em> sehingga potensi untuk menggarapnya masih besar.</p>
<p>Untuk panduan lengkapnya dapat dilihat di <a target="_blank" href="https://ariesgun.xyz/panduan-airdrop-scroll">https://ariesgun.xyz/panduan-airdrop-scroll</a>.</p>
<h2 id="heading-zerolend">ZeroLend</h2>
<p><img src="https://pbs.twimg.com/media/GK0DLQsbwAEqOH3?format=jpg&amp;name=large" alt="Image" /></p>
<p>Program Dapps <em>Lending</em> yang juga sedang menjalankan program <em>airdrop</em>. Untuk berpartisipasi dalam program ini, tentunya membutuhkan likuiditas yang cukup banyak. Untuk setiap meminjamkan (<em>lend)</em> token akan mendapatkan 1 poin per 1 $USD, dan 4 poin per 1 $USD untuk meminjam (<em>borrow)</em> token.</p>
<p>ZeroLend berjalan di beberapa <em>blockchain</em> seperti ZkSync Era, Linea, Blast, dan Manta. Bagi kamu yang juga sedang menggarap proyek-proyek <em>tokenless</em> tersebut, kamu bisa "sekali mendayung, dua tiga pulau terlampaui".</p>
<p>Bagi kamu yang tertarik, bisa langsung ke TKP <a target="_blank" href="https://airdrop.zerolend.xyz/#/?invite=pnaNZ1HizZIu">https://airdrop.zerolend.xyz/</a>.</p>
<h2 id="heading-kamino">Kamino</h2>
<p><img src="https://miro.medium.com/v2/resize:fit:700/0*CobpcDNKQq4QFbX1.png" alt class="image--center mx-auto" /></p>
<p>Proyek di ekosistem Solana yang baru menyelesaikan Season 1 program airdrop (berakhir tanggal 31 Maret 2024). Mereka melanjutkan program <em>airdrop</em> season 2 untuk 3 bulan ke depan (berakhir Juni 2024).</p>
<p>Saya mendapatkan alokasi 664 $KMNO untuk season 1. Modal tetap saya tinggalkan di Kamino untuk berpartisipasi untuk season 2 nya.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713110884562/6942c4c8-3551-4107-98e4-a56d9faca434.png" alt class="image--center mx-auto" /></p>
<p>Bagi kamu yang punya wallet Solana, boleh ikut berpartisipasi. Jangan sampai ketinggalan.</p>
<p><a target="_blank" href="https://app.kamino.finance/genesis">KMNO Genesis | Kamino Finance</a></p>
<h2 id="heading-joltify-finance">Joltify Finance</h2>
<p><img src="https://s1.coincarp.com/news/other/20220411/0ff43d56e53a770f04be60ecfa1f914e.jpg" alt="Joltify Finance (JOLT) IEO Round 3 - ProBit Global | CoinCarp" /></p>
<p>Dapps Lending di ekosistem Cosmos. Saat ini jaringan Testnet sudah aktif dan program insentif Testnet juga sedang berjalan. Dengan berpartisipasi dalam program seperti ini, kita berkesempatan untuk mendapatkan token <em>airdrop</em> di masa mendatang.</p>
<p>Program ini tidak membutuhkan modal. Cukup dengan membuat wallet Keplr, kamu bisa langsung berpartisipasi.</p>
<p><a target="_blank" href="https://hub.joltify.io?code=JSUJVX">https://hub.joltify.io?code=JSUJVX</a></p>
<h2 id="heading-elys-network">Elys Network</h2>
<p><img src="https://images.mirror-media.xyz/publication-images/oUb78Yj-U-H-uq5wue-Xb.png?height=800&amp;width=1600" alt="Elys network Odyssey — Elys Network" /></p>
<p>Sama seperti Joltify, program insentif Testnet sedang berjalan sehingga partisipasi program <em>airdrop</em> gratis. Tidak butuh modal, jadi tunggu apa lagi.</p>
<p><a target="_blank" href="https://elys.bonusblock.io?r=N76z6bMW">https://elys.bonusblock.io?r=N76z6bMW</a></p>
<h2 id="heading-koii-network">Koii Network</h2>
<p><img src="https://outlierventures.io/wp-content/uploads/2021/03/Koii2.png" alt="Koii Network - Outlier Ventures" class="image--center mx-auto" /></p>
<p>Proyek DePIN yang bisa digarap dengan membuat Node untuk berpartisipasi dalam program Testnet nya. <em>Hardware</em> yang dibutuhkan untuk berpartisipasi cukup ringan. Silahkan cek di websitenya langsung.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://docs.koii.network/run-a-node/task-nodes/Running-on-VPS">https://docs.koii.network/run-a-node/task-nodes/Running-on-VPS</a></div>
<p> </p>
<h2 id="heading-berachain">Berachain</h2>
<p><img src="https://www.finsmes.com/wp-content/uploads/2024/03/berachain.jpg" alt="Berachain Raises $69M in Funding" /></p>
<p>Blockhain terbaru yang kompatible dengan EVM menggunakan <em>Proof-of-Liquidity</em>. Program insentif testnet sedang berjalan dan tidak membutuhkan modal apa pun.</p>
<p><a target="_blank" href="https://www.berachain.com/">https://www.berachain.com/</a></p>
<h2 id="heading-taiko">Taiko</h2>
<p>Program Testnet sebenarnya sudah berjalan cukup lama. Walaupun potensinya cukup kecil, tidak ada ruginya untuk menggarap proyek ini.</p>
<p><a target="_blank" href="https://taiko.xyz/">https://taiko.xyz/</a></p>
]]></content:encoded></item><item><title><![CDATA[Panduan Airdrop Scroll]]></title><description><![CDATA[Salah satu project yang belum mempunyai token dan menarik untuk digarap adalah Scroll. Scroll merupakan jaringan layer-2 Etherum berbasis teknologi ZK-rollup yang memproses transaksi di luar jaringan Ethereum. Tujuan dari project ini adalah untuk men...]]></description><link>https://ariesgun.xyz/panduan-airdrop-scroll</link><guid isPermaLink="true">https://ariesgun.xyz/panduan-airdrop-scroll</guid><category><![CDATA[airdrop]]></category><category><![CDATA[scroll]]></category><category><![CDATA[crypto]]></category><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Thu, 11 Apr 2024 10:00:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1712787465239/302f7895-11eb-4231-8234-f538aee0c047.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Salah satu project yang belum mempunyai token dan menarik untuk digarap adalah Scroll. Scroll merupakan jaringan layer-2 Etherum berbasis teknologi ZK-rollup yang memproses transaksi di luar jaringan Ethereum. Tujuan dari project ini adalah untuk mengurangi biaya transaksi dan meningkatkan kecepatan transaksi.</p>
<p>Project ini sudah mendapatkan <em>funding</em> dari Tier-A VC dengan total sebesar 80M $USD dan salah satu VC utamanya adalah Polychain Capital. Karena Scroll belum mempunyai token dan TVL masih rendah, hal ini membuat project ini menarik untuk digarap untuk mendapatkan <em>airdrop.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1712784442038/a47a7812-f3b8-4506-b731-dcb57df8a2ed.png" alt="scroll funding rounds" class="image--center mx-auto" /></p>
<h2 id="heading-langkah-1-scroll-bridge">Langkah 1 - Scroll Bridge</h2>
<p>Kalau kamu belum pernah mengirim token ETH ke Scroll, maka kamu perlu mengirim token terlebih dahulu. Sebisa mungkin lakukan <em>bridging</em> minimal satu kali dari jaringan Ethereum ke Scroll menggunakan <a target="_blank" href="https://scroll.io/bridge">https://scroll.io/bridge</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1712785330161/4e2b3080-bec7-4eb1-9b07-26970c88f2ae.png" alt class="image--center mx-auto" /></p>
<p>Untuk mengurangi biaya transaksi (<em>gas fee)</em>, kalian bisa melakukan transaksi ini pada hari sabtu atau minggu. Pada umumnya, jaringan tidak sibuk selama <em>weekend</em> sehingga <em>gas fee</em> cukup murah.</p>
<p>Alternatif lain adalah menggunakan <em>bridge dapps</em> berikut ini.</p>
<ul>
<li><p>OwlTo Bridge (<a target="_blank" href="https://owlto.finance/">https://owlto.finance/</a>)</p>
</li>
<li><p>Orbiter FInance (<a target="_blank" href="https://www.orbiter.finance/">https://www.orbiter.finance/</a>)</p>
</li>
<li><p>Rhino-fi Bridge (<a target="_blank" href="https://app.rhino.fi/bridge">https://app.rhino.fi/bridge</a>)</p>
</li>
</ul>
<h2 id="heading-langkah-2-tingkatkan-transaksi">Langkah 2 - Tingkatkan transaksi</h2>
<p>Langkah berikutnya adalah meningkatkan jumlah transaksi dan volume transaksi secara konsisten. Target pada umumnya adalah minimal 5x transaksi per bulan dan/atau $10000 volume transaksi per bulan. Idealnya lakukan transaksi setiap hari atau minggu.</p>
<p>Bagaimana cara mencapai target tersebut? Kalau kita cek di defilama, jaringan Scroll memiliki list dapps yang sudah terdaftar. Untuk meningkatkan jumlah transaksi, kita cukup melakukan transaksi di dapps-dapps tersebut. Pilih dapps dengan TVL yang cukup tinggi untuk alasan keamanan.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1712785743462/e786a18d-9c41-490e-89ac-a4e84653d592.png" alt class="image--center mx-auto" /></p>
<p>Sebelum melakukan transaksi, pastikan URL benar dan jaringan yang dipilih adalah Scroll.</p>
<h3 id="heading-ambient-finance-httpsambientfinancehttpsambientfinance">Ambient Finance - <a target="_blank" href="https://ambient.finance/">https://ambient.finance/</a></h3>
<p>Swap ETH ke USDC, kemudian ke USDT, dan (optional) kembali ke ETH.</p>
<p>Optional -&gt; Buat LP ETH-USDC.</p>
<h3 id="heading-sushiswap-httpswwwsushicomswaphttpswwwsushicomswap">Sushiswap - <a target="_blank" href="https://www.sushi.com/swap">https://www.sushi.com/swap</a></h3>
<p>Lakukan transaksi swap sama seperti Ambient Finance. Kalian juga bisa memilih pasangan token lain biar lebih bervariasi.</p>
<h3 id="heading-syncswap-httpssyncswapxyzhttpssyncswapxyz">SyncSwap - <a target="_blank" href="https://syncswap.xyz/">https://syncswap.xyz/</a></h3>
<p>Lakukan transaksi swap sama seperti Ambient Finance. Kalian juga bisa memilih pasangan token lain biar lebih bervariasi.</p>
<p>Optional -&gt; Buat LP ETH-USDC</p>
<h3 id="heading-izumi-finance-httpsizumifinancetradeswaphttpsizumifinancetradeswap">Izumi Finance - <a target="_blank" href="https://izumi.finance/trade/swap">https://izumi.finance/trade/swap</a></h3>
<p>Lakukan transaksi swap sama seperti Ambient Finance. Kalian juga bisa memilih pasangan token lain biar lebih bervariasi.</p>
<h3 id="heading-merkly-httpsmintermerklycomhyperlanehttpsmintermerklycomhyperlane">Merkly - <a target="_blank" href="https://minter.merkly.com/hyperlane">https://minter.merkly.com/hyperlane</a></h3>
<p>Mint NFT di jaringan Scroll dan kirim NFT ke jaringan lain.</p>
<p>Merkly menggunakan teknologi Hypelane dan ada potensi airdrop sehingga dengan menggunakan dapps ini, kalian juga berkesempatan mendapatkan airdrop Hyperlane.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1712786513753/6a675f0b-8f3b-4b96-b118-c1b930eaf4d4.png" alt class="image--center mx-auto" /></p>
<p>Perhatikan tujuan jaringan yang dikirim karena biaya pengiriman berbeda-beda untuk masing-masing jaringan.</p>
<h3 id="heading-aave-httpsappaavecomhttpsappaavecom">Aave - <a target="_blank" href="https://app.aave.com/">https://app.aave.com/</a></h3>
<p>Ganti jaringan ke Scroll. Kalian bisa melakukan transaksi seperti ini.</p>
<ul>
<li><p>Deposit -&gt; Borrow -&gt; Repay -&gt; Withdraw. Pastikan berikan jeda beberapa menit setelah melakukan transkasi.</p>
</li>
<li><p>Deposit -&gt; Withdraw -&gt; Deposit dan seterusnya.</p>
</li>
</ul>
<p>Cara ini bisa digunakan untuk meningkatkan jumlah dan volume transaksi.</p>
<p>Aave merupakan platform pinjam meminjam. Dapps ini biasanya saya pakai di tahap akhir. Ketimbang meletakkan token di dompet, saya lebih memilih untuk menyimpan token saya di Aave dan mendapatkan bunga setiap harinya.</p>
<h3 id="heading-dapp-non-defi">Dapp non-Defi</h3>
<p>Untuk list dapps di jaringan Scroll di luar Defi bisa dicek di <a target="_blank" href="https://scroll.io/ecosystem">https://scroll.io/ecosystem</a>.</p>
<h2 id="heading-penutup">Penutup</h2>
<p>Untuk meningkatkan peluang mendapatkan airdrop, kalian harus bisa meningkatkan jumlah dan volume transaksi secara konsisten. Lakukan transaksi secara konsisten setiap minggu dan usahain tingkatin jumlah transaksi sampai 100 lebih. Target untuk volume transaksi sebisa mungkin minimal $10000 dan tingatkan bertahap sampai mencapai $100000.</p>
<p>Ingat kalian tidak perlu mencapai target ini dalam satu bulan. Garap airdrop secara konsisten dan kalian bisa mencapai target ini dalam 3 bulan. Sebagai contoh bermodalkan $300, kalau kalian melakukan transaksi sebanyak 10 kali saja per hari, kalian sudah mengumpulkan volume $3000. Kalau dilakukan minimal sekali seminggu, kalian sudah bisa mengumpulkan volume sebesar $12000.</p>
<p>Variasi transaksi juga sangat penting. Semakin dapps yang dipakai maka semakin bagus.</p>
<p>Terakhir, kalian bisa memantau perkembangan kalian menggunakan link-link berikut.</p>
<p><a target="_blank" href="https://wenser.vercel.app/">https://wenser.vercel.app/</a></p>
<p><a target="_blank" href="https://trustgo.trustalabs.ai/dashboard/0x085ed975a8b6b860de3c2b871da60a3f9f48a5b8?chainId=324">https://trustgo.trustalabs.ai/dashboard/</a></p>
<p><a target="_blank" href="https://zkcodex.com/">https://zkcodex.com/</a></p>
<p>Sekian. Semoga beruntung!</p>
]]></content:encoded></item><item><title><![CDATA[Grass - Revolusi AI Data Layer]]></title><description><![CDATA[TL;DR
Grass adalah Layer 2 Data Rollup dengan konsep jaringan desentralisasi untuk mengumpulkan data-data dari internet untuk keperluan AI.
Partisipasi dalam jaringan Grass cukup mudah. Cukup dengan membuat Grass Node, kita "menjual" bandwidth intern...]]></description><link>https://ariesgun.xyz/grass-revolusi-ai-data-layer</link><guid isPermaLink="true">https://ariesgun.xyz/grass-revolusi-ai-data-layer</guid><category><![CDATA[depin]]></category><category><![CDATA[Cryptocurrency]]></category><category><![CDATA[Blockchain]]></category><category><![CDATA[grass,]]></category><category><![CDATA[#ai-tools]]></category><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Sat, 16 Mar 2024 15:28:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1710596348917/4c6c2c36-2e1b-42f4-9084-091f0dc97258.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-tldr"><strong>TL;DR</strong></h3>
<p>Grass adalah Layer 2 Data Rollup dengan konsep jaringan desentralisasi untuk mengumpulkan data-data dari internet untuk keperluan AI.</p>
<p>Partisipasi dalam jaringan Grass cukup mudah. Cukup dengan membuat Grass Node, kita "menjual" <em>bandwidth</em> internet yang tidak digunakan dan mendapatkan imbalan sebagai gantinya.</p>
<p>Privasi kalian aman karena Grass Node tidak bisa mengakses komputer kalian dan hanya mengalihkan trafik internet melewati alamat IP kita.</p>
<p>Untuk berpartisipasi, kalian bisa menggunakan <a target="_blank" href="https://bit.ly/43sewWD">link</a> di bawah ini. Kode referal wajib untuk bisa berpartisipasi dan kita akan sama-sama mendapatkan poin tambahan.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://bit.ly/43sewWD">https://bit.ly/43sewWD</a></div>
<p> </p>
<h3 id="heading-depin-ai-dan-grass">DePIN, AI, dan Grass</h3>
<p>Sejak beberapa bulan yang lalu, topik DePIN merupakan salah satu topic yang cukup <em>hot</em> dan sering diperbincangkan. DePIN adalah singkatan dari <em>Decentralized Physical Infrastructure Network</em>.</p>
<p>Bagi yang sudah lama berkecimpung di dunia, pastinya sudah paham dengan konsep desentralisasi. Bitcoin adalah koin pertama yang menawarkan kemudahan mengirimkan aset (uang) ke orang lain tanpa perantara (bank). Ethereum adalah yang pertama menawarkan konsep dApps dimana aplikasi bisa berjalan di dalam jaringan blockchain secara terdesentraliasi.</p>
<p>DePIN dalam konteks desentralisasi menawarkan konsep desentralisasi jaringan infrastruktur, baik secara fisik mau digital. Pihak yang aktif berkontribusi dalam pengembangan jaringan atau infrastruktur ini akan diberikan intensif dalam bentuk token. Contoh yang terkenal adalah Helium dengan konsep pengembangan jaringan internet atau konektivitas dan Filecoin dengan konsep penyimpanan data secara terdesentralisasi. Jadi, untuk menyimpan data kita tidak perlu lagi bergantung sama satu pihak seperti Amazon, Google, Microsoft atau Dropbox. Kita bisa menyimpan data apa saja dan tidak ada satu pun pihak yang bisa melarang kita. Itulah indahnya desentralisasi.</p>
<p>Grass termasuk dalam naratif DePIN dan dalam hal ini, Grass menawarkan konsep desentralisasi data untuk keperluan pelatihan AI. Dalam jaringan Grass, terdapat ribuan <em>grass</em> <em>node</em> yang ikut berpartisipasi dalam pengumpulan data di internet. Data kemudian akan diproses sehingga siap untuk digunakan untuk melatih AI. Data inilah yang menjadi nilai jual untuk keperluan pelatihan AI.</p>
<h3 id="heading-program-insentif-grass-node">Program Insentif Grass Node</h3>
<p>Saat ini Grass sedang menjalan program insentif beta yang memberikan kesempatan kepada khalayak umum untuk menjadi <em>grass node</em>. Partisipan sebagai <em>grass node</em> "menjual" bandwidth internet yang tidak digunakan dan sebagai imbalan akan diberikan <em>reward</em> dalam bentuk poin. Saat ini belum jelas apakah Grass akan menjalankan program coin airdrop. Tapi yang pasti partisipan yang mempunyai poin tinggi akan berpotensi mendapat <em>reward</em> di masa depan.</p>
<p>Untuk berpartisipasi dalam program ini cukup mudah tentunya. Saya sendiri menggunakan Raspberry Pi 3 saya yang sudah jarang digunakan. Cukup menginstall Chromium terbaru dan <em>web extension Grass,</em> kita sudah dapat mulai mendapatkan poin. Langkah-langkah untuk meng-setup grass node bisa dibaca di halaman <a target="_blank" href="https://wynd-network.gitbook.io/grass-docs/how-to-guide/set-up-grass-node">ini</a>.</p>
<p><a target="_blank" href="https://wynd-network.gitbook.io/grass-docs/how-to-guide/set-up-grass-node"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710602477558/02b89cf4-3fe2-4087-8ad4-1284e110286b.png" alt class="image--center mx-auto" /></a></p>
<h3 id="heading-progress-saat-ini">Progress saat ini</h3>
<p>Saya baru menjalankan program ini selama dua minggu dan sampai sekarang saya sudah mengumpulkan hampir 20K poin. Per artikel ini ditulis, program sudah berada di epoch 3 yang berakhir tanggal 9 April 2024. Jadi masih ada kesempatan bagi kalian yang ingin mengikuti program ini.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710597811004/edf37478-0dad-4924-93e2-be35b96f70b9.png" alt class="image--center mx-auto" /></p>
<p>Poin yang bisa dikumpulkan per hari untuk satu device adalah sekitar 1600 poin. Ini dengan asumsi device nyala 24 jam 7 hari.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710597830251/b0abb31d-2b69-41a6-9782-52f8e2a1e55a.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710597843798/fecd0d23-afdb-4604-b445-0dd9fe432178.png" alt class="image--center mx-auto" /></p>
<p>Jadi, semakin cepat kamu bergabung, semakin besar poin yang bisa kamu dapatkan. Proyek proyek separti Grass biasanya memberikan imbal hasil lebih besar ke para pengguna fase awal. Sekarang adalah kesempatan bagi kamu untuk ikutan program seperti ini. Jangan sampai ketinggalan!</p>
]]></content:encoded></item><item><title><![CDATA[Panduan Lengkap Cosmos Coin Airdrop]]></title><description><![CDATA[Crypto coin airdrop merupakan suatu event dimana coin crypto ditransfer ke para dompet-dompet pengguna secara gratis. Biasanya ini dilakukan ketika project baru ingin launching dan untuk mengumpulkan likuiditas, mereka memberikan koin baru mereka sec...]]></description><link>https://ariesgun.xyz/panduan-lengkap-cosmos-coin-airdrop</link><guid isPermaLink="true">https://ariesgun.xyz/panduan-lengkap-cosmos-coin-airdrop</guid><category><![CDATA[Cosmos ecosystem]]></category><category><![CDATA[airdrop]]></category><category><![CDATA[Atom]]></category><category><![CDATA[Celestia]]></category><category><![CDATA[CryptoAirdrops ]]></category><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Sat, 09 Mar 2024 18:46:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/1vKTnwLMdqs/upload/6f2a9c088327cb1358eeb6a8890bd14b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Crypto coin airdrop merupakan suatu event dimana coin crypto ditransfer ke para dompet-dompet pengguna secara gratis. Biasanya ini dilakukan ketika project baru ingin launching dan untuk mengumpulkan likuiditas, mereka memberikan koin baru mereka secara gratis kepada para pengguna loyal. Contoh airdrop-airdrop yang terkenal akhir-akhir ini adalah koin meme $PEPE dan $BONK di ekosistem Solana. Selain Solana, terdapat juga koin-koin baru di ekosistem Cosmos yang di-airdrop dan mengalami kenaikan harga secara signifikan juga.</p>
<p>Di artiket ini, saya menjabarkan panduan untuk bisa berpartisipasi di event coin airdrop di ekosistem blockchain Cosmos. Ekosistem Cosmos merupakan salah satu blockchain yang memberikan yield coin-airdrop yang paling menarik. Sebagai <em>Internet of blockchains,</em> banyak chain-chain yang telah dilahirkan dalam ekosistem Cosmos (salah satunya Terra di masa lampau) dan memberikan keuntungan yang cukup menarik kepada para $ATOM stakers.</p>
<p>Lantai bagaimana cara untuk berpartisipasi di dalam coin airdrop cosmos? Sebenarnya cukup mudah, kita hanya perlu meng-stake token di Cosmos Hub dan di side blockchain yang lainnya. Contoh dalam panduan ini adalah kita akan meng-stake $ATOM di Cosmos Hub (chain utamanya) dan $TIA di Celestia chain.</p>
<p>Per tanggal artikel ini ditulis, ada kesempatan untuk berpartisipasi dalam airdrop $AETHER.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://x.com/Cosmos_Airdrops/status/1766475423934738487?s=20">https://x.com/Cosmos_Airdrops/status/1766475423934738487?s=20</a></div>
<p> </p>
<p>Berikut langkah-langkah yang harus dilakukan.</p>
<h2 id="heading-langkah-1-install-cosmos-wallet">Langkah 1: Install Cosmos Wallet</h2>
<p>Untuk bisa berpartisipasi dalam proses staking $ATOM, kita perlu memiliki dompet Cosmos terlebih dahulu. Salah satu dompet Cosmos yang direkomendasikan adalah Kepler.</p>
<p>Akses <a target="_blank" href="http://www.keplr.app">www.keplr.app</a> dan ikutin langkah-langkah di website mereka untuk membuat dompet Cosmos. Pastikan <em>recovery phrases</em> kalian disimpan dengan aman.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710084883786/67a83080-5554-49ce-ac2e-4bc5d3a48aeb.png" alt class="image--center mx-auto" /></p>
<p>Setelah kalian melakukan langkah ini, keplr wallet extension seharusnya sudah ter-install di browser kalian.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710084965351/5297f7a2-77ef-410c-8ad5-09d2122bb702.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-langkah-2-kirim-koin-atom-ke-dompet-kepler">Langkah 2: Kirim koin $ATOM ke dompet Kepler</h2>
<p>Setelah memiliki dompet Kepler, kalian bisa mulai mengirimkan koin ATOM kalian ke dompet Kepler. Bagi yang belum memiliki koin $ATOM, kalian bisa membeli koin $ATOM terlebih dahulu di <a target="_blank" href="http://www.kepler.com">aplikasi CEx (</a>Centralized Exchange), seperti Binance, Bybit, atau Coinbase. Kalian bisa mendapatkan alamat walet Cosmos kalian di wallet Kepler. Verifikasi ulang alamat yang dikirim benar.</p>
<h2 id="heading-langkah-3-mulai-staking-atom-di-cosmos-hub">Langkah 3: Mulai Staking $ATOM di Cosmos Hub</h2>
<p>Akses dashboard dompet Kepler kalian. Selanjutnya pilih Stake dan pilih aset yang akan di-stake, dalam hal ini kita akan meng-stake $ATOM, dan validator.</p>
<p>Beberapa tips yang perlu diketahui ketika memilih validator.</p>
<ul>
<li><p>Jangan pilih validator central exchange seperti <a target="_blank" href="http://www.kepler.com">Binance, Coinb</a>ase, dan lainnya. Berdasarkan pengalaman, para stakers yang meng-stake koin mereka di validator-validator ini tidak mendapatkan coin-airdrop. Alasan utamanya a<a target="_blank" href="http://www.kepler.com">dalah mereka i</a>ngin mempromosikan d<a target="_blank" href="http://www.kepler.com">esentralisasi.</a></p>
</li>
<li><p>Jangan pilih validator yang memiliki biaya komisi tinggi dan komisi 0%.</p>
</li>
<li><p>Pilih validator yang memiliki biaya komisi antara 3% - 8%.</p>
</li>
</ul>
<p>Untuk mendapatkan kesempatan terpilih dalam airdrop, pastikan kalian memasukkan minimal 10 token $ATOM (senilai $100). Tentu saja semakin banyak token yang di-stake, semakin tinggi kesempatan untuk dipilih dan semakin besar hadiahnya.</p>
<h2 id="heading-langkah-4-staking-koin-dihttpwwwkeplercom-chain-cosmos-lainnya">Langkah 4: St<a target="_blank" href="http://www.kepler.com">aking koin di</a> chain cosmos lainnya.</h2>
<p>Ulangin langkah 1-3 untuk stake di chain lainnya. Contoh Chain Cosmos yang saya minati saat ini adalah Celest<a target="_blank" href="http://www.kepler.com">ia.</a> Saya juga akan meng-stake <a target="_blank" href="http://www.kepler.com">koin $TIA say</a>a di chain Celestia. Harapannya di masa mendatang ada project-project baru dalam Celestia yang melakukan airdrop.</p>
<p>Pilih <em>Chains</em> dan pilih Celestia</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710081254468/9dbb9640-6d68-4abb-88b8-5ddd6be9bb7c.png" alt class="image--center mx-auto" /></p>
<p>Selanjutnya pilih validator-nya. Ikutin tips-tips yang sama ketika melakukan staking $ATOM.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710081264049/3fe38b42-9a1a-40a9-93ea-953320d1ef63.png" alt class="image--center mx-auto" /></p>
<p>Mas<a target="_blank" href="http://www.kepler.com">ukkin nominal</a> koin yang akan di-stake dan <em>appr</em><a target="_blank" href="http://www.kepler.com"><em>ove transaksi</em></a><em>nya.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710081271729/e1085baf-f1b2-4341-b0ea-5883cd84d0da.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-langkah-5-tunggu-dan-hodl">Langkah 5: Tunggu dan HODL.</h2>
<p>Langkah selanjutnya adalah menunggu saja sampai kita mendapatkan <a target="_blank" href="http://www.kepler.com">coin-airdrop.</a> Kalian bisa ‘follow’ akun twitter cosmos atau <a target="_blank" href="https://cosmos.leapwallet.io/airdrops">Leap Cosmos Airdrop</a> <a target="_blank" href="https://cosmos.leapwallet.io/airdrops">untuk mendapatkan berita terbaru even</a>t-event coin airdrop di masa mendatang.</p>
<p>Dan jangan lupa untuk klaim <em>reward</em> hasil staking.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710081278267/70cf7739-2905-4071-80d8-519f74ed5a95.png" alt class="image--center mx-auto" /></p>
<p>Kalau kalian bullish di ekosistem COSMOS, tidak ada salahnya untk top-up konsisten per bulan. Dan tentunya ini bukan ajakan <a target="_blank" href="http://www.kepler.com">untuk membeli</a> atau menjual, hanya informasi semata. <em>Disclaimer ON.</em> 🚀🚀🚀</p>
]]></content:encoded></item><item><title><![CDATA[Outerbase - Better Map Preview Plugins]]></title><description><![CDATA[In the previous article, I discussed creating a Map Preview plugin for Outerbase using the Google Maps API. That particular implementation utilized the Web component version of Google Maps API. It works, but the features are still limited compared to...]]></description><link>https://ariesgun.xyz/outerbase-better-map-preview-plugins</link><guid isPermaLink="true">https://ariesgun.xyz/outerbase-better-map-preview-plugins</guid><category><![CDATA[Outerbase]]></category><category><![CDATA[outerbasehackathon]]></category><category><![CDATA[outerbase-plugin]]></category><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Thu, 28 Sep 2023 12:14:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1695902044795/413710ea-a8c9-45c4-858f-e9cecc92650f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the <a target="_blank" href="https://ariesgun.xyz/outerbase-map-preview-plugin">previous article</a>, I discussed creating a Map Preview plugin for Outerbase using the Google Maps API. That particular implementation utilized the Web component version of Google Maps API. It works, but the features are still limited compared to the full features that we can get if we use the standard Google Maps JS API. So, I am interested in exploring the possibility of developing a Map plugin using this approach to take advantage of the full features offered by Google Maps API.</p>
<h1 id="heading-about-the-plugin">About the Plugin</h1>
<p>To reiterate what I have mentioned in the previous article, we can use this plugin to visualize addresses or GPS coordinates on a Google Maps view. Instead of merely observing a bunch of coordinates numbers in a spreadsheet, we can display them in a visually appealing representation.</p>
<p>I am going to create Map Preview plugins that work with both tables and cells.</p>
<h1 id="heading-table-plugin">Table Plugin</h1>
<p>For the demonstration, I am using a dummy database generated using UIBakery (<a target="_blank" href="http://uibakery.io/sql-playground"><strong>uibakery.io/sql-playground</strong></a><strong>).</strong> They can generate a Car dealer database which contains a table that stores the location of all cars they have. The plugin uses the longitude and latitude information to show the location on a map.</p>
<h2 id="heading-configuration-view">Configuration View</h2>
<p>I made a few minor modifications to the configuration view. First, a new text field is added so that the user can put in the Google Maps API key. As a result, there is no longer a need to include a hard-coded API key in the codebase. Below is the updated configuration class.</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OuterbasePluginConfig_</span>$<span class="hljs-title">PLUGIN_ID</span> </span>{
    <span class="hljs-comment">// Inputs from Outerbase for us to retain</span>
    tableValue = <span class="hljs-literal">undefined</span>
    count = <span class="hljs-number">0</span>
    page = <span class="hljs-number">1</span>
    offset = <span class="hljs-number">50</span>
    theme = <span class="hljs-string">"light"</span>

    <span class="hljs-comment">// Inputs from the configuration screen</span>
    imageKey = <span class="hljs-literal">undefined</span>
    apiKey = <span class="hljs-literal">undefined</span>
    titleKey = <span class="hljs-literal">undefined</span>
    descriptionKey = <span class="hljs-literal">undefined</span>
    subtitleKey = <span class="hljs-literal">undefined</span>
    longitudeKey = <span class="hljs-literal">undefined</span>
    latitudeKey = <span class="hljs-literal">undefined</span>

    <span class="hljs-comment">// Variables for us to hold state of user actions</span>
    deletedRows = []

    <span class="hljs-keyword">constructor</span>(object) {
        <span class="hljs-built_in">this</span>.imageKey = object?.imageKey
        <span class="hljs-built_in">this</span>.apiKey = object?.apiKey
        <span class="hljs-built_in">this</span>.titleKey = object?.titleKey
        <span class="hljs-built_in">this</span>.descriptionKey = object?.descriptionKey
        <span class="hljs-built_in">this</span>.subtitleKey = object?.subtitleKey
        <span class="hljs-built_in">this</span>.latitudeKey = object?.latitudeKey
        <span class="hljs-built_in">this</span>.longitudeKey = object?.longitudeKey
    }

    toJSON() {
        <span class="hljs-keyword">return</span> {
            <span class="hljs-string">"imageKey"</span>: <span class="hljs-built_in">this</span>.imageKey,
            <span class="hljs-string">"apiKey"</span>: <span class="hljs-built_in">this</span>.apiKey,
            <span class="hljs-string">"titleKey"</span>: <span class="hljs-built_in">this</span>.titleKey,
            <span class="hljs-string">"descriptionKey"</span>: <span class="hljs-built_in">this</span>.descriptionKey,
            <span class="hljs-string">"subtitleKey"</span>: <span class="hljs-built_in">this</span>.subtitleKey,
            <span class="hljs-string">"latitudeKey"</span>: <span class="hljs-built_in">this</span>.latitudeKey,
            <span class="hljs-string">"longitudeKey"</span>: <span class="hljs-built_in">this</span>.longitudeKey
        }
    }
}
</code></pre>
<p>Secondly, I reorganized the fields to make them more logical. Here is the final result.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695900383763/78e2388c-a65d-4883-a6b1-07e53759318c.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-table-view">Table View</h2>
<p>The challenging aspect of creating this plugin involved figuring out how to correctly import the Google Maps library. I encountered a couple of issues because the library complained about being imported multiple times. Eventually, I figured out how to resolve or work around the issue.</p>
<p>In addition, it is now possible to retrieve additional information about the view, such as the number of rows, current page index, and page count. These attributes are useful to prevent the user from accessing empty pages, which can happen if the user keeps pressing <strong>"Next Page" or "Previous Page".</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695900467304/29e5e0ad-59a3-4f12-bfca-42f2c8205300.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-map-preview-table-plugin-demo">Map Preview Table Plugin Demo</h2>
<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/xJIAfnQtit0">https://youtu.be/xJIAfnQtit0</a></div>
<p> </p>
<p>The full source code can be found here.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/ariesgun/outerbase/blob/main/plugins/map-preview-v2.js">https://github.com/ariesgun/outerbase/blob/main/plugins/map-preview-v2.js</a></div>
<p> </p>
<h1 id="heading-cell-plugin">Cell Plugin</h1>
<p>The plugin works with cells. In this case, the input will be a valid address and the plugin should show the address directly on a map. This plugin uses the Geocoder API provided by Google Maps API.</p>
<h2 id="heading-cell-view">Cell View</h2>
<p>The Cell View is quite simple as I adopted one of the cell plugin examples created by the Outerbase team. It shows an extra button on the cell to show the address on a map. When clicked, a map pops up with a marker indicating the address's location.</p>
<p>To improve the user experience, I implemented a mechanism where the button will open the map if it is not open yet, and close it if it is already in an open state.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695893578519/8207a8c4-166e-41f1-8050-692a8baebbdc.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695893614636/81595337-0123-4d60-aadc-0220cfed8c1b.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-cell-editor">Cell Editor</h2>
<p>The Cell Editor is where the map is going to be shown. The challenging aspect also involved figuring out how to import the Google Maps library. Fortunately, I was able to apply the same logic that I used for implementing the Table plugin.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695893770523/f8692b56-72b3-4dfa-a497-3351f920eca4.png" alt class="image--center mx-auto" /></p>
<p>The implementation itself is quite straightforward. After the Google Maps API has been loaded, we can start creating a map, use Geocoder API to get the map location of the address and create a marker on that location. Below is the code snippet demonstrating the implementation.</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// Code Snippet</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OuterbasePluginEditor_</span>$<span class="hljs-title">PLUGIN_ID</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
<span class="hljs-comment">// ...</span>
    render() {
        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">window</span>.gmap) {

            <span class="hljs-built_in">window</span>.gmap = <span class="hljs-keyword">new</span> google.maps.Map(<span class="hljs-built_in">this</span>.shadowRoot.getElementById(<span class="hljs-string">"map"</span>), {
                <span class="hljs-attr">center</span>: { <span class="hljs-attr">lat</span>: <span class="hljs-number">37.39094933041195</span>, <span class="hljs-attr">lng</span>: <span class="hljs-number">-122.02503913145092</span> },
                <span class="hljs-attr">zoom</span>: <span class="hljs-number">14</span>,
                <span class="hljs-attr">mapId</span>: <span class="hljs-string">"4504f8b37365c3d0"</span>,
            });

            <span class="hljs-keyword">const</span> marker = <span class="hljs-keyword">new</span> google.maps.Marker({
                <span class="hljs-attr">map</span>: <span class="hljs-built_in">window</span>.gmap
            });
            <span class="hljs-keyword">const</span> geocoder = <span class="hljs-keyword">new</span> google.maps.Geocoder();
            geocoder.geocode({ <span class="hljs-attr">address</span>: <span class="hljs-built_in">this</span>.getAttribute(<span class="hljs-string">'cellvalue'</span>)})
            .then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
                <span class="hljs-keyword">const</span> { results } = result;

                <span class="hljs-built_in">window</span>.gmap.setCenter(results[<span class="hljs-number">0</span>].geometry.location);
                marker.setPosition(results[<span class="hljs-number">0</span>].geometry.location);
                marker.setMap(<span class="hljs-built_in">window</span>.gmap);
                <span class="hljs-keyword">return</span> results;
            })
            .catch(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
                alert(<span class="hljs-string">"Geocode was not successful for the following reason: "</span> + e);
            });
        }
    }
}
</code></pre>
<h2 id="heading-map-preview-cell-plugin-demo">Map Preview Cell Plugin Demo</h2>
<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/o61hij7PSIE">https://youtu.be/o61hij7PSIE</a></div>
<p> </p>
<p>The source code of the cell plugin can be found here.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/ariesgun/outerbase/blob/main/plugins/map-cell-plugin.js">https://github.com/ariesgun/outerbase/blob/main/plugins/map-cell-plugin.js</a></div>
<p> </p>
<h1 id="heading-limitations">Limitations</h1>
<ul>
<li><p>At this moment, it is not possible to have a configuration view for a cell plugin. That's the reason why the Google Maps API Key is still hardcoded in the cell plugin code. Hopefully, the Outerbase team will implement a way to configure cell plugins.</p>
</li>
<li><p>The Map Preview table and cell plugins only support reading data from the Database. It might be interesting to be able to change the address value by simply dragging the marker on the map directly.</p>
</li>
</ul>
<h1 id="heading-conclusion">Conclusion</h1>
<p>In conclusion, creating Map Preview plugins for Outerbase using the Google Maps JS API allows for a more feature-rich and interactive experience compared to the Web component version. By developing both table and cell plugins, users can visualize addresses and GPS coordinates more effectively.</p>
<p>Hopefully, you find this article useful. If you have any comments or suggestions, feel free to leave them in the comment section. Thanks for reading.</p>
]]></content:encoded></item><item><title><![CDATA[Outerbase - Map Preview Plugin]]></title><description><![CDATA[Outerbase is a cloud-based database interface that modernizes how we interact with databases. Typical Database interface supports only basic CRUD operations in a spreadsheet form. Outerbase sets itself apart by offering the ability to query databases...]]></description><link>https://ariesgun.xyz/outerbase-map-preview-plugin</link><guid isPermaLink="true">https://ariesgun.xyz/outerbase-map-preview-plugin</guid><category><![CDATA[Outerbase]]></category><category><![CDATA[outerbasehackathon]]></category><category><![CDATA[outerbase-plugin]]></category><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Tue, 26 Sep 2023 12:53:34 GMT</pubDate><content:encoded><![CDATA[<p><strong>Outerbase</strong> is a cloud-based database interface that modernizes how we interact with databases. Typical Database interface supports only basic CRUD operations in a spreadsheet form. Outerbase sets itself apart by offering the ability to query databases using EZQL (a natural language to SQL for data queries), data visualization through plugins, and workflow automation using commands. With Outerbase, you can save time and hassle because everything you need is in one location.</p>
<p>One of the features offered in Outerbase is the ability to create and install plugins. Plugins enable us to perceive and interpret our data in custom visually engaging experiences. In this article, I will describe how I created a plugin for Outerbase to visualize GPS information.</p>
<h1 id="heading-about-the-plugin">About the Plugin</h1>
<p>I would like to create a plugin that allows users to visualize the addresses in the database table on a map view (Google Maps in this case). Instead of just looking at GPS coordinates in a number form, why not visualize them directly on a map? It is more appealing and useful.</p>
<p>For the demo, I am using a dummy database generated using UIBakery (<a target="_blank" href="https://uibakery.io/sql-playground"><strong>https://uibakery.io/sql-playground</strong></a><strong>).</strong> They can generate a Car dealer database which contains a table that stores the location of all cars they have. The plugin uses the longitude and latitude information to show the location on a map.</p>
<p>It is a simple plugin but I imagine this plugin can also be used in different scenarios or domains, for example, Asset Management where you can show the location of your assets visually on a map.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695728851228/793322db-3672-4180-96e0-3e4e902a3aae.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-creating-the-plugin">Creating the Plugin</h1>
<p>In this section, we will see how the configuration model is defined. After the configuration model is ready, we can start building the configuration view and the table plugin view.</p>
<h2 id="heading-configuration-model">Configuration Model</h2>
<p>Here we define a class model that captures the types of information for the plugin to render. For the Map View plugin, we want the users to specify which column(s) to reference the following fields:</p>
<ol>
<li><p>Longitude and Latitude.</p>
</li>
<li><p>Image.</p>
</li>
<li><p>Title.</p>
</li>
<li><p>Subtitle.</p>
</li>
<li><p>Description.</p>
</li>
</ol>
<p>The longitude and latitude are used to create the markers on the map; the remaining items are used to render the "detail view" when the user clicks on the marker.</p>
<p>Two extra fields are added: <code>longitudeKey</code> and <code>latitudeKey</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OuterbasePluginConfig_</span>$<span class="hljs-title">PLUGIN_ID</span> </span>{
    <span class="hljs-comment">// Inputs from Outerbase for us to retain</span>
    tableValue = <span class="hljs-literal">undefined</span>
    count = <span class="hljs-number">0</span>
    page = <span class="hljs-number">1</span>
    offset = <span class="hljs-number">50</span>
    theme = <span class="hljs-string">"light"</span>

    <span class="hljs-comment">// Inputs from the configuration screen</span>
    imageKey = <span class="hljs-literal">undefined</span>
    optionalImagePrefix = <span class="hljs-literal">undefined</span>
    titleKey = <span class="hljs-literal">undefined</span>
    descriptionKey = <span class="hljs-literal">undefined</span>
    subtitleKey = <span class="hljs-literal">undefined</span>
    longitudeKey = <span class="hljs-literal">undefined</span>
    latitudeKey = <span class="hljs-literal">undefined</span>

    <span class="hljs-comment">// Variables for us to hold state of user actions</span>
    deletedRows = []

    <span class="hljs-keyword">constructor</span>(object) {
        <span class="hljs-built_in">this</span>.imageKey = object?.imageKey
        <span class="hljs-built_in">this</span>.optionalImagePrefix = object?.optionalImagePrefix
        <span class="hljs-built_in">this</span>.titleKey = object?.titleKey
        <span class="hljs-built_in">this</span>.descriptionKey = object?.descriptionKey
        <span class="hljs-built_in">this</span>.subtitleKey = object?.subtitleKey
        <span class="hljs-built_in">this</span>.latitudeKey = object?.latitudeKey
        <span class="hljs-built_in">this</span>.longitudeKey = object?.longitudeKey
    }

    toJSON() {
        <span class="hljs-keyword">return</span> {
            <span class="hljs-string">"imageKey"</span>: <span class="hljs-built_in">this</span>.imageKey,
            <span class="hljs-string">"imagePrefix"</span>: <span class="hljs-built_in">this</span>.optionalImagePrefix,
            <span class="hljs-string">"titleKey"</span>: <span class="hljs-built_in">this</span>.titleKey,
            <span class="hljs-string">"descriptionKey"</span>: <span class="hljs-built_in">this</span>.descriptionKey,
            <span class="hljs-string">"subtitleKey"</span>: <span class="hljs-built_in">this</span>.subtitleKey,
            <span class="hljs-string">"latitudeKey"</span>: <span class="hljs-built_in">this</span>.latitudeKey,
            <span class="hljs-string">"longitudeKey"</span>: <span class="hljs-built_in">this</span>.longitudeKey
        }
    }
}
</code></pre>
<h2 id="heading-configuration-view">Configuration View</h2>
<p>After defining the configuration model, we can start defining how we present the user interface to the users so that they can fill in the configuration model. For this plugin, I would like to ask the users to specify which column each field should link to. In addition, the detail view will be previewed when the user starts filling in the fields.</p>
<p>Thankfully Outerbase team has done an awesome job providing an example plugin code. I used the example code and made some small modifications to build the configuration view for my plugin.</p>
<p>What I did was add two extra fields for the longitude and latitude. The preview is still displayed on the right side of the view.</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OuterbasePluginConfiguration_</span>$<span class="hljs-title">PLUGIN_ID</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{

    <span class="hljs-comment">// ...</span>

    render() {
        <span class="hljs-keyword">let</span> sample = <span class="hljs-built_in">this</span>.config.tableValue.length ? <span class="hljs-built_in">this</span>.config.tableValue[<span class="hljs-number">0</span>] : {}
        <span class="hljs-keyword">let</span> keys = <span class="hljs-built_in">Object</span>.keys(sample)

        <span class="hljs-keyword">if</span> (!keys || keys.length === <span class="hljs-number">0</span> || !<span class="hljs-built_in">this</span>.shadow.querySelector(<span class="hljs-string">'#configuration-container'</span>)) <span class="hljs-keyword">return</span>

        <span class="hljs-built_in">this</span>.shadow.querySelector(<span class="hljs-string">'#configuration-container'</span>).innerHTML = <span class="hljs-string">`
        &lt;div style="flex: 1;"&gt;

            // ... Truncated ...

            &lt;p class="field-title"&gt;Subtitle Key&lt;/p&gt;
            &lt;select id="subtitleKeySelect"&gt;
                `</span> + keys.map(<span class="hljs-function">(<span class="hljs-params">key</span>) =&gt;</span> <span class="hljs-string">`&lt;option value="<span class="hljs-subst">${key}</span>" <span class="hljs-subst">${key === <span class="hljs-built_in">this</span>.config.subtitleKey ? <span class="hljs-string">'selected'</span> : <span class="hljs-string">''</span>}</span>&gt;<span class="hljs-subst">${key}</span>&lt;/option&gt;`</span>).join(<span class="hljs-string">""</span>) + <span class="hljs-string">`
            &lt;/select&gt;

            &lt;p class="field-title"&gt;Longitude Key&lt;/p&gt;
            &lt;select id="longitudeKeySelect"&gt;
                `</span> + keys.map(<span class="hljs-function">(<span class="hljs-params">key</span>) =&gt;</span> <span class="hljs-string">`&lt;option value="<span class="hljs-subst">${key}</span>" <span class="hljs-subst">${key === <span class="hljs-built_in">this</span>.config.longitudeKey ? <span class="hljs-string">'selected'</span> : <span class="hljs-string">''</span>}</span>&gt;<span class="hljs-subst">${key}</span>&lt;/option&gt;`</span>).join(<span class="hljs-string">""</span>) + <span class="hljs-string">`
            &lt;/select&gt;

            &lt;p class="field-title"&gt;Latitude Key&lt;/p&gt;
            &lt;select id="latitudeKeySelect"&gt;
                `</span> + keys.map(<span class="hljs-function">(<span class="hljs-params">key</span>) =&gt;</span> <span class="hljs-string">`&lt;option value="<span class="hljs-subst">${key}</span>" <span class="hljs-subst">${key === <span class="hljs-built_in">this</span>.config.latitudeKey ? <span class="hljs-string">'selected'</span> : <span class="hljs-string">''</span>}</span>&gt;<span class="hljs-subst">${key}</span>&lt;/option&gt;`</span>).join(<span class="hljs-string">""</span>) + <span class="hljs-string">`
            &lt;/select&gt;

            &lt;div style="margin-top: 8px;"&gt;
                &lt;button id="saveButton"&gt;Save View&lt;/button&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        &lt;div style="position: relative;"&gt;
            &lt;div class="preview-card"&gt;
                &lt;img src="<span class="hljs-subst">${sample[<span class="hljs-built_in">this</span>.config.imageKey]}</span>" width="100" height="100"&gt;

                &lt;div&gt;
                    &lt;p style="margin-bottom: 8px; font-weight: bold; font-size: 16px; line-height: 24px; font-family: 'Inter', sans-serif;"&gt;<span class="hljs-subst">${sample[<span class="hljs-built_in">this</span>.config.titleKey]}</span>&lt;/p&gt;
                    &lt;p style="margin-bottom: 8px; font-size: 14px; line-height: 21px; font-weight: 400; font-family: 'Inter', sans-serif;"&gt;<span class="hljs-subst">${sample[<span class="hljs-built_in">this</span>.config.descriptionKey]}</span>&lt;/p&gt;
                    &lt;p style="margin-top: 12px; font-size: 12px; line-height: 16px; font-family: 'Inter', sans-serif; color: gray; font-weight: 300;"&gt;<span class="hljs-subst">${sample[<span class="hljs-built_in">this</span>.config.subtitleKey]}</span>&lt;/p&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        `</span>

        <span class="hljs-comment">// ....</span>
        <span class="hljs-comment">// Whenever the latitudeKey and longitudeKey are changed, re-render the view.</span>

        <span class="hljs-keyword">var</span> latitudeKeySelect = <span class="hljs-built_in">this</span>.shadow.getElementById(<span class="hljs-string">"latitudeKeySelect"</span>);
        latitudeKeySelect.addEventListener(<span class="hljs-string">"change"</span>, <span class="hljs-function">() =&gt;</span> {
            <span class="hljs-built_in">this</span>.config.latitudeKey = latitudeKeySelect.value
            <span class="hljs-built_in">this</span>.render()
        });

        <span class="hljs-keyword">var</span> longitudeKeySelect = <span class="hljs-built_in">this</span>.shadow.getElementById(<span class="hljs-string">"longitudeKeySelect"</span>);
        longitudeKeySelect.addEventListener(<span class="hljs-string">"change"</span>, <span class="hljs-function">() =&gt;</span> {
            <span class="hljs-built_in">this</span>.config.longitudeKey = longitudeKeySelect.value
            <span class="hljs-built_in">this</span>.render()
        });
    }
}
</code></pre>
<p>Here is the final result.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695651688850/879db794-50a9-4add-a0b8-14fff04b8a65.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-table-plugin-view">Table Plugin View</h2>
<p>This is the most important part of the plugin. The way it works is as follows: It receives all of the rows from the table and displays the results to the users. Using this plugin, we want to display the results to the users on a Google Maps view.</p>
<p>Given the longitude and latitude information, we will create a marker for each entry in the table. When the user clicks on one for the marker, they will see more detailed information about the marker.</p>
<p>The first challenge in using embedded Google Maps API in this plugin is importing the Google Maps API. Google provides a guide on how to use Google Maps API in a Web component. The document can be found <a target="_blank" href="https://developers.google.com/maps/documentation/javascript/web-components/overview#html">here</a>. It is still in preview mode, so I guess things might change in the future.</p>
<p>To import Google Maps API, I created a new DOM element for <code>script</code>. Here we can specify the <code>src</code> of the script. After that, we need to append this element to the <code>shadowDOM</code>. The script will be evaluated when it is being appended.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// ...</span>
<span class="hljs-keyword">var</span> script = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'script'</span>)
script.type = <span class="hljs-string">'text/javascript'</span>
script.async = <span class="hljs-literal">true</span>
script.src = <span class="hljs-string">"//maps.googleapis.com/maps/api/js?key=&lt;YOUR_API_KEY&gt;&amp;libraries=maps,marker&amp;v=beta&amp;callback=initMap"</span>;

<span class="hljs-comment">// ...</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OuterbasePluginTable_</span>$<span class="hljs-title">PLUGIN_ID</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
    <span class="hljs-comment">// ...</span>

    <span class="hljs-keyword">constructor</span>() {
        <span class="hljs-built_in">super</span>()

        <span class="hljs-built_in">this</span>.shadow = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">"open"</span> })    
        <span class="hljs-built_in">this</span>.shadow.appendChild(script)
        <span class="hljs-built_in">this</span>.shadow.appendChild(templateTable.content.cloneNode(<span class="hljs-literal">true</span>))
    }
</code></pre>
<p>After the Google Maps Javascript API has been included, we can start using the map by using the <code>&lt;gmp-map&gt;</code> custom element in the <code>render</code> function of the plugin table class. To add a marker, we can nest an <code>&lt;gmp-advanced-marker&gt;</code> element inside <code>&lt;gmp-map&gt;</code>. So in the code snippet, you can see that the <code>&lt;gmp-advanced-marker&gt;</code> element is added for every table row in the database (<code>this.config.tableValue</code>).</p>
<p>In addition, I would like to show a small info window showing more detailed information when a marker is being clicked on. To achieve this, we need to add an event listener to every <code>&lt;gmp-advanced-marker&gt;</code> element and the callback whenever the event occurs. The info window is just an HTML element. Since every map marker is unique, we need to pass some information from the <code>TableValue</code>. This can be easily done by adding <code>data-&lt;attribute&gt;</code> elements into the <code>&lt;gmp-advanced-marker&gt;</code> element. We can access this information in the callback by accessing the <code>dataset</code> variable.</p>
<p>Here is what the complete <code>render()</code> function looks like.</p>
<pre><code class="lang-javascript">
render() {

        <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">this</span>.config.page)

        <span class="hljs-built_in">this</span>.shadow.querySelector(<span class="hljs-string">"#container"</span>).innerHTML = <span class="hljs-string">`
        &lt;div class="grid-container"&gt;
            &lt;h1&gt;Welcome to the Outerbase Car Dealership!&lt;/h1&gt;
            &lt;div class="grid-item"&gt;
                &lt;gmp-map id="marker-click-event-example" center="43.4142989,-124.2301242" zoom="4" map-id="DEMO_MAP_ID"&gt;
                    <span class="hljs-subst">${<span class="hljs-built_in">this</span>.config?.tableValue?.length &amp;&amp; <span class="hljs-built_in">this</span>.config?.tableValue?.map((row) =&gt; <span class="hljs-string">`
                        &lt;gmp-advanced-marker position="<span class="hljs-subst">${row[<span class="hljs-built_in">this</span>.config.latitudeKey]}</span>,<span class="hljs-subst">${row[<span class="hljs-built_in">this</span>.config.longitudeKey]}</span>" title=<span class="hljs-subst">${row[<span class="hljs-built_in">this</span>.config.titleKey]}</span> 
                            data=title='<span class="hljs-subst">${row[<span class="hljs-built_in">this</span>.config.titleKey]}</span>'
                            data-image='<span class="hljs-subst">${row[<span class="hljs-built_in">this</span>.config.imageKey]}</span>'
                            data-description='<span class="hljs-subst">${row[<span class="hljs-built_in">this</span>.config.descriptionKey]}</span>'
                            data-subtitle='<span class="hljs-subst">${row[<span class="hljs-built_in">this</span>.config.subtitleKey]}</span>'
                        &gt;
                        &lt;/gmp-advanced-marker&gt;
                    `</span>).join(<span class="hljs-string">""</span>)}</span>
                &lt;/gmp-map&gt;
            &lt;/div&gt;

            &lt;div style="display: flex; flex-direction: column; gap: 12px;"&gt;
                &lt;h1&gt;What Next?&lt;/h1&gt;
                &lt;button id="previousPageButton"&gt;Previous Page&lt;/button&gt;
                &lt;button id="nextPageButton"}&gt;Next Page&lt;/button&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        `</span>

        <span class="hljs-keyword">const</span> advancedMarkers = <span class="hljs-built_in">this</span>.shadow.querySelectorAll(<span class="hljs-string">"#marker-click-event-example gmp-advanced-marker"</span>);

        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> advancedMarker <span class="hljs-keyword">of</span> advancedMarkers) {
            customElements.whenDefined(advancedMarker.localName).then(<span class="hljs-keyword">async</span> () =&gt; {
                advancedMarker.addEventListener(<span class="hljs-string">'gmp-click'</span>, <span class="hljs-keyword">async</span> () =&gt; {

                    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.infoWindow) {
                        <span class="hljs-built_in">this</span>.infoWindow.close()
                    }

                    <span class="hljs-keyword">const</span> {InfoWindow} = <span class="hljs-keyword">await</span> google.maps.importLibrary(<span class="hljs-string">"maps"</span>);

                    <span class="hljs-keyword">const</span> content = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'div'</span>);
                    content.classList.add(<span class="hljs-string">"property"</span>)
                    content.innerHTML = <span class="hljs-string">`
                        &lt;style&gt;
                            #theme-container {
                                height: 100%;
                            }

                            #container {
                                display: flex;
                                flex-direction: column;
                                height: 100%;
                                overflow-y: hidden;
                                width: 450px;
                            }

                            .grid-container {
                                flex: 1;
                                display: grid;
                                // grid-template-columns: repeat(2, minmax(0, 1fr));
                                gap: 12px;
                                padding: 12px;
                            }

                            .grid-item {
                                position: relative;
                                display: flex;
                                flex-direction: column;
                                background-color: transparent;
                                border: 1px solid rgb(238, 238, 238);
                                border-radius: 4px;
                                box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.05);
                                overflow: clip;
                            }

                            img {
                                vertical-align: top;
                                height: 300px;
                                object-fit: cover;
                            }

                            .contents {
                                padding: 12px;
                            }

                            .title {
                                font-weight: bold;
                                font-size: 16px;
                                line-height: 24px;
                                font-family: "Inter", sans-serif;
                                line-clamp: 2;
                                margin-bottom: 8px;
                            }

                            .description {
                                flex: 1;
                                overflow: hidden;
                                text-overflow: ellipsis;
                                font-size: 14px;
                                line-height: 20px;
                                font-family: "Inter", sans-serif;

                                display: -webkit-box;
                                -webkit-line-clamp: 3;
                                -webkit-box-orient: vertical;  
                                overflow: hidden;
                            }

                            .subtitle {
                                font-size: 12px;
                                line-height: 16px;
                                font-family: "Inter", sans-serif;
                                color: gray;
                                font-weight: 300;
                                margin-top: 8px;
                            }

                            p {
                                margin: 0;
                            }

                            .dark {
                                #container {
                                    background-color: black;
                                    color: white;
                                }
                            }
                        &lt;/style&gt;

                        &lt;div id="theme-container"&gt;
                            &lt;div id="container"&gt;
                                 &lt;div class="grid-item"&gt;
                                    <span class="hljs-subst">${ advancedMarker.dataset.image ? <span class="hljs-string">`&lt;img src="<span class="hljs-subst">${advancedMarker.dataset.image}</span>"&gt;`</span> : <span class="hljs-string">``</span> }</span>

                                    &lt;div class="contents"&gt;
                                        <span class="hljs-subst">${ advancedMarker.dataset.title ? <span class="hljs-string">`&lt;p class="title"&gt;<span class="hljs-subst">${advancedMarker.dataset.title}</span>&lt;/p&gt;`</span> : <span class="hljs-string">``</span> }</span>
                                        <span class="hljs-subst">${ advancedMarker.dataset.subtitle ? <span class="hljs-string">`&lt;p class="subtitle"&gt;<span class="hljs-subst">${advancedMarker.dataset.subtitle}</span>&lt;/p&gt;`</span> : <span class="hljs-string">``</span> }</span>
                                        <span class="hljs-subst">${ advancedMarker.dataset.description ? <span class="hljs-string">`&lt;p class="description"&gt;<span class="hljs-subst">${advancedMarker.dataset.description}</span>&lt;/p&gt;`</span> : <span class="hljs-string">``</span> }</span>
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;

                    `</span>

                    <span class="hljs-built_in">this</span>.infoWindow = <span class="hljs-keyword">new</span> InfoWindow({
                        <span class="hljs-attr">content</span>: content
                    });
                    <span class="hljs-built_in">this</span>.infoWindow.open({
                        <span class="hljs-attr">anchor</span>: advancedMarker
                    });
                });
            });
        }

        <span class="hljs-keyword">var</span> previousPageButton = <span class="hljs-built_in">this</span>.shadow.getElementById(<span class="hljs-string">"previousPageButton"</span>);
        previousPageButton.addEventListener(<span class="hljs-string">"click"</span>, <span class="hljs-function">() =&gt;</span> {
            triggerEvent(<span class="hljs-built_in">this</span>, {
                <span class="hljs-attr">action</span>: OuterbaseTableEvent.getPreviousPage,
                <span class="hljs-attr">value</span>: {}
            })
        });

        <span class="hljs-keyword">var</span> nextPageButton = <span class="hljs-built_in">this</span>.shadow.getElementById(<span class="hljs-string">"nextPageButton"</span>);
        nextPageButton.addEventListener(<span class="hljs-string">"click"</span>, <span class="hljs-function">() =&gt;</span> {
            triggerEvent(<span class="hljs-built_in">this</span>, {
                <span class="hljs-attr">action</span>: OuterbaseTableEvent.getNextPage,
                <span class="hljs-attr">value</span>: {}
            })
        });
    }
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695728911957/140a440f-b649-4abc-875d-41d3a31b9068.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-demo-time">Demo Time</h1>
<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=cXqwJo6ySK4">https://www.youtube.com/watch?v=cXqwJo6ySK4</a></div>
<p> </p>
<p>You can find the code in my Github repository.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/ariesgun/outerbase/blob/main/plugins/map-preview.js">https://github.com/ariesgun/outerbase/blob/main/plugins/map-preview.js</a></div>
<p> </p>
<h1 id="heading-final-thoughts">Final Thoughts</h1>
<p>I have had lots of fun building this plugin for the Outerbase platform. Web component is a new thing for me and I have got lots of issues in the beginning. But I am happy that I can make it work in the end. Overall, I think the ability to create a plugin and visualize the database tables directly on the platform is a cool feature. As more and more plugins are available, it can be a powerful tool to easily interact with our databases.</p>
<p>I hope this article helps you create a new plugin for Outerbase. If you have any questions or comments, feel free to do so.</p>
]]></content:encoded></item><item><title><![CDATA[Docker-based ESP32 Development Environment]]></title><description><![CDATA[In the previous article, I discussed the benefits of using Docker containers for building and managing development environments. In this post, I would like to describe how I use the Docker Dev Environment to create a development environment for build...]]></description><link>https://ariesgun.xyz/docker-based-esp32-development-environment</link><guid isPermaLink="true">https://ariesgun.xyz/docker-based-esp32-development-environment</guid><category><![CDATA[ESP32]]></category><category><![CDATA[Docker]]></category><category><![CDATA[docker dev environments]]></category><category><![CDATA[docker container]]></category><category><![CDATA[espressif]]></category><dc:creator><![CDATA[Aries]]></dc:creator><pubDate>Sun, 27 Aug 2023 15:29:28 GMT</pubDate><content:encoded><![CDATA[<p>In the <a target="_blank" href="https://ariesgun.xyz/managing-dev-projects-with-docker-dev-environments">previous article,</a> I discussed the benefits of using Docker containers for building and managing development environments. In this post, I would like to describe how I use the Docker Dev Environment to create a development environment for building ESP32 applications. Additionally, it is also possible to flash the application directly to an ESP board from within the container.</p>
<p>Luckily for us, Espressif has provided a Docker image (<code>espressif/idf</code>) for building applications and libraries with specific versions of ESP-IDF. It appears that the image is regularly, making it reasonably safe to employ it for our setup.</p>
<h2 id="heading-setup">Setup</h2>
<ul>
<li>For this setup, you are using a host system running Windows OS with WSL2 enabled. The WSL2 will be used so that we can access the ESP development board from within the container.</li>
</ul>
<ul>
<li><p>Docker Desktop installed</p>
</li>
<li><p>The IDE used here is VSCode with the <strong>Dev Containers</strong> installed. It lets you use a <a target="_blank" href="https://docker.com/">Docker container</a> as a full-featured development environment.</p>
</li>
</ul>
<h2 id="heading-constructing-compose-devyaml">Constructing <code>compose-dev.yaml</code></h2>
<p>To use the Docker Dev Environment, we need to create the <code>compose-dev.yaml</code> file. It is quite simple with a few additions.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">build-env:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">espressif/idf</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">sleep</span> <span class="hljs-string">infinity</span>
    <span class="hljs-attr">devices:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'/dev:/dev'</span>
    <span class="hljs-attr">init:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">..:/workspaces</span>
</code></pre>
<p>I added <code>devices: '/dev:/dev'</code> to map <code>/dev</code> directory from the WSL2 environment to the Docker container. This is useful for the next step when we want to communicate with the ESP board from inside the container.</p>
<p>Open Docker Desktop and go to Dev Environments. Simply click on the Create button and create the new environment. The container will stay running and you will see the following results.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693081274492/65c11d4f-dc91-4ae2-83e7-323d08a13387.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693081250394/823e79d4-c69b-4295-8398-04bae6c102bb.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-flashing-and-monitoring-esp32">Flashing and Monitoring ESP32</h2>
<p>The ESP-IDF Programming Guide <a target="_blank" href="https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-docker-image.html">page</a> outlines a method to communicate with the development boards from within the container. The idea is to use the remote serial port protocol (RFC2217). So I tried to follow the method described in the document, but it was not successful for me. Luckily there is another way to achieve this.</p>
<p>An alternative way for accessing the ESP development board from inside the container is using WSL2. By default, WSL2 does not support USB devices. Fortunately, there is a project officially supported by Microsoft that enables WSL2 to be able to access connected USB devices on Windows OS via a USB/IP mechanism. The project is called <code>usbipd-win</code>.</p>
<h3 id="heading-setting-up-usbipd-win">Setting up <code>usbipd-win</code></h3>
<ol>
<li><p>Firstly we need to install <code>usbipd-win</code>. We just need to follow the installation instructions described on its Github repository <a target="_blank" href="https://github.com/dorssel/usbipd-win">page</a>. It is pretty straightforward.</p>
</li>
<li><p>Open the WSL2 terminal and execute <code>uname -a</code>. If it reports a kernel version of 5.10.60.1 or later, then you don't need to do anything. Otherwise, follow the instructions on installing USB/IP client tools on WSL2 <a target="_blank" href="https://github.com/dorssel/usbipd-win">here</a>.</p>
</li>
<li><p>Run Powershell as Administrator and execute the following command.</p>
<pre><code class="lang-powershell"> <span class="hljs-comment">### Powershell</span>
 <span class="hljs-built_in">PS</span> C:\Windows\system32&gt; usbipd wsl list
 BUSID  VID:PID    DEVICE                                                        STATE
 <span class="hljs-number">2</span><span class="hljs-literal">-1</span>    <span class="hljs-number">303</span>a:<span class="hljs-number">1001</span>  USB Serial Device (COM3), USB JTAG/serial debug unit          Not attached
 <span class="hljs-number">2</span><span class="hljs-literal">-2</span>    <span class="hljs-number">046</span>d:c534  USB Input Device                                              Not attached
 <span class="hljs-number">2</span><span class="hljs-literal">-3</span>    <span class="hljs-number">0</span>bda:<span class="hljs-number">4853</span>  Realtek Bluetooth Adapter                                     Not attached
 <span class="hljs-number">3</span><span class="hljs-literal">-1</span>    <span class="hljs-number">04</span>f2:b758  Integrated Camera, Integrated IR Camera, Camera DFU Device    Not attached
</code></pre>
<p> Find the USB device of your ESP development board connected. In this example, it is assigned BUSID 2-1</p>
</li>
<li><p>Attach the USB device to WSL2 by running this command.</p>
<pre><code class="lang-powershell"> <span class="hljs-comment">### Powershell</span>
 <span class="hljs-built_in">PS</span> C:\Windows\system32&gt; usbipd wsl attach -<span class="hljs-literal">-busid</span> <span class="hljs-number">2</span><span class="hljs-literal">-1</span>
 usbipd: info: <span class="hljs-keyword">Using</span> default WSL distribution <span class="hljs-string">'Ubuntu-22.04'</span>; specify the <span class="hljs-string">'--distribution'</span> option to select a different one.
</code></pre>
</li>
<li><p>Go back to the WSL2 terminal and run <code>lsusb</code> to see that the ESP32 development can be accessed from WSL2 now via <code>/dev/ttyACM0</code> (or <code>/dev/ttyUSB0</code>).</p>
<pre><code class="lang-bash"> <span class="hljs-comment">### WSL2</span>
 &gt; lsusb
 Bus 001 Device 014: ID 303a:1001 Espressif USB JTAG/serial debug unit

 &gt; ls -al /dev/tty*
 ...
 crw------- 1 root root 166,  0 Aug 26 17:27 /dev/ttyACM0
 ...
</code></pre>
</li>
<li><p>Now we need to change the permission so that the device can be accessed by non-root users.</p>
<pre><code class="lang-bash"> <span class="hljs-comment">### WSL2</span>
 &gt; sudo chmod 666 /dev/ttyACM0
</code></pre>
</li>
</ol>
<p>If you remember, we added an extra field in the <code>compose-dev.yaml</code> file. With this option, we map the <code>/dev</code> directory of WSL2 into the Docker container. Hence, it is possible to access your development board from inside the container.</p>
<pre><code class="lang-yaml"><span class="hljs-string">...</span>
    <span class="hljs-attr">devices:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'/dev:/dev'</span>
<span class="hljs-string">...</span>
</code></pre>
<h3 id="heading-automating-usbipd">Automating <code>usbipd</code></h3>
<p>Attaching USB devices using <code>usbipd</code> requires manual steps that need to be executed whenever we plug or re-plug our ESP development board. It can be quite annoying over time. Luckily, there is a convenient GUI that can automate all these steps. This project was created by <a target="_blank" href="https://gitlab.com/alelec/wsl-usb-gui">Andrew Leech</a>. You can just download the package from <a target="_blank" href="https://gitlab.com/alelec/wsl-usb-gui/-/releases">here</a> and install it.</p>
<p>The GUI is quite self-explanatory. You just need to find your ESP development board and click on 'Auto-Attach'. With this feature enabled, your ESP development board will be attached automatically.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693085038991/c4cdbc8b-a603-4384-9a05-da19a0f01d05.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-udev">udev</h3>
<p>On WSL2, we have to configure the attached USB device so that non-root users can access it. We can use the <code>chmod</code> command to configure it manually, but then we have to do it every time the USB device is re-attached. To avoid all the hassles, we can create a <code>udev</code> rule.</p>
<p>Create a new file called <code>99-usbftdi.rules</code> on <code>/etc/udev/rules.d/</code> and add the following information to the file. The <code>{idVendor}</code> and <code>{idProduct}</code> values can be obtained from <code>lsusb</code></p>
<pre><code class="lang-bash">&gt; lsusb
Bus 001 Device 014: ID 303a:1001 Espressif USB JTAG/serial debug unit

&gt; cat /etc/udev/rules.d/99-usbftdi.rules
SUBSYSTEM==<span class="hljs-string">'usb'</span>, ATTRS{idVendor}==<span class="hljs-string">"303a"</span>, ATTRS{idProduct}==<span class="hljs-string">"1001"</span>, MODE=<span class="hljs-string">"0666"</span>

<span class="hljs-comment"># Then reload udevadm and restart the udev service</span>
&gt; udevadm control --reload
&gt; sudo service udev restart
</code></pre>
<p>Now, every time the development board is plugged in, it will be automatically attached to your WSL2 environment and can be readily accessed from the container.</p>
<h2 id="heading-final-result">Final Result</h2>
<p>Upon the successful creation of the development environment, the container can be opened in VSCode. The VSCode extensions, such as CMake and C++, will also be automatically installed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693086126894/669b6c3a-9aea-49af-8f32-f23fca24ddf9.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693143611446/d7c9bf25-121e-41f6-a229-86a3f3b034e9.png" alt class="image--center mx-auto" /></p>
<p>If everything is correctly configured, it is possible to build the application inside the container</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693142933595/47b3b7ff-7f13-4bc2-ba16-38f276be0a93.png" alt class="image--center mx-auto" /></p>
<p>Since the container can access the USB device via WSL2, we are also able to directly flash the image to the ESP development board.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693143700276/58d76f71-1d12-44a7-a609-6fb563eef995.png" alt class="image--center mx-auto" /></p>
<hr />
<p>In this article, we discuss creating a Docker container using the Docker Dev Environment for building ESP32 applications and flashing them directly to an ESP board from within the Docker container. This streamlined workflow simplifies the development process and ensures a consistent development environment across different systems.</p>
<p>You can see the full setup on my Github repository.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/ariesgun/esp32-docker-dev">https://github.com/ariesgun/esp32-docker-dev</a></div>
]]></content:encoded></item></channel></rss>