<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Projects on Manuel's bits and bytes</title><link>https://martin-ortiz.eu/projects/</link><description>Recent content in Projects on Manuel's bits and bytes</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Mon, 17 Aug 2026 18:11:54 +0000</lastBuildDate><atom:link href="https://martin-ortiz.eu/projects/index.xml" rel="self" type="application/rss+xml"/><item><title>Openflexure</title><link>https://martin-ortiz.eu/projects/openflexure/</link><pubDate>Mon, 17 Aug 2026 18:11:54 +0000</pubDate><guid>https://martin-ortiz.eu/projects/openflexure/</guid><media:content type="image/jpeg" medium="image" url="https://martin-ortiz.eu/img/microscope.jpg"/><description>&lt;p&gt;This year I attended &lt;a href="https://fosdem.org"&gt;FOSDEM&lt;/a&gt; in Brussels, and I fell in love with the stand where the people from &lt;a href="https://openflexure.org/"&gt;OpenFlexure&lt;/a&gt; were showing off their microscope.&lt;/p&gt;
&lt;p&gt;A few months later, with some spare time and a 3D printer in need of a project, I gathered all the required bits and bolts from &lt;a href="https://taulab.eu/3-openflexure"&gt;TLab&lt;/a&gt; and set aside a weekend for printing and assembly.&lt;/p&gt;
&lt;p&gt;The version I built is the V7 Low Resolution, using a Raspberry Pi 3 and a NoIR camera. Once assembled, everything worked out of the box, and we could examine how leaves look at a magnification of 20×.&lt;/p&gt;
&lt;p&gt;Don&amp;rsquo;t hesitate to build one yourself if you&amp;rsquo;ve always wanted to explore the world at a different scale. Leaves, water, insects — the list of everyday objects that can be observed with this microscope is endless. I uploaded a &lt;a href="https://www.printables.com/model/1813373-openflexure-microscope-v7-low-resolution"&gt;3MF project to Printables&lt;/a&gt; with all parts distributed across 3 different beds, so they can easily be printed with different colours or materials.&lt;/p&gt;
&lt;p&gt;And if 20× isn&amp;rsquo;t enough for you, you can always start with the &lt;a href="https://build.openflexure.org/openflexure-microscope/v7.0.0-beta5/high_res_microscope.html"&gt;High Resolution&lt;/a&gt;, or upgrade your low-resolution build with the required parts.&lt;/p&gt;</description></item><item><title>Matrix Server</title><link>https://martin-ortiz.eu/projects/matrix/</link><pubDate>Wed, 11 Mar 2026 08:28:28 +0000</pubDate><guid>https://martin-ortiz.eu/projects/matrix/</guid><media:content type="image/jpeg" medium="image" url="https://martin-ortiz.eu/img/matrix-logo.png"/><description>&lt;h1 id="synapse-matrix-server-configuration"&gt;Synapse Matrix Server Configuration&lt;/h1&gt;
&lt;p&gt;Recently I set up a Matrix homeserver on my personal infrastructure, using Caddy as a reverse proxy and Postgres as the database backend. There are other examples on the internet; these are just my two cents.&lt;/p&gt;
&lt;h2 id="synapse"&gt;Synapse&lt;/h2&gt;
&lt;p&gt;The Matrix server I used is Synapse. The container requires three volumes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/data&lt;/code&gt; stores runtime data, media uploads, and the signing key.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/var/lib/synapse&lt;/code&gt; provides a dedicated library path.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;homeserver.yaml&lt;/code&gt; is bind-mounted read-only from the host.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To avoid permissions errors, I set the user&amp;rsquo;s UID to the same UID that will start the service. The ports are exposed instead of published since the host does not need to use them; they only need to be available to Caddy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;docker-compose.yaml&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;synapse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;matrixdotorg/synapse:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;synapse&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unless-stopped&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;1000:1000&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;expose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;8008:8008&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;${VOLUME_PATH}/synapse/data:/data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;${VOLUME_PATH}/synapse/synapse:/var/lib/synapse&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./homeserver.yaml:/data/homeserver.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="synapse-configuration"&gt;Synapse Configuration&lt;/h3&gt;
&lt;p&gt;Server identity: the &lt;code&gt;server_name&lt;/code&gt; is the root domain, not the subdomain. This means user IDs appear as &lt;code&gt;@user:example.org&lt;/code&gt; rather than &lt;code&gt;@user:matrix.example.org&lt;/code&gt;. The &lt;code&gt;serve_server_wellknown&lt;/code&gt; option tells Synapse to handle well-known requests internally as a fallback.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;homeserver.yaml&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;server_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;example.org&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;serve_server_wellknown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The listener binds to all interfaces on port 8008 without TLS (&lt;code&gt;tls: false&lt;/code&gt;). TLS termination happens at Caddy. The bind address needs to be &lt;code&gt;0.0.0.0&lt;/code&gt; because the service will be listening on the Docker container port &lt;code&gt;8008&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;homeserver.yaml&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;bind_addresses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;0.0.0.0&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8008&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;compress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;names&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;client&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;federation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;http&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;x_forwarded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The environment variables defined here are not handled by Docker. Instead, a Python script managed by &lt;a href="https://pyinfra.com/"&gt;PyInfra&lt;/a&gt; will replace the variables with the right values before the files are pushed to the remote server.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;homeserver.yaml&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;psycopg2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${SYNAPSE_USER}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${SYNAPSE_PASSWORD}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${SYNAPSE_DB}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5432&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cp_min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cp_max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The same principle applies to other secrets.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;homeserver.yaml&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;enable_registration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;registration_shared_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;${REGISTRATION_SHARED_SECRET}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;macaroon_secret_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;${MACAROON_SECRET_KEY}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;form_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;${FORM_SECRET}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;signing_key_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/data/example.org.signing.key&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="postgresql-service"&gt;PostgreSQL Service&lt;/h2&gt;
&lt;p&gt;This was a bit trickier. I experienced some problems with collation and locales, so I pinned to a Debian-based image and wrote an init script that would set up the database for Synapse.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;docker-compose.yaml&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;postgres:18-trixie&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;synapse-postgres&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unless-stopped&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;${VOLUME_PATH}/postgres/data:/var/lib/postgresql&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./postgres-init.sh:/docker-entrypoint-initdb.d/init.sh:ro&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;.env&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;SYNAPSE_DB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;SYNAPSE_USER&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;SYNAPSE_PASSWORD&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;CMD-SHELL&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pg_isready -U synapse&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;10s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;5s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;docker-entrypoint-initdb.d/init.sh&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;psql -v &lt;span class="nv"&gt;ON_ERROR_STOP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; --username &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_USER&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; --dbname &lt;span class="s2"&gt;&amp;#34;postgres&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;&amp;lt;-EOSQL
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; CREATE USER &amp;#34;$SYNAPSE_USER&amp;#34; WITH PASSWORD &amp;#39;$SYNAPSE_PASSWORD&amp;#39;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; CREATE DATABASE &amp;#34;$SYNAPSE_DB&amp;#34; WITH OWNER=&amp;#34;$SYNAPSE_USER&amp;#34; ENCODING=&amp;#39;UTF8&amp;#39; LC_COLLATE=&amp;#39;C&amp;#39; LC_CTYPE=&amp;#39;C&amp;#39; TEMPLATE=template0;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOSQL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="caddy-service"&gt;Caddy Service&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;docker-compose.yaml&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;caddy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;caddy:2-alpine&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;synapse-caddy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unless-stopped&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;80:80&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;443:443&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./Caddyfile:/etc/caddy/Caddyfile:ro&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;${VOLUME_PATH}/caddy/data:/data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;${VOLUME_PATH}/caddy/config:/config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;.env&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;synapse&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;caddy run --config /etc/caddy/Caddyfile --adapter caddyfile&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="caddyfile-routing"&gt;Caddyfile Routing&lt;/h2&gt;
&lt;p&gt;The Caddy configuration handles two domains: the root domain for well-known delegation and the Matrix subdomain for actual traffic. The redirection is done to the Docker name instead of the host&amp;rsquo;s port.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Caddyfile&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;example.org {
 header /.well-known/matrix/* Content-Type application/json
 header /.well-known/matrix/* Access-Control-Allow-Origin *
 respond /.well-known/matrix/server `{&amp;#34;m.server&amp;#34;: &amp;#34;matrix.example.org:443&amp;#34;}`
 respond /.well-known/matrix/client `{&amp;#34;m.homeserver&amp;#34;:{&amp;#34;base_url&amp;#34;:&amp;#34;https://matrix.example.org&amp;#34;},&amp;#34;m.identity_server&amp;#34;:{&amp;#34;base_url&amp;#34;:&amp;#34;https://identity.example.org&amp;#34;}}`
}

matrix.example.org {
 reverse_proxy /_matrix/* synapse:8008
 reverse_proxy /_synapse/client/* synapse:8008
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="dns-requirements"&gt;DNS Requirements&lt;/h2&gt;
&lt;p&gt;The configuration requires DNS records that match the Caddyfile setup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An A record for &lt;code&gt;example.org&lt;/code&gt; pointing to the server IP&lt;/li&gt;
&lt;li&gt;An A record for &lt;code&gt;matrix.example.org&lt;/code&gt; pointing to the same IP&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="final-notes"&gt;Final notes&lt;/h2&gt;
&lt;p&gt;To deploy the Matrix service I used two technologies that I&amp;rsquo;m getting familiar with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform"&gt;Terraform&lt;/a&gt;, used to generate resources like a remote server on my cloud provider and the DNS entries.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pyinfra.com/"&gt;PyInfra&lt;/a&gt;, an Ansible-like tool used to provision the server with the &lt;code&gt;docker-compose.yaml&lt;/code&gt;, the configuration files, and the secrets substitution.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Hnefatafl</title><link>https://martin-ortiz.eu/projects/hnefatafl/</link><pubDate>Sun, 10 Aug 2025 18:48:15 +0000</pubDate><guid>https://martin-ortiz.eu/projects/hnefatafl/</guid><media:content type="image/jpeg" medium="image" url="https://martin-ortiz.eu/img/hnefatafl.jpg"/><description>&lt;p&gt;During my holidays, I&amp;rsquo;ve been reading &lt;em&gt;&lt;a href="https://ludotecapampala.wordpress.com/2023/01/20/breve-historia-de-los-juegos-de-mesa-nueva-edicion/"&gt;Breve Historia de los Juegos de Mesa&lt;/a&gt;&lt;/em&gt;, a gentle introduction to the origin and history of various board games. From the earliest known examples, like the &lt;a href="https://en.wikipedia.org/wiki/Royal_Game_of_Ur"&gt;Royal Game of Ur&lt;/a&gt;, to modern mechanics.&lt;/p&gt;
&lt;p&gt;The book describes an entire family of games originating in northern Europe, known as the &lt;a href="https://en.wikipedia.org/wiki/Tafl_games"&gt;Tafl&lt;/a&gt; family. These games share similar boards and rules, with variations depending on the region or time period. One such variation is &lt;a href="https://en.wikipedia.org/wiki/Tafl_games#Hnefatafl"&gt;Hnefatafl&lt;/a&gt;. The mechanics intrigued me, so I created a prototype using the most basic tools: pen and paper. After playing a few rounds with my son, I decided it deserved a wooden version.&lt;/p&gt;
&lt;p&gt;After engraving the design onto spare wood and finishing it with varnish, I crafted my own board. It may not be the most beautiful, but it’s one I made with my own hands—and a little help from my youngest.&lt;/p&gt;</description></item><item><title>3d Printed Hooks</title><link>https://martin-ortiz.eu/projects/3d-printed-hooks/</link><pubDate>Sat, 31 May 2025 12:50:24 +0000</pubDate><guid>https://martin-ortiz.eu/projects/3d-printed-hooks/</guid><media:content type="image/jpeg" medium="image" url="https://martin-ortiz.eu/img/hooks.png"/><description>&lt;p&gt;Recently I got a 3D printer and I have been experimenting with it. After a few prints for &amp;ldquo;very important things&amp;rdquo;, like a &lt;a href="https://www.printables.com/model/644028-drill-guide-and-dust-catcher"&gt;drill guide&lt;/a&gt;, a &lt;a href="https://www.printables.com/model/97000-wine-bottle-holder-for-my-new-kitchen"&gt;wine bottle holder&lt;/a&gt; or a &lt;a href="https://www.printables.com/make/2644977"&gt;Lego Extension Card&lt;/a&gt; for my Framework laptop, I thought that it was time to design something myself.&lt;/p&gt;
&lt;p&gt;Since my pegboard had still enough room for tools but I run long time ago out of hooks, I took the original hooks and &amp;ldquo;copied&amp;rdquo; them in TinkerCAD. A couple of failed prints taught me the importance of measuring properly and count with the printing tolerances.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m very happy with the results, and now my pegboard is completely filled with tools. You can download the STL files at my &lt;a href="https://www.printables.com/model/1311933-hooks-for-the-parkerside-lidl-powerfix-pegboard"&gt;Printables page&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Dev Container Template</title><link>https://martin-ortiz.eu/projects/devcontainer/</link><pubDate>Thu, 23 Jan 2025 20:38:16 +0200</pubDate><guid>https://martin-ortiz.eu/projects/devcontainer/</guid><media:content type="image/jpeg" medium="image" url="https://martin-ortiz.eu/img/tupper.png"/><description>&lt;p&gt;&lt;a href="https://containers.dev"&gt;Dev Container&lt;/a&gt; is an open specification to build and use an OCI container as development environment. It is something I already used in my &lt;a href="https://github.com/mmartinortiz/chip8emulator"&gt;Chip 8 emulator&lt;/a&gt; for having a development environment that can be reused across platforms. Lately, I&amp;rsquo;ve been looking into &lt;a href="https://containers.dev/templates"&gt;Dev Container templates&lt;/a&gt; as a way to have reusable &lt;code&gt;devcontainer.json&lt;/code&gt; files for new projects. The idea is that while a new project is setup, you can use a template as a starting point, and modify it from there for the new project needs. &lt;a href="https://github.com/mmartinortiz/devcontainer-templates/blob/main/src/python/README.md"&gt;My template&lt;/a&gt; contains those features, VS Code extensions and settings that I always set when I start a new project. Making use of it for a new project is as simple as copying the &lt;code&gt;.devcontainer&lt;/code&gt; folder of the template on your project or just use the &lt;a href="https://github.com/devcontainers/cli"&gt;devcontainer-cli&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;devcontainer templates apply --template-id ghcr.io/mmartinortiz/devcontainer-templates/python
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>Chip8 Emulator</title><link>https://martin-ortiz.eu/projects/chip8-emulator/</link><pubDate>Sat, 21 Dec 2024 20:38:16 +0200</pubDate><guid>https://martin-ortiz.eu/projects/chip8-emulator/</guid><media:content type="image/jpeg" medium="image" url="https://martin-ortiz.eu/img/chip8.png"/><description>&lt;p&gt;During the last months I&amp;rsquo;ve been developing a &lt;a href="https://en.wikipedia.org/wiki/CHIP-8"&gt;CHIP-8&lt;/a&gt; emulator in Python. My main goal was to learn how an emulator works and how to program one. That&amp;rsquo;s why I tried to write as good and comprehensive code as possible. At this point, tagged as &lt;a href="https://github.com/mmartinortiz/chip8emulator/releases/tag/v1.0.0"&gt;&lt;code&gt;v1.0.0&lt;/code&gt;&lt;/a&gt;, I consider the emulator complete, as a piece of code that fully emulates a CHIP-8, which does not mean that I will not continue developing some new features now and then.&lt;/p&gt;
&lt;p&gt;Here you can find the source code of my &lt;a href="https://github.com/mmartinortiz/chip8emulator"&gt;Chip-8 Emulator&lt;/a&gt; as well as links to the main resources I&amp;rsquo;ve used.&lt;/p&gt;</description></item><item><title>RGB Flowers</title><link>https://martin-ortiz.eu/projects/rgb-flowers/</link><pubDate>Tue, 28 May 2024 19:30:20 +0100</pubDate><guid>https://martin-ortiz.eu/projects/rgb-flowers/</guid><media:content type="image/jpeg" medium="image" url="https://martin-ortiz.eu/img/rgb-flower/rgb-flowers-1.jpg"/><description>&lt;p&gt;A beautiful and decorative pot with flowers created with my children. My contribution was some soldering and electronics for powering RGB LEDs that cycle across colors. The components used are a couple of RGB LEDs with their corresponding resistors and a Raspberry Pi Pico (my favorite microcontroller). The source code for the RGB cycle can be found on &lt;a href="https://github.com/mmartinortiz/electronics/tree/main/rgb_led"&gt;this repository&lt;/a&gt;. The project has been strongly inspired by a similar project from Adafruit.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://martin-ortiz.eu/img/rgb-flower/rgb-flowers-4.gif"
			alt="The lighting flowers"&gt;&lt;figcaption&gt;
			&lt;p&gt;The lighting flowers&lt;/p&gt;
		&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Here are other small details of the project:&lt;/p&gt;



&lt;div class="container"&gt;
 &lt;div class="gallery"&gt;
 
 &lt;a href="https://martin-ortiz.eu/img/rgb-flower/rgb-flowers-1.jpg" class="glightbox" class="gallery-item"&gt;
 &lt;img src="https://martin-ortiz.eu/img/rgb-flower/rgb-flowers-1.jpg" alt="/img/rgb-flower/rgb-flowers-1.jpg" class="img-fluid rounded mb-2" /&gt;
 &lt;/a&gt;
 
 &lt;a href="https://martin-ortiz.eu/img/rgb-flower/rgb-flowers-2.jpg" class="glightbox" class="gallery-item"&gt;
 &lt;img src="https://martin-ortiz.eu/img/rgb-flower/rgb-flowers-2.jpg" alt="/img/rgb-flower/rgb-flowers-2.jpg" class="img-fluid rounded mb-2" /&gt;
 &lt;/a&gt;
 
 &lt;a href="https://martin-ortiz.eu/img/rgb-flower/rgb-flowers-3.jpg" class="glightbox" class="gallery-item"&gt;
 &lt;img src="https://martin-ortiz.eu/img/rgb-flower/rgb-flowers-3.jpg" alt="/img/rgb-flower/rgb-flowers-3.jpg" class="img-fluid rounded mb-2" /&gt;
 &lt;/a&gt;
 
 &lt;/div&gt;
&lt;/div&gt;

&lt;style&gt;
 
 .gallery {
 column-count: 3;
 
 column-gap: 15px;
 
 }

 .gallery-item {
 display: inline-block;
 width: 100%;
 break-inside: avoid;
 
 }

 .gallery img {
 width: 100%;
 height: auto;
 transition: transform 0.3s ease;
 border-radius: 4px;
 }

 .gallery img:hover {
 transform: scale(1.05);
 }

 
 @media (max-width: 992px) {
 .gallery {
 column-count: 3;
 }
 }

 @media (max-width: 768px) {
 .gallery {
 column-count: 2;
 }
 }

 @media (max-width: 576px) {
 .gallery {
 column-count: 1;
 }
 }

 .glightbox-open {
 height: auto;
 }
&lt;/style&gt;


&lt;script&gt;
 const lightbox = GLightbox({
 selector: '.glightbox',
 scrollLock: false 
 });
&lt;/script&gt;</description></item><item><title>Greenhouse Sensor</title><link>https://martin-ortiz.eu/projects/greenhouse-sensor/</link><pubDate>Sun, 19 May 2024 11:35:16 +0200</pubDate><guid>https://martin-ortiz.eu/projects/greenhouse-sensor/</guid><media:content type="image/jpeg" medium="image" url="https://martin-ortiz.eu/img/greenhouse-sensor.jpg"/><description>&lt;p&gt;This is a little project for our greenhouse. Even if we do not really need it (and I still need to make sure that the battery will be safe), this circuit uses a Raspberry Pi Pico for monitoring temperature, humidity, gas and and atmospheric pressure. The collected information is sent every 5 minutes to my Home Assistance instance via MQTT.&lt;/p&gt;
&lt;p&gt;The Pico is powered by a rechargeable battery connected to a repurposed solar panel from the garden, but the charger module can also be connected to a phone charger. However, as the Pico goes to deep sleep when it is not running, the energy consumption is very low.&lt;/p&gt;
&lt;p&gt;The shopping list, code and instructions can be found in my &lt;a href="https://github.com/mmartinortiz/electronics/tree/main/greenhouse"&gt;electronics repository&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Table Clock</title><link>https://martin-ortiz.eu/projects/table-clock/</link><pubDate>Wed, 06 Mar 2024 20:36:30 +0100</pubDate><guid>https://martin-ortiz.eu/projects/table-clock/</guid><media:content type="image/jpeg" medium="image" url="https://martin-ortiz.eu/img/table-clock.jpeg"/><description>&lt;p&gt;Using an ESP32, a potentiometer, a temperature and humidity sensor, and a sleek 7-segment display, I crafted a functional and stylish addition to any workspace.&lt;/p&gt;
&lt;p&gt;Nestled discreetly within the IKEA frame, this clock is not just about telling time. With a twist of the potentiometer, switch between displaying the time, current temperature, and humidity on the 7-segment display. The ESP32 acts as the silent orchestrator, ensuring seamless operation and connectivity, and using your Wifi to keep the time in sync with a NTP server.&lt;/p&gt;
&lt;p&gt;Extensive instructions for building the clock within the frame can be found in my &lt;a href="https://makeprojects.com/project/table-clock"&gt;Make Project&lt;/a&gt; profile.&lt;/p&gt;</description></item><item><title>RGB Cabinet</title><link>https://martin-ortiz.eu/projects/rgb-cabinet/</link><pubDate>Sun, 25 Feb 2024 15:37:08 +0100</pubDate><guid>https://martin-ortiz.eu/projects/rgb-cabinet/</guid><media:content type="image/jpeg" medium="image" url="https://martin-ortiz.eu/img/rgb-cabinet.jpg"/><description>&lt;p&gt;What to do after creating a &lt;a href="https://martin-ortiz.eu/projects/led-matrix-games/"&gt;Space Invaders game for an RGB LED Matrix&lt;/a&gt;? Obviously, build a cabinet for RGB games. Using an online service called &lt;a href="https://www.festi.info/boxes.py/Console2?language=en"&gt;Boxes.py&amp;rsquo;s Console2&lt;/a&gt; for designing the cabinet and the &lt;a href="https://pi-top.com"&gt;PiTop-4&lt;/a&gt; to run the software, I iterated over the previous code and adapted it to the actuators provided by the PiTop: a potentiometer and a button.&lt;/p&gt;
&lt;p&gt;Code and build instructions are available in the &lt;a href="https://github.com/mmartinortiz/rgb-led-games"&gt;project&amp;rsquo;s GitHub repository&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Led Matrix Games</title><link>https://martin-ortiz.eu/projects/led-matrix-games/</link><pubDate>Sun, 11 Feb 2024 10:30:52 +0100</pubDate><guid>https://martin-ortiz.eu/projects/led-matrix-games/</guid><media:content type="image/jpeg" medium="image" url="https://martin-ortiz.eu/img/led-matrix-game.png"/><description>&lt;p&gt;Embarking on the journey to build a video game from the ground up has always been one of my ambitions. Understanding the inner workings of a video game and implementing classics have been on my to-do list since my university days.&lt;/p&gt;
&lt;p&gt;Utilizing a 64x64 LED matrix, a USB controller, and a Raspberry Pi, I programmed my first Pong and Space Invaders. Upon submitting my project to a contest on &lt;a href="https://makeprojects.com/project/led-space-invaders"&gt;Make Projects&lt;/a&gt;, I was fortunate enough to win a fantastic &lt;a href="https://www.pi-top.com/"&gt;pi-top&lt;/a&gt;. If you&amp;rsquo;re curious about the code, you can find it on my &lt;a href="https://github.com/mmartinortiz/rgb-led-matrix-space-invaders"&gt;GitHub account&lt;/a&gt;, as always.&lt;/p&gt;</description></item><item><title>Pico Speed Wheel</title><link>https://martin-ortiz.eu/projects/pico-speed-wheel/</link><pubDate>Sat, 10 Feb 2024 13:19:47 +0100</pubDate><guid>https://martin-ortiz.eu/projects/pico-speed-wheel/</guid><media:content type="image/jpeg" medium="image" url="https://martin-ortiz.eu/img/speed-wheel.png"/><description>&lt;p&gt;In the late &amp;rsquo;90s, I purchased a Genius DB15 Wheel controller for playing driving video games. Regrettably, it turned out to be a poor choice as the potentiometers used in constructing the wheel and gear produced significant noise. Moreover, the Windows driver available at that time couldn&amp;rsquo;t effectively handle the noise, resulting in a subpar driving game experience.&lt;/p&gt;
&lt;p&gt;The controller ended up stored away in a box until 2020 when it resurfaced during a cleaning day. I opted to give it a fresh start by replacing the DB15 port with a Raspberry Pi Pico and writing my own software to eliminate the noise.&lt;/p&gt;
&lt;p&gt;For a more detailed explanation and access to the code, check out my &lt;a href="https://github.com/mmartinortiz/pico-speed-wheel"&gt;GitHub repository&lt;/a&gt; and &lt;a href="https://makeprojects.com/project/reuse-your-old-db15-gamepad-thanks-to-a-raspberry-pi-pico"&gt;Make Projects&lt;/a&gt;.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
			&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/gwAEzs_7QYg?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
		&lt;/div&gt;
</description></item><item><title>Master Robots Project</title><link>https://martin-ortiz.eu/projects/master-robots-project/</link><pubDate>Sun, 07 Jan 2024 14:30:52 +0100</pubDate><guid>https://martin-ortiz.eu/projects/master-robots-project/</guid><media:content type="image/jpeg" medium="image" url="https://martin-ortiz.eu/img/epuck.jpg"/><description>&lt;p&gt;As part of my university studies, I undertook two projects centered around robots.&lt;/p&gt;
&lt;p&gt;For my Computer Science degree, I worked on a project featuring a remote-controlled robot equipped with four infrared LEDs. Additionally, I developed a fully autonomous robot with the primary objective of locating and following the first robot. The progress and details of the project development are documented in &lt;a href="https://nine-years.blogspot.com"&gt;its dedicated blog&lt;/a&gt;. You can also find videos, including one showcasing the end result, on &lt;a href="https://www.youtube.com/@mmartinortiz"&gt;my YouTube channel&lt;/a&gt;.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
			&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/2WtYktiJiII?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
		&lt;/div&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=2WtYktiJiII"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For my Master&amp;rsquo;s in Advanced Artificial Intelligence, I created a communication framework designed for robots operating in environments with unreliable communication. This framework enables autonomous agents to update their understanding of the tasks at hand as well as their knowledge of the surrounding environment.&lt;/p&gt;
&lt;p&gt;Check out the video below for an implementation featuring virtual agents using the communication framework.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
			&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/b2GWJjQmxxM?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
		&lt;/div&gt;

&lt;p&gt;Throughout the project, I also designed a virtual laser specifically tailored for real robots.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
			&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/s4hKaalIPtI?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
		&lt;/div&gt;

&lt;p&gt;Some scientific articles were authored during the development of the project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://link.springer.com/chapter/10.1007/978-3-642-21344-1_20"&gt;Study of a Multi-Robot Collaborative Task through Reinforcement Learning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://link.springer.com/chapter/10.1007/978-3-642-21344-1_19"&gt;Selective Method Based on Auctions for Map Inspection by Robotic Team&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ieeexplore.ieee.org/document/6089466"&gt;Inspection method based on multi-agent auction for graph-like maps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sciencedirect.com/science/article/abs/pii/S092188901200139X?via%3Dihub"&gt;Auction based method for graphic-like maps inspection by multi-robot system in simulated and real environments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sciencedirect.com/science/article/abs/pii/S092188901200139X"&gt;Auction based method for graphic-like maps inspection by multi-robot system in simulated and real environments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The project was implemented using the ePuck robot, and I developed a software both in &lt;a href="https://github.com/mmartinortiz/ePuckcpp"&gt;C++&lt;/a&gt; and &lt;a href="https://github.com/mmartinortiz/pyePuck"&gt;Python&lt;/a&gt;.&lt;/p&gt;</description></item></channel></rss>