<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: JF Gagnon</title>
    <description>The latest articles on DEV Community by JF Gagnon (@jf_gagnon_7b21e4c9ae2551d).</description>
    <link>https://dev.to/jf_gagnon_7b21e4c9ae2551d</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3760704%2F0a320921-e52c-4033-9295-531c8602ec06.png</url>
      <title>DEV Community: JF Gagnon</title>
      <link>https://dev.to/jf_gagnon_7b21e4c9ae2551d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jf_gagnon_7b21e4c9ae2551d"/>
    <language>en</language>
    <item>
      <title>Cloudflare as Reverse proxy SSL Auth</title>
      <dc:creator>JF Gagnon</dc:creator>
      <pubDate>Sun, 08 Feb 2026 23:31:21 +0000</pubDate>
      <link>https://dev.to/jf_gagnon_7b21e4c9ae2551d/cloudflare-as-reverse-proxy-ssl-auth-56ep</link>
      <guid>https://dev.to/jf_gagnon_7b21e4c9ae2551d/cloudflare-as-reverse-proxy-ssl-auth-56ep</guid>
      <description>&lt;h1&gt;
  
  
  Introduction: why?
&lt;/h1&gt;

&lt;p&gt;As I was comparing NPM, Zoraxy and Traefik, Authentik pricing and automatic SSL management I realized this was error-prone, complex, and require maintenance. After chatting with my favorite AI, I found that Cloudflare offers everything built-in for free. This solutions is daunting at first, but when you have a good tutorial, its easy. Since SSL, Auth and Reverse Proxy are all in Cloudflare, the diagram is simply: VM ←Cloudflared tunnel →Cloudflare←Users. Please note this tutorial expect your services to be on Docker, and will be very Docker centric.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 0: What is?
&lt;/h1&gt;

&lt;p&gt;Reverse Proxy: Its a mailman of the internet. Your VM may have 20 services: a fitness tracking app, a finance app, home assistant, etc. Those services (docker container) does not give you a “fitness.yoursite.com” just by starting it. Usually they open a HTTP access to a port, and you need to do the rest of the stack. A reverse proxy is a type of software that receive web connection from users, and dispatch it to the right docker container. To do this, it needs a list: if a user seeks fitness.yoursite.com, forward it to localhost:4592, if a user wants private.yoursite.com, ask for password, but then forward it to localhost:8513. A reverse proxy can feature a SSL component, to make your site HTTPS (required these days) (otherwise you would have to get it another way), and can feature some access control (password protecting or something fancier)&lt;/p&gt;

&lt;p&gt;Docker: If you never played with Docker before, you should find other tutorials on that. Docker allows you to run anything with mobility: if you create a docker for your self-hosted fitness tracking app on your Windows machine, you can then migrate it to your new macbook, raspberry pi, or AWS VM with very minimal setup.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 1: Zero trust tunnel / cloudflared
&lt;/h1&gt;

&lt;p&gt;This tunnel will hide our VM. The VM still has access to internet (egress), but all ingress will be from that tunnel. That makes it impossible for hackers to have direct access, and Cloudflare is great at blocking hacker. You will also benefit from cache and other optimization.&lt;/p&gt;

&lt;p&gt;If docker is not in your VM, install it.&lt;/p&gt;

&lt;p&gt;Go to your Cloudflare profile and add MFA. After all, what’s the point of all of this if your account is easy to compromise.&lt;/p&gt;

&lt;p&gt;You will need a domain name. Transfer yours, or simply buy a 6–9 digit domain with “.xyz” directly on cloudflare for 90 cents.&lt;/p&gt;

&lt;p&gt;Then go in your Cloudflare, Zero Trust, Network, Connectors, Create a tunnel, Cloudflared, name, next, Docker, copy the key, remote the fluff before the key and keep it for .env below.&lt;/p&gt;

&lt;p&gt;Add your first connector application. It doesn’t exists yet, but we will enter one anyway: test.yourdomain.com on top, &lt;a href="http://host.docker.internal:3000" rel="noopener noreferrer"&gt;http://host.docker.internal:3000&lt;/a&gt; at bottom.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fixpjy3s4wuck51iwojk7.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fixpjy3s4wuck51iwojk7.webp" alt=" " width="720" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Who is host.docker.internal? Below, we add it to the docker compose file. It tells Docker to add a DNS entry in its internal DNS to containers that maps this fake domain name (not an internet domain) to the IP address of the host. When we will launch other container, publishing a port, the service can be accessed from bash on the VM (curl localhost:3000), or from the cloudflared container (host.docker.internal:3000), ergo, by the tunnel)&lt;/p&gt;

&lt;p&gt;Then create a directory with 2 files. First, “docker-compose.yaml”&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
version: '3.8'
services:
  tunnel:
    image: cloudflare/cloudflared:latest
    command: tunnel run
    restart: unless-stopped
    environment:
      - TUNNEL_TOKEN=${TUNNEL_TOKEN}
    extra_hosts:
      - "host.docker.internal:host-gateway"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, “.env”&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TUNNEL_TOKEN=Please enter here the key provided by cloudflare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In that directory, type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo docker compose up -d
sudo docker container ls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should setup your tunnel, and list all containers, which should include your tunnel.&lt;/p&gt;

&lt;p&gt;Lets try this quickly by typing this command in your VM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -p 3000:80 hashicorp/http-echo -text=Welcome
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then your website should work. Ctrl+C when your test is done. Congratulation, you now have a reverse proxy and SSL working!&lt;/p&gt;

&lt;p&gt;(Some would argue that you should create a docker network. I disagree. Some pre-made docker compose file contain a network for itself, and that makes integration of our new network harder. In addition, it allows the tunnel container to connect to any port of any service, published or not. Many containers have a postgres or other unprotected internal container that doesn’t expose any port… well now they are exposed (yes to cloudflared only. but when you give unrestricted access from one service to other service, a compromised system compromise the whole chain). Doing what I recommend is easier, and safer)&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 2: Protecting
&lt;/h1&gt;

&lt;p&gt;My stack recommends using Dockhand, which is a web Docker manager, like Portainer but free and better. That would grant admin access to strangers on the internet! So before we jump to that step, we need to discover Cloudflare Access Application.&lt;/p&gt;

&lt;p&gt;Cloudflare Connector Application: Found in Networks-&amp;gt;Connectors-&amp;gt;Your connector-&amp;gt;Published application routes. It tells Cloudflare which public hostname (test.yoursite.com) go to which internal service (like the Welcome site we made on internal port 3000)&lt;/p&gt;

&lt;p&gt;Cloudflare Access Application: Found in Access Control-&amp;gt;Applications. You will have to re-type the public hostname because it is not synced with the above. It will tell Cloudflare which hostname or partial url is private, authentication methods and their exceptions.&lt;/p&gt;

&lt;p&gt;For this step, let’s setup authentication method first. Head to Integrations, Identity Providers, Add an Identity Provider and setup some of them. The easiest is One time password. For the rest, follow carefully the steps on the right side of your screen. They are complete and up to date.&lt;/p&gt;

&lt;p&gt;If you’re using Groups with OIDC, add the extra permission as stated in the page, but I personally added those OIDC claims: acct groups ctry. I also added “Add Group Claims” in Token configuration in Azure App Registration.&lt;/p&gt;

&lt;p&gt;Then go to Access Control, Policies, and create a policy. The first one should be “Empty Bypass”. The type is Bypass and the include selector is Everyone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs9hxnqyjfyl07j49c1h3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs9hxnqyjfyl07j49c1h3.webp" alt=" " width="720" height="402"&gt;&lt;/a&gt;&lt;br&gt;
The second policy will be your regular access control.&lt;/p&gt;

&lt;p&gt;If you want to use Azure AD group (and did what I suggested), the following should works (use the group ID provided by Azure). Otherwise, you can use email or make your own policies.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyki58or9o9syre0eyhp2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyki58or9o9syre0eyhp2.webp" alt=" " width="720" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last step is to go to Access Control, Applications, and create one self-hosted for your restricted access and one for the bypass. For the restricted access, put the domain name you’re trying to secure and your normal policy you created.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsyc0fwcn1a87vnaazrap.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsyc0fwcn1a87vnaazrap.webp" alt=" " width="720" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will need one for bypass. Same thing, but add URL that the public needs access too. For tests, you can add /api/*.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft3pd0hpsa6yji1sr0o5z.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft3pd0hpsa6yji1sr0o5z.webp" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now test your site using incognito (you can reuse the docker run command from step 1 to re-create the HTTP site temporarily). You should have access to test.yoursite.com/api/ but when going to test.yoursite.com Cloudflare should ask you to authenticate. When you do, you should have see your site!&lt;/p&gt;
&lt;h1&gt;
  
  
  Step 3: Dockhand
&lt;/h1&gt;

&lt;p&gt;For this, you will have to create a Cloudflare Connector Application and a Cloudflare Access Application, as you did in the previous steps. The only difference is that the port should be different, lets say 5823. Considering this gives root access to everything to anyone on the internet with the URL, now is the time to test if the access is restricted.&lt;br&gt;
Then create a directory for dockhand, a subdirectory compose, a subdirectory data, and the file dockhand/compose/docker-compose.yaml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
  dockhand:
    image: fnsys/dockhand:latest
    container_name: dockhand
    restart: unless-stopped
    ports:
      - 5823:3000
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - FULL_PATH_TO_DATA:/app/data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(don’t forget to replace FULL_PATH_TO_DATA)&lt;/p&gt;

&lt;p&gt;Bring it up by doing “sudo docker compose up -d”. Go to the url, and you should have access to Dockhand. Go in settings and add a user/password, enable authentication, then if you want, enable SSO. Please note that SSO won’t work with an obscure error message if you don’t do the 2 other steps first. It seems unnecessary since we’re protected by Cloudflare Access, but I always recommend 2 layers of security in case we make a small mistake. Personally, I unlocked both (individually) by accident: CloudFlare Access with a URL typo, and Dockhand by messing up the volume path (created a new empty directory, thus started fresh, and Dockhand doesn’t have a password by default). Be safe.&lt;/p&gt;

&lt;p&gt;Now we will explore a bit of Dockhand. Create an environement called local with no further settings — default will be localhost (our own machine). Then go to Stack, Create, add this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3.6'
services:
  http-echo:
    image: hashicorp/http-echo:latest
    command: ["-text", "Hello, world!"]
    ports:
      - "5678:5678"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Press Create And Start. Create a Cloudflare Connector Application with &lt;a href="http://host.docker.internal:5678" rel="noopener noreferrer"&gt;http://host.docker.internal:5678&lt;/a&gt;. Test the site you created, but this time, with Dockhand.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv58ga53ou7x8s6zgc5qz.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv58ga53ou7x8s6zgc5qz.webp" alt=" " width="800" height="610"&gt;&lt;/a&gt;&lt;br&gt;
Now you have all the knowledge to deploy any self-hosted app you want! Go to &lt;a href="https://github.com/awesome-selfhosted/awesome-selfhosted" rel="noopener noreferrer"&gt;https://github.com/awesome-selfhosted/awesome-selfhosted&lt;/a&gt; and explore all the things you can do.&lt;/p&gt;
&lt;h1&gt;
  
  
  Step 4: The last firewall rule — SSH
&lt;/h1&gt;

&lt;p&gt;In this journey we created a simple stack for deploying and securing our web applications, and as much as SSH is secure, I wasn’t happy with the idea of letting SSH access open to the public. Maybe it’s just paranoia after the &lt;a href="https://arstechnica.com/security/2024/03/backdoor-found-in-widely-used-linux-utility-breaks-encrypted-ssh-connections/" rel="noopener noreferrer"&gt;famous ssh backdoor (xz)&lt;/a&gt; was found before catastrophe. After doing this step, we will be able to change the firewall settings of our VM (in AWS or Azure) to delete all ingress rules.&lt;/p&gt;

&lt;p&gt;First step is to create a Cloudflare Connector Application, with the type SSH and pointing to host.docker.internal:22.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8xioywqyvped6947btyn.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8xioywqyvped6947btyn.webp" alt=" " width="800" height="610"&gt;&lt;/a&gt;&lt;br&gt;
Then we will need to go secure it by adding a Cloudflare Access Application. (feel free to add the path to an existing application).&lt;/p&gt;

&lt;p&gt;In your linux machine, install cloudflared and add this to your ~/.ssh/config&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host MySecureSSH
    ProxyCommand cloudflared access ssh --hostname ssh.mysite.com
    User admin
    IdentityFile ~/.ssh/id_rsa
    Port 22
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, replace your identity file, username, and ssh url.&lt;/p&gt;

&lt;p&gt;In your terminal, typing “ssh MySecureSSH” should now open a browser, ask you to authenticate, then once you did, your terminal will SSH handshake with your private key and let you in.&lt;/p&gt;

&lt;p&gt;All you have to do now is to remove SSH from AWS firewall settings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyz5gcjf5haj8p60cfsj9.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyz5gcjf5haj8p60cfsj9.webp" alt=" " width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’re scared of losing access to your machine don’t worry — when you mess up you can re-create that inbound rule, connect to your VM normally, fix everything, then delete this rule again.&lt;/p&gt;

&lt;h1&gt;
  
  
  Advantages of using Cloudflare
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;No update ever&lt;/li&gt;
&lt;li&gt;    Free*&lt;/li&gt;
&lt;li&gt;    SSL always work transparently.&lt;/li&gt;
&lt;li&gt;    Reuses Azure/Office365 user management systems — no independent tool to add/remove people.&lt;/li&gt;
&lt;li&gt;    For staff-only services, prevents unauthenticated security issue that the service might have.&lt;/li&gt;
&lt;li&gt;    For public access services, if there’s a known vulnerability, Cloudflare blocks the http request that contains that vulnerability&lt;/li&gt;
&lt;li&gt;    Caching &amp;amp; other optimization&lt;/li&gt;
&lt;li&gt;    Attackers is unable to identify where the server is (real IP) and thus, cannot try to find unprotected service or bypass Cloudflare’s security&lt;/li&gt;
&lt;li&gt;    The Cloudflared container itself has very little access, so even if someone found a way in, they wouldn’t go much further.&lt;/li&gt;
&lt;li&gt;    Less compute load than having Traefik + GoAuthentik&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Disadvantages
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;    If Cloudflare go down, you go down too.&lt;/li&gt;
&lt;li&gt;    Cannot easily migrate to other vendors.&lt;/li&gt;
&lt;li&gt;    No video streaming or high throughput&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Well, that was a very long article. I hope it helped somebody. Have a wonderful day.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>networking</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>BEES 🐝 Btrfs dedupe tutorial</title>
      <dc:creator>JF Gagnon</dc:creator>
      <pubDate>Sun, 08 Feb 2026 22:50:13 +0000</pubDate>
      <link>https://dev.to/jf_gagnon_7b21e4c9ae2551d/bees-btrfs-dedupe-tutorial-nj2</link>
      <guid>https://dev.to/jf_gagnon_7b21e4c9ae2551d/bees-btrfs-dedupe-tutorial-nj2</guid>
      <description>&lt;p&gt;On ubuntu 24.04, download latest release from &lt;a href="https://github.com/Zygo/bees/tags" rel="noopener noreferrer"&gt;https://github.com/Zygo/bees/tags&lt;/a&gt;&lt;br&gt;
Then install prerequites: sudo apt -y install build-essential btrfs-progs markdown&lt;/p&gt;

&lt;p&gt;Then unzip and go in that directory and “make” then “make install”&lt;/p&gt;

&lt;p&gt;Find your UUID for BTRFS: sudo btrfs fi show (PATH-HERE)&lt;br&gt;
(might be useless) cp /etc/bees/beesd.conf.sample /etc/bees/beesd.conf&lt;/p&gt;

&lt;p&gt;(might be useless) nano /etc/bees/beesd.conf (and put your UUID there)&lt;/p&gt;

&lt;p&gt;sudo systemctl enable beesd@(your BTRFS UUID here).service&lt;/p&gt;

&lt;p&gt;sudo systemctl start beesd@(your BTRFS UUID here).service&lt;/p&gt;

&lt;p&gt;sudo systemctl status beesd@(your BTRFS UUID here).service&lt;/p&gt;

&lt;p&gt;Happy de-duping&lt;/p&gt;

</description>
      <category>linux</category>
      <category>tooling</category>
      <category>tutorial</category>
      <category>ubuntu</category>
    </item>
    <item>
      <title>Upgrading OpenWRT beyond the manufacturer’s download options</title>
      <dc:creator>JF Gagnon</dc:creator>
      <pubDate>Sun, 08 Feb 2026 22:45:33 +0000</pubDate>
      <link>https://dev.to/jf_gagnon_7b21e4c9ae2551d/upgrading-openwrt-beyond-the-manufacturers-download-options-29li</link>
      <guid>https://dev.to/jf_gagnon_7b21e4c9ae2551d/upgrading-openwrt-beyond-the-manufacturers-download-options-29li</guid>
      <description>&lt;p&gt;If you are like me and have a Raspberry Pi with a shield to make it a router, odds are, the manufacturer have a very old firmware for you. In my case, I have the DFRobot DFR0767&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxt199tf3j2z6av7cmrno.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxt199tf3j2z6av7cmrno.webp" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
Sadly, their instructions will only lead you to OpenWRT 21.02.3. If you download from elsewhere, the second ethernet port will not work, and you’ll have to access the shell through UART 3.3v.&lt;br&gt;
Happily, I finally found the solution &lt;a href="https://github.com/geerlingguy/raspberry-pi-pcie-devices/issues/114#issuecomment-1309443204" rel="noopener noreferrer"&gt;online&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;1.BACKUP YOUR CONFIG&lt;br&gt;
2.Open &lt;a href="https://firmware-selector.openwrt.org/" rel="noopener noreferrer"&gt;https://firmware-selector.openwrt.org/&lt;/a&gt;&lt;br&gt;
3.Select the type: “Raspberry Pi 4B/400/4CM (64bit)”&lt;br&gt;
4.Expand “Customize installed packages”&lt;br&gt;
5.For DFR0767: Add the following to the list of packages, space seperated: kmod-r8169 kmod-usb-dwc2 bcm27xx-userland&lt;br&gt;
6.For everyone else: Find the required packages (I’m sorry). One method would be to flash your manufacturer’s OpenWRT and stock OpenWRT and compare the package list.&lt;br&gt;
7.Click “Request build”&lt;br&gt;
8.Factory reset: download your ext4 image and flash the eMMC with it (the 4 blue download buttons get their link updated after the build is requested and done)&lt;br&gt;
9.Upgrade: On OpenWRT bash, wget the ext4 sysupgrade, do the cli/web command to upgrade it (sysupgrade -v /tmp/firmware_image.bin.gz)&lt;/p&gt;

&lt;p&gt;Note: Your download link should have a hash in it. If it doesn’t, it’s not your special-request’ed build.&lt;/p&gt;

&lt;p&gt;Note: Do it at your own risk, I’m not responsible, blabla.&lt;/p&gt;

&lt;p&gt;Good luck&lt;/p&gt;

&lt;p&gt;JF&lt;/p&gt;

</description>
      <category>iot</category>
      <category>linux</category>
      <category>networking</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Preventing inkjet printer clog with Home Assistant</title>
      <dc:creator>JF Gagnon</dc:creator>
      <pubDate>Sun, 08 Feb 2026 22:42:19 +0000</pubDate>
      <link>https://dev.to/jf_gagnon_7b21e4c9ae2551d/preventing-inkjet-printer-clog-with-home-assistant-3h04</link>
      <guid>https://dev.to/jf_gagnon_7b21e4c9ae2551d/preventing-inkjet-printer-clog-with-home-assistant-3h04</guid>
      <description>&lt;p&gt;After a few RMA of my great Epson ET-2800, the customer support told me it’s recommended to print at least twice a week to prevent clog. I asked if there is a settings to do that automatically. He told me no.&lt;/p&gt;

&lt;p&gt;I started using 2printer and windows task scheduling on my PC, however: it print an unnecessary page (I didn’t pay for it) and it only works when I’m at home when the event triggers. Going to work or worse, on vacation, will be a problem … Time to automate using Home Assistant!&lt;/p&gt;

&lt;p&gt;The setup was somewhat easy. First, install the CUPS addon from&lt;a href="https://github.com/MaxWinterstein/homeassistant-addons/" rel="noopener noreferrer"&gt; Max Winterstein addon repo&lt;/a&gt;. Beware, as of writing, &lt;a href="https://github.com/MaxWinterstein/homeassistant-addons/issues/167" rel="noopener noreferrer"&gt;CUPS addon only works with HAOS 9 on RPI&lt;/a&gt;. Then go to the web UI of cups and add your printer. For mine, the URL of my printer was ipps://MY_PRINTER_IP:631/ipp/print.&lt;br&gt;
Then I needed to run a python script using pycups. Home Assistant does have python script support, but it only works with built-in module. Worse, pycups need a software install next to it… So I found that the built-in AppDaemon addon would do the trick just fine! Before your start it, though, go in the addon config and add pycups in python packages, and cups-dev in system packages. Then start the AppDaemon. Then, just go to a file editor and you will find a new directory: /config/appdaemon/ . While a normal person would create a new python script in the app folder, I just edited hello.py. I’m lazy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import appdaemon.plugins.hass.hassapi as hass
import cups
import json
import tempfile
import requests
import os

class HelloWorld(hass.Hass):

  def initialize(self):
    self.log("My printing script initialize")
    self.listen_event(self.mode_event, "plz_print_purge")

  def mode_event(self, event, data, kvargs):
    self.log("Starting the print")
    self.print_purge()
    self.log("Print is done!!")

  def print_purge(self):
    cups.setServer("192.168.1.1:631") # &amp;lt;- Your CUPS server IP here!
    conn = cups.Connection(host='192.168.1.1', port=631)# &amp;lt;- Your CUPS server IP here!

    # Download the PDF file using requests
    response = requests.get("https://www.testprint.net/wp-content/uploads/2022/05/Testprint-testpage-CMYK.pdf")

    # Check if the request was successful (status code 200)
    if response.status_code == 200:
        # Create a temporary file
        with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as temp_file:
            temp_file.write(response.content)
            temp_file.flush()

            printer = conn.getDefault()

            self.log(f"Printing on {printer}")

            # Print the temporary file
            job_id = conn.printFile(printer, temp_file.name, 'Print Job', {})

            # Remove the temporary file
            os.unlink(temp_file.name)
    else:
        print("Failed to download the file.")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The IP is there two time, &lt;a href="https://github.com/OpenPrinting/pycups/issues/30" rel="noopener noreferrer"&gt;because of a bug in pycups&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Just test this script by going in developer tool of Home Assistant, and raise an event called plz_print_purge.&lt;/p&gt;

&lt;p&gt;To automate in Home Assistant, I did the following to print 3 time per week:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alias: Printing CYMK 3 times a week
description: ""
trigger:
  - platform: time
    at: "13:15:00"
condition:
  - condition: time
    weekday:
      - mon
      - wed
      - fri
action:
  - event: plz_print_purge
    event_data: {}
mode: single
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This took surprisingly a low amount of time, effort and it is quite elegant…&lt;br&gt;
Hope this helps you!!&lt;/p&gt;

</description>
      <category>automation</category>
      <category>iot</category>
      <category>sideprojects</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Home Automation’s biggest trap</title>
      <dc:creator>JF Gagnon</dc:creator>
      <pubDate>Sun, 08 Feb 2026 22:39:17 +0000</pubDate>
      <link>https://dev.to/jf_gagnon_7b21e4c9ae2551d/home-automations-biggest-trap-p79</link>
      <guid>https://dev.to/jf_gagnon_7b21e4c9ae2551d/home-automations-biggest-trap-p79</guid>
      <description>&lt;p&gt;Imagine this. You buy some Philips Hue light bulb. They disconnect consistently. You try Wiz. Very limited feature. Then you try Insteon and you fall in love. The cloud makes it effortless and everything you want is there, working together. Then you buy light switches. Then dimmer. Then smart plug. Before you know it, your whole house is Insteon and working like a charm.&lt;/p&gt;

&lt;p&gt;You wake up and your light bulb doesn’t open. Then your smart windows blind won’t respond. The app is loading but not doing anything. Turns on, Insteon declared bankrupcy and shut down all the servers your devices relied on to coordinate together. You fell for the Close Ecosystem trap.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1pt0z1v6v9xhjq60lp3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1pt0z1v6v9xhjq60lp3.webp" alt=" " width="326" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This story is real and happened. &lt;a href="https://arstechnica.com/gadgets/2022/04/insteon-finally-comes-clean-about-its-sudden-smart-home-shutdown/" rel="noopener noreferrer"&gt;Click here&lt;/a&gt;. With a twist. &lt;a href="https://www.pcmag.com/news/smart-home-company-insteon-shuts-down-servers-without-warning" rel="noopener noreferrer"&gt;“A small group of Insteon users”&lt;/a&gt; were able to gather enough money to buy Insteon and revive the servers.&lt;/p&gt;

&lt;p&gt;Philips also killed the plug on its Hub V1, which, in comparison, is way less problematic, but still.&lt;/p&gt;

&lt;p&gt;Because servers cost money and companies doesn’t charge subscription fees (would you buy a smart bulb that cost money monthly?), the only way to finance is to sell more product, but the more you sell the more expensive the servers become. It’s truly a bad plan. Although some companies understood this: Wyze Cam Plus, IFTTT, Wink …&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 1: Home Assistant
&lt;/h2&gt;

&lt;p&gt;The first step is to make your own local Home Assistant server. For as long as it’s there, nobody can mess with it.&lt;br&gt;
Home Assistant is a coordinator/hub that grew and grew. It allow you to connect almost every smart home thing you can buy from any brand and connect it together. A GE Wi-Fi light switch that open a Philips Hue Zigbee light bulb, or a Z-Wave motion sensor that sends the ON command to the TV through infrared, whatever your heart can dream of it can do. It looks complex and harsh, but as long as you take small steps, you’ll be fine and learn as you go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 2: Local Push
&lt;/h2&gt;

&lt;p&gt;The second step if to pick your device carefully.&lt;/p&gt;

&lt;p&gt;Every Home Assistant integration list local/cloud push/poll. Additionally, it may have cloud features. Let’s dissect it.&lt;/p&gt;

&lt;p&gt;Let’s use Nest integration as an example. &lt;a href="https://www.home-assistant.io/integrations/nest" rel="noopener noreferrer"&gt;Click here,&lt;/a&gt; you should see this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F53zud6xbl3a9rbfwfcja.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F53zud6xbl3a9rbfwfcja.webp" alt=" " width="360" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The more it’s used, the more likely it will be fixed fast if something breaks.&lt;/li&gt;
&lt;li&gt;The quality scales show how much the integration is sturdy and works fine.&lt;/li&gt;
&lt;li&gt;Cloud/Local: Indicate whether this integration directly talks to your light bulb over your local network, or if it connects to the manufacturer’s website to send commends. This has two implication. First, local is way faster. Second, it is likely that if the manufacturer close their door, your device will still work.&lt;/li&gt;
&lt;li&gt;Push/Pull: Push means the device will inform Home Assistant of any changes so it can change the status of the light in its user interface. Pull means Home Assistant needs to ask every minute or so “Anything new for me?”, which means there will be a delay if the status change, unless Home Assistant is responsible for this change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In an ideal world, always use local push. Bonus point for high % usage and quality scale.&lt;/p&gt;

&lt;p&gt;Additionally, consider cloud features because it can breaks my promises.&lt;/p&gt;

&lt;p&gt;Locally controllable devices may still need the cloud to work. To test this, remove power to the device, keep the Wi-Fi on but break the internet connection, then turn on the device. If it works fine, then good! It will likely survive a bankruptcy. One last thing, if it has cloud enabled features, it has likely cloud over-the-air updates. The manufacturer can introduce issues by a buggy update. My Wiz light bulb used to toggle just fine, but now it goes from Normal/100% ➡ Off ➡ Last effect used. Spotcast was broken for a few day because Spotify changed something. And please don’t get me started on Wyze.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 3: Local Cloudless
&lt;/h2&gt;

&lt;p&gt;It is not very common, but there are cloud-less solutions:&lt;/p&gt;

&lt;p&gt;Zigbee, Z-Wave, RF: may have issues with stuff like Power-On behaviour and other things. However, if you manage to get it to work directly with Home Assistant to your liking, it will work forever.&lt;/p&gt;

&lt;p&gt;ESPHome: if you’re a beginner, just plug in an &lt;a href="https://www.athom.tech/" rel="noopener noreferrer"&gt;Athom&lt;/a&gt; product. It will work out of the box and does not have any cloud, therefore will work forever. If you’re a bit more advanced, you can flash ESPHome on non-ESPHome devices, make your own devices or edit the ESPHome configuration to change anything you want.&lt;/p&gt;

&lt;p&gt;In a home with only local cloudless devices and Home Assistant, the world can burn and your smart home will continue to work just fine. Congratulation, you are now immune to the Close Ecosystem traps.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Home Automation: Choosing Wireless Protocol</title>
      <dc:creator>JF Gagnon</dc:creator>
      <pubDate>Sun, 08 Feb 2026 22:34:49 +0000</pubDate>
      <link>https://dev.to/jf_gagnon_7b21e4c9ae2551d/home-automation-choosing-wireless-protocol-5h8a</link>
      <guid>https://dev.to/jf_gagnon_7b21e4c9ae2551d/home-automation-choosing-wireless-protocol-5h8a</guid>
      <description>&lt;p&gt;I’m somewhat a professional in wireless, but I also wanna make it short.&lt;br&gt;
Home Assistant gives you the luxury to pick your devices from any vendor that uses any wireless technologies. So before you pick between Wiz’s Wi-Fi, Philip’s Zigbee or Jasco’s Z-Wave, read this!&lt;/p&gt;

&lt;p&gt;Without talking OSI layers, communication works in 3 stages: Frequencies, Protocol and Format. OK, I made up these term, please don’t sue me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequencies
&lt;/h2&gt;

&lt;p&gt;Here’s the frequencies your devices can use: 315MHz, 433MHz, 815MHz, 2.4 GHz, 5GHz. The protocol will dictate which frequency you will be using. This is important because if you have unreliable Zigbee connection, maybe switching to Bluetooth will not help you.&lt;br&gt;
Frequencies are like walkie talkie. After many active users on the same frequencies, it becomes hard to find a silence to talk. Your neighbor and companies may use these channels, so you have no control over that. Keep in mind most technologies are fine co-existing on the same frequencies, as long as it’s not too crowded.&lt;/p&gt;

&lt;p&gt;Lower frequency: Higher range, Worse wall penetration.&lt;/p&gt;

&lt;p&gt;Higher frequency: Lower range, better wall penetration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protocol
&lt;/h2&gt;

&lt;p&gt;Zigbee: Operates in 2.4GHz. All channels overlap a with Wi-Fi, some of them less. Forces the Zigbee Format. Some Zigbee devices acts as a relay, usually battery-less devices. Quite Secure. Good for battery.&lt;/p&gt;

&lt;p&gt;Z-Wave: Operate in 815MHz. Only two channels but very few devices uses these frequencies. Also forces Z-Wave format. Some Z-Wave devices acts as a relay. Secure. Good for battery.&lt;/p&gt;

&lt;p&gt;Wi-Fi: Operates in 2.4GHz, 5GHz. You already have a router/AP, so no need for a hub. Many format (all based on IP but still). No relay, but multi-access-points Wi-Fi system exists. Quite secure. Terrible for batteries.&lt;/p&gt;

&lt;p&gt;RF: Operates in 315 or 433 MHz. One channel. Technically has many format. No relay. Not secure. Good for batteries. High range, low wall penetration.&lt;/p&gt;

&lt;p&gt;Bluetooth: Operates in all 2.4GHz channels, therefore quite resistant to interference. Many format, except for speakers/headphone. Some manufacturer made Bluetooth relays. Secure. Good for batteries.&lt;/p&gt;

&lt;p&gt;Obscure: Some devices, like Wyze sensors uses their own RF technologies with unknown channel and technologies.&lt;/p&gt;

&lt;p&gt;Infrared: Requires line of sight. Used in TV remotes. Your neighbor cannot mess with this one! Not secure. Good for batteries.&lt;/p&gt;

&lt;p&gt;Wired: Great for applications where failure is not acceptable, such as a security system. Secure (even if unencrypted). No batteries required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Format
&lt;/h2&gt;

&lt;p&gt;Once the data is sent over the air and received, it’s still a bunch of 0 and 1. To do something with it, we must understand which 1 means what. This is important because we know a Zigbee light bulb will play nicely with your hub because it talks the same language. In contrast, Wi-Fi light bulb will speak in “IP”, but will requires HA to have an appropriate code to interact with it. There are no standard for Wi-Fi light bulb (that is, until Thread saves the day)&lt;/p&gt;

&lt;p&gt;Local Network/IP: Some devices, like Wiz, allow local devices to discover light bulb and control them directly. Some other devices, requires a Wireless-To-Network hub, but this hub is locally controllable too. All brands have their own format, requiring their own software module to interact with it (that is, until Thread saves the day)&lt;/p&gt;

&lt;p&gt;Cloud: If your vendor have a cloud and makes it accessible to you, check if Home Assistant have a module for it! All vendors have their own API (format)!&lt;/p&gt;

&lt;p&gt;Zigbee/Z-Wave: Standard protocol, although some brands may use proprietary action ID to do some things. Philips Hue “power on behaviour” requires such thing, and thus, very hard to make it work.&lt;/p&gt;

&lt;p&gt;RF: For remotes, very standard. For other applications, it is very non-standard and will requires special software module or even RF Radio receiver.&lt;/p&gt;

&lt;p&gt;Bluetooth: Unless I’m wrong, they are all non-standard, requiring a software module (or a vendor-specific hub) (that is, until Thread saves the day)&lt;/p&gt;

&lt;p&gt;Obscure: Non-Standard.&lt;/p&gt;

&lt;p&gt;Infrared: I only know of remotes, and remotes have their own codes (ie 0x813b = ON, 0x1333 = OFF). However, you can just read the code and replicate it. So in this way, very standard.&lt;/p&gt;

&lt;p&gt;Wired Relay: Why bothering with Bluetooth &amp;amp; Hubs for motion sensor when you can use very reliable wired motion sensor? Oh yeah I remember why. There are no devices that does general input for such devices. I will make a post about how to DIY one. Until then, at least, you can use one of the many ESPHome relay/plug to power on and off things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not buy in bulk
&lt;/h2&gt;

&lt;p&gt;Wireless if finicky. It may not work where you are. I have trouble with Bluetooth and Zigbee at my apartment, and replacing all the devices I bought was painful.&lt;/p&gt;

&lt;p&gt;Instead, buy a few, install the hub, relays and motion sensors at expected distances. Then monitor it. Does the connection drop? Does the motion sensor trigger when nobody’s home? Is the battery drained in 2 weeks? Once you thoroughly tested, then you should consider expending!&lt;/p&gt;

&lt;h2&gt;
  
  
  Winners
&lt;/h2&gt;

&lt;p&gt;🥇 If you can, locally controllable wired devices will offer reliability and security.&lt;/p&gt;

&lt;p&gt;🥈 If wireless if mandatory, Z-Wave is expensive but seems to be very reliable. The frequency is quite isolated from other tech and the protocol seems fast.&lt;/p&gt;

&lt;p&gt;🥉 My third choice go to ESPHome over Wi-Fi. If my Wi-Fi is unreliable, I will want to fix that reguardless. ESPHome is local only (secure), static (vendor cannot push changes) and hackable (advanced users can do whatever). You can go on esphome-devices if you have the skills. If you are a beginner, simply order ESPHome Athom devices. All you need to do is to connect to it, enter your home Wi-Fi credentials, then Home Assistant will be able to detect it and interact with it. I am not paid by them, just a big fan.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>iot</category>
      <category>networking</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>NSPanel ESPHome: Unlimited actions</title>
      <dc:creator>JF Gagnon</dc:creator>
      <pubDate>Sun, 08 Feb 2026 22:32:23 +0000</pubDate>
      <link>https://dev.to/jf_gagnon_7b21e4c9ae2551d/nspanel-esphome-unlimited-actions-1021</link>
      <guid>https://dev.to/jf_gagnon_7b21e4c9ae2551d/nspanel-esphome-unlimited-actions-1021</guid>
      <description>&lt;p&gt;My cheap and great NSPanel had an issue. Flashing with ESPHome allowed me to make my own UI and program everything I want. So I would make a button on Nextion Editor, and tell ESPHome what to do when pressed. That required flashing both ESPHome and Nextion.&lt;/p&gt;

&lt;p&gt;However I ran out of space. It would compile and tell me I have plenty of flash/ram but the device would be in a bootloop, requiring me to take out the device from my wall. I raised an issue but I couldn’t find a way to fix it myself. So I came up with this workaround…&lt;/p&gt;

&lt;h2&gt;
  
  
  GPSig: General Purpose Signal
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc3sqr81as4qwugik09br.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc3sqr81as4qwugik09br.webp" alt=" " width="642" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The idea is simple: All buttons will change the variable “gpsig”. On RAW change, this would call the HA script GPSig with the action ID and all arguments/variables stored also on ESPHome. Best thing is we dont even need to edit ESPHome to add new action: flash Nextion and add new action id in Home Assistant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nextion: Writing to ESPHome variable
&lt;/h2&gt;

&lt;p&gt;Truthfully, dont ask question. Simply copy and paste these two&lt;br&gt;
Text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;printh 92
prints "text_sensor_name",0
printh 00
prints "new value!!",0
printh 00
printh FF FF FF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;printh 91
prints "number_sensor_name",0
printh 00
prints 123,0
printh FF FF FF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A full example of Nextion code for PIN Code input would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;printh 92
prints "argstr1",0
printh 00
prints code.txt,0
printh 00
printh FF FF FF
printh 91
prints "gpsig",0
printh 00
prints 1,0
printh FF FF FF
code.txt=""
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ESPHome: Send up Action ID and variables
&lt;/h2&gt;

&lt;p&gt;Note: If you use on_value instead of on_raw_value, pressing on the button twice will only do the action once, because changing the variable from 1 to 1 is not a “change”. on_raw_value will however trigger.&lt;/p&gt;

&lt;p&gt;Note 2: removing c_str() will send a string with a null character in the end, somehow. So doing == in Home Assistant will fail even though the UI will show you two identical strings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;text_sensor:
  - platform: nextion
    nextion_id: disp1
    name: argstr1
    id: argstr1
    component_name: argstr1
    update_interval: 4s
sensor:
  - platform: nextion
    internal: true
    id: argint1
    variable_name: argint1
  - platform: nextion
    internal: true
    id: argint2
    variable_name: argint2
  - platform: nextion
    internal: true
    id: gpsig
    variable_name: gpsig
    on_raw_value:
    - homeassistant.service:
       service: script.gpsig
       data:
          actionid: !lambda return (int)(x);
          argstr1: !lambda return id(argstr1).state.c_str();
          argint1: !lambda return (int)(id(argint1).state);
          argint2: !lambda return (int)(id(argint2).state);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Home Assistant: Triggering action
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alias: gpsig
sequence:
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ actionid == \"1\" }}"
        sequence:
          - if:
              - condition: template
                value_template: "{{ argstr1 == \"1234\" }}"
            then:
              - service: script.alarm_lock_disarm
                data: {}
            else:
              - service: notify.mobile_app_nexus4
                data:
                  message: Someone tried to unlock the alarm with {{ argstr1 }}.
      - conditions:
          - condition: template
            value_template: "{{ actionid == \"2\" }}"
        sequence:
          - service: script.alarm_lock
            data: {}
    default: []
mode: single
icon: mdi:robot-industrial
fields:
  actionid:
    name: actionid
    description: actionid
    selector:
      number:
        min: 0
        max: 100000
        step: 1
  argstr1:
    name: argstr1
    description: argstr1
    selector:
      text: null
  argint1:
    name: argint1
    description: argint1
    selector:
      number:
        min: 0
        max: 100000
        step: 1
  argint2:
    name: argint2
    description: argint2
    selector:
      number:
        min: 0
        max: 100000
        step: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pros and cons
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Unlimited actions!
- Complex at first.
- New buttons requires TFT upload and gpsig script modifications. No ESPHome flashing required!
- One way only: Nextion-&amp;gt;HA
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>iot</category>
      <category>showdev</category>
      <category>tutorial</category>
      <category>ui</category>
    </item>
  </channel>
</rss>
