[{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eOverview\u003c/h2\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/002/051/original/irc-display-bot.jpeg?1763442107","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003e\u003cspan\u003eA CircuitPython IRC bot for Metro ESP32-S3 to show status notifications on a small dedicated screen.\u003c/span\u003e\u003c/p\u003e\n\u003cp\u003eThis uses a 2.8\" TFT display shield to show the topic of an IRC channel in large text. Combined with a local Raspberry Pi hosted IRC server, you can use this to make a wireless status notification system for environmental sensors or whatever. \u003cspan\u003eOnce you put the wifi and IRC server details in your settings.toml, the display will connect automatically. Then you can use any IRC bot or client you like to set the channel topic to whatever should be shown on the display. \u003c/span\u003e\u003cspan\u003eFor example, I built this to work with my\u0026nbsp;\u003c/span\u003e\u003ca href=\"https://github.com/samblenny/serial-sensor-hub\" target=\"_blank\"\u003eserial-sensor-hub\u003c/a\u003e\u003cspan\u003e project which forwards USB serial sensor reports from my \u003c/span\u003e\u003ca href=\"https://github.com/samblenny/lora-greenhouse-monitor\" target=\"_blank\"\u003elora-greenhouse-monitor\u003c/a\u003e\u003cspan\u003e sensors.\u003c/span\u003e\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eParts\u003c/h2\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/5500","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/1651","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eSet Up Raspberry Pi OS with IRC Server\u003c/h2\u003e\n\u003cp\u003eIf you want to host your own IRC server on Raspberry Pi, this explains how. If you don't care about that, you can skip down to the Updating CircuitPython section.\u003c/p\u003e\n\u003cp\u003eFirst you need to set up a Raspberry Pi with Raspberry Pi OS. I prefer to use the Raspberry Pi OS Lite version and to enable the SSH server so I can access it remotely, but it doesn't really matter. You do you. Any vaguely recent model of Raspberry Pi should work fine as \u003ccode\u003engircd\u003c/code\u003e will run on a potato. I'm using a\u0026nbsp;Raspberry Pi 3 Model B.\u003c/p\u003e\n\u003cp\u003eOnce you have the Raspberry Pi up and running, you'll need to install and\u0026nbsp;configure the \u003ccode\u003eircd\u003c/code\u003e IRC server. Don't worry. It's easy to set up.\u003c/p\u003e\n\u003ch3\u003eOfficial Pi OS Install Method\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003eFollow the Raspberry Pi Foundation Getting Started instructions at \u003ca href=\"https://www.raspberrypi.com/documentation/computers/getting-started.html\"\u003ehttps://www.raspberrypi.com/documentation/computers/getting-started.html\u003c/a\u003e to prepare a bootable disk image for Raspberry Pi OS\u003c/li\u003e\n\u003cli\u003eContinue following the instructions to set up a user account and network connection.\u003c/li\u003e\n\u003cli\u003eEnable SSH remote terminal access: \u003ca href=\"https://www.raspberrypi.com/documentation/computers/remote-access.html#ssh\"\u003ehttps://www.raspberrypi.com/documentation/computers/remote-access.html#ssh\u003c/a\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3\u003eOld School CLI Raspberry Pi OS Install Method\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"markdown","content":"\u003cp\u003eIf like me you prefer to use macOS commandline tools (\u003ccode class=\"inline\"\u003edd ...\u003c/code\u003e) rather than the\nRaspberry Pi Imager app, you might find it useful to know that the macOS Finder\ncan expand xz archives. Just open the archive file in the Finder.\u003c/p\u003e\n\n\u003cp\u003eVerify the SHA256 hash:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003eshasum -a 256 ~/Downloads/2025-10-01-raspios-trixie-arm64-lite.img.xz\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eCheck for the correct disk file number for your sd card (be careful!):\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003ediskutil list\n...\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eUnmount the drive for your SD card (e.g. assume you found it was /dev/disk5):\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003ediskutil unmountDisk /dev/disk5\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eFlash the disk image with \u003ccode class=\"inline\"\u003edd\u003c/code\u003e (DANGER! double check drive number!):\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003esudo dd if=2025-10-01-raspios-trixie-arm64-lite.img of=/dev/rdisk5 bs=1M; sync\n\u003c/pre\u003e\u003c/code\u003e","metadata":{"markdown":"If like me you prefer to use macOS commandline tools (`dd ...`) rather than the\nRaspberry Pi Imager app, you might find it useful to know that the macOS Finder\ncan expand xz archives. Just open the archive file in the Finder.\n\nVerify the SHA256 hash:\n```\nshasum -a 256 ~/Downloads/2025-10-01-raspios-trixie-arm64-lite.img.xz\n```\n\nCheck for the correct disk file number for your sd card (be careful!):\n```\ndiskutil list\n...\n```\n\nUnmount the drive for your SD card (e.g. assume you found it was /dev/disk5):\n```\ndiskutil unmountDisk /dev/disk5\n```\n\nFlash the disk image with `dd` (DANGER! double check drive number!):\n```\nsudo dd if=2025-10-01-raspios-trixie-arm64-lite.img of=/dev/rdisk5 bs=1M; sync\n```"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eCLI Config Option: raspi-config\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"markdown","content":"\u003cp\u003eIf you do things according to the official instructions you can just use the\nGUI config stuff. But, if you prefer to use the Lite OS version and CLI tools,\nYou'll need to run \u003ccode class=\"inline\"\u003esudo raspi-config\u003c/code\u003e to configure your Pi.\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003esudo raspi-config\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eIn raspi-config:\u003c/p\u003e\n\n\u003cul\u003e\n\u003cli\u003eSystem Options \u0026gt; Wireless LAN \u0026gt; set wifi country, SSID, and password\u003c/li\u003e\n\u003cli\u003eInterface Options \u0026gt; SSH \u0026gt; enable ssh\u003c/li\u003e\n\u003cli\u003eLocalisation Options \u0026gt; Locales \u0026gt; select your locale, but also select\n\u003ccode class=\"inline\"\u003een_US.UTF-8\u003c/code\u003e to avoid trouble with software that assumes \u003ccode class=\"inline\"\u003een_US\u003c/code\u003e is present\u003c/li\u003e\n\u003cli\u003eLocalisation Options \u0026gt; Timezone \u0026gt; set your timezone\u003c/li\u003e\n\u003cli\u003eAdvanced Options \u0026gt; Expand Filesystem\u003c/li\u003e\n\u003cli\u003eAdvanced Options \u0026gt; Logging \u0026gt; select Volatile (to reduce SD card wear)\u003c/li\u003e\n\u003cli\u003eAdvanced Options \u0026gt; WLAN Power Save \u0026gt; select No (improve wifi reliability)\u003c/li\u003e\n\u003c/ul\u003e\n","metadata":{"markdown":"If you do things according to the official instructions you can just use the\nGUI config stuff. But, if you prefer to use the Lite OS version and CLI tools,\nYou'll need to run `sudo raspi-config` to configure your Pi.\n\n```\nsudo raspi-config\n```\n\nIn raspi-config:\n- System Options \u0026gt; Wireless LAN \u0026gt; set wifi country, SSID, and password\n- Interface Options \u0026gt; SSH \u0026gt; enable ssh\n- Localisation Options \u0026gt; Locales \u0026gt; select your locale, but also select\n  `en_US.UTF-8` to avoid trouble with software that assumes `en_US` is present\n- Localisation Options \u0026gt; Timezone \u0026gt; set your timezone\n- Advanced Options \u0026gt; Expand Filesystem\n- Advanced Options \u0026gt; Logging \u0026gt; select Volatile (to reduce SD card wear)\n- Advanced Options \u0026gt; WLAN Power Save \u0026gt; select No (improve wifi reliability)"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eRouter Configuration for Reserved IP Address\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"markdown","content":"\u003cp\u003eRunning a local IRC server on your home network will work better if your\nRaspberry Pi has an IP address that doesn't change. For typical home wifi\nrouters that assign IP addresses with DHCP, it's usually possible to log into\nthe router admin console and configure IP address assignments. For example, my\nrouter's DHCP assigns my computer a 192.168.0.x IP address with the gateway\n192.168.0.1 and the router admin console is at \u003ccode class=\"inline\"\u003ehttp://192.168.0.1\u003c/code\u003e.\u003c/p\u003e\n\n\u003cp\u003eCAUTION: If you're unfamiliar with configuring routers, tread carefully. It's\npossible to get yourself into trouble. You might want to seek help from an\nexperienced friend.\u003c/p\u003e\n\n\u003cp\u003eWifi routers usually have a feature for assigning a static IP address to a\nconnected device. The name of the feature and the means of enabling it may vary\nby router brand and model. For example, my router has a page for Connected\nDevices, and each device has an \"EDIT\" button. If you click EDIT, it gives you\nseveral choices including an option to pick between \"DHCP\" and \"Reserved IP\".\nFor me, picking the \"Reserved IP\" option for a Raspberry Pi device causes the\nrouter to always assign it the same IP.\u003c/p\u003e\n\n\u003cp\u003eYou might need to know the MAC address of your Raspberry Pi's Wifi interface.\nYou can check that from a terminal on the Raspberry Pi with the command:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003eip address\n\u003c/pre\u003e\u003c/code\u003e","metadata":{"markdown":"Running a local IRC server on your home network will work better if your\nRaspberry Pi has an IP address that doesn't change. For typical home wifi\nrouters that assign IP addresses with DHCP, it's usually possible to log into\nthe router admin console and configure IP address assignments. For example, my\nrouter's DHCP assigns my computer a 192.168.0.x IP address with the gateway\n192.168.0.1 and the router admin console is at `http://192.168.0.1`.\n\nCAUTION: If you're unfamiliar with configuring routers, tread carefully. It's\npossible to get yourself into trouble. You might want to seek help from an\nexperienced friend.\n\nWifi routers usually have a feature for assigning a static IP address to a\nconnected device. The name of the feature and the means of enabling it may vary\nby router brand and model. For example, my router has a page for Connected\nDevices, and each device has an \"EDIT\" button. If you click EDIT, it gives you\nseveral choices including an option to pick between \"DHCP\" and \"Reserved IP\".\nFor me, picking the \"Reserved IP\" option for a Raspberry Pi device causes the\nrouter to always assign it the same IP.\n\nYou might need to know the MAC address of your Raspberry Pi's Wifi interface.\nYou can check that from a terminal on the Raspberry Pi with the command:\n```\nip address\n```"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eCheck Pi's IP Address\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"markdown","content":"\u003cp\u003eSo far, the instructions have been for a Pi with a keyboard and HDMI display.\nTo log into the Pi remotely by SSH, or to connect to its IRC server (once you\nset one up), you will need to know the Pi's IP address. To check the IP\naddress, do:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003eip address\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eLook for something like \u003ccode class=\"inline\"\u003e191.168.0.100\u003c/code\u003e. Probably the fourth number will be\nsomething other than \u003ccode class=\"inline\"\u003e100\u003c/code\u003e, but the first three might be the same.\u003c/p\u003e\n","metadata":{"markdown":"So far, the instructions have been for a Pi with a keyboard and HDMI display.\nTo log into the Pi remotely by SSH, or to connect to its IRC server (once you\nset one up), you will need to know the Pi's IP address. To check the IP\naddress, do:\n\n```\nip address\n```\n\nLook for something like `191.168.0.100`. Probably the fourth number will be\nsomething other than `100`, but the first three might be the same."}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eAdd SSH Authorized Keys\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"markdown","content":"\u003cp\u003eIt's easier to log in remotely by SSH if you generate a key pair on your normal\ncomputer and install a copy of the public key from that pair on the Pi.\u003c/p\u003e\n\n\u003cp\u003eFor example:\u003c/p\u003e\n\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eOn macOS, generate a keypair (or skip the first bit to use existing keys)\nand print a copy of the public key (\u003ccode class=\"inline\"\u003e.ssh/id_ed25519.pub\u003c/code\u003e):\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003essh-keygen -C macbook -t ed25519\ncat ~/.ssh/id_ed25519.pub\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eIf you select the option to protect your private key with a password, which\nis a good idea to do, you might want to configure SSH to store that password\nin your macOS login keychain. You can do that by editing \u003ccode class=\"inline\"\u003e~/.ssh/config\u003c/code\u003e on\nthe mac to add the following lines:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003eHost *\n  UseKeychain yes\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eOnce you do that, you will probably be prompted for the private key's\npassword once, when you first try to use it. After that first time, the\nlogin keychain should unlock the private key for you automatically.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eOn macOS, log into your Pi by SSH using password authentication using the\nIP address and username for your Pi. For example:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003essh pi@192.168.0.100\n\u003c/pre\u003e\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eOnce you're in a Pi terminal using SSH, create a \u003ccode class=\"inline\"\u003e.ssh\u003c/code\u003e directory on the PI,\nthen edit \u003ccode class=\"inline\"\u003e.ssh/authorized_keys\u003c/code\u003e and paste in your public key(s):\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003emkdir -p ~/.ssh\nchmod 700 ~/.ssh\nnano ~/.ssh/authorized_keys\n# paste your mac's public key, then exit and save the file\n\u003c/pre\u003e\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eOn macOS, start a new Terminal window and try connecting to the Pi by SSH\nusing your SSH keypair:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003essh pi@192.168.0.100\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eThis may ask you for the password to unlock your private key the first time\nout. But, after that, assuming you set up \u003ccode class=\"inline\"\u003eUseKeychain yes\u003c/code\u003e, the key should\nunlock automatically.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e[Optional] to make it even easier, you can add a line to your \u003ccode class=\"inline\"\u003e/etc/hosts\u003c/code\u003e\nfile on the mac (e.g. \u003ccode class=\"inline\"\u003e192.168.0.100  pi3b\u003c/code\u003e), then you could do something\nlike \u003ccode class=\"inline\"\u003essh pi@pi3b\u003c/code\u003e. CAUTION: Tread carefully with \u003ccode class=\"inline\"\u003esudo nano /etc/hosts\u003c/code\u003e as\nediting mistakes there can cause trouble.\u003c/p\u003e\u003c/li\u003e\n\u003c/ol\u003e\n","metadata":{"markdown":"It's easier to log in remotely by SSH if you generate a key pair on your normal\ncomputer and install a copy of the public key from that pair on the Pi.\n\nFor example:\n\n1. On macOS, generate a keypair (or skip the first bit to use existing keys)\n   and print a copy of the public key (`.ssh/id_ed25519.pub`):\n\n    ```\n    ssh-keygen -C macbook -t ed25519\n    cat ~/.ssh/id_ed25519.pub\n    ```\n\n    If you select the option to protect your private key with a password, which\n    is a good idea to do, you might want to configure SSH to store that password\n    in your macOS login keychain. You can do that by editing `~/.ssh/config` on\n    the mac to add the following lines:\n\n    ```\n    Host *\n      UseKeychain yes\n    ```\n\n    Once you do that, you will probably be prompted for the private key's\n    password once, when you first try to use it. After that first time, the\n    login keychain should unlock the private key for you automatically.\n\n2. On macOS, log into your Pi by SSH using password authentication using the\n    IP address and username for your Pi. For example:\n\n    ```\n    ssh pi@192.168.0.100\n    ```\n\n3. Once you're in a Pi terminal using SSH, create a `.ssh` directory on the PI,\n    then edit `.ssh/authorized_keys` and paste in your public key(s):\n\n    ```\n    mkdir -p ~/.ssh\n    chmod 700 ~/.ssh\n    nano ~/.ssh/authorized_keys\n    # paste your mac's public key, then exit and save the file\n    ```\n\n4. On macOS, start a new Terminal window and try connecting to the Pi by SSH\n    using your SSH keypair:\n\n    ```\n    ssh pi@192.168.0.100\n    ```\n\n    This may ask you for the password to unlock your private key the first time\n    out. But, after that, assuming you set up `UseKeychain yes`, the key should\n    unlock automatically.\n\n5. [Optional] to make it even easier, you can add a line to your `/etc/hosts`\n    file on the mac (e.g. `192.168.0.100  pi3b`), then you could do something\n    like `ssh pi@pi3b`. CAUTION: Tread carefully with `sudo nano /etc/hosts` as\n    editing mistakes there can cause trouble."}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eSet Up Pi with IRC Server (motd, ngircd, etc)\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"markdown","content":"\u003cp\u003eThis all assumes you're logged into the Pi over SSH or at its keyboard. Some of\nthese steps are optional aesthetic tweaks to modify default behaviors that I\npersonally find irritating. Maybe you don't care. In that case, feel free to\nskip those steps.\u003c/p\u003e\n\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eGet updates and install useful packages including irssi and ngircd. When you\ninstall ngircd, you will have a working IRC server with default settings,\nbound to all your IP addresses (localhost and wifi):\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003esudo apt update\nsudo apt upgrade\nsudo apt install figlet tio screen git vim irssi ngircd\n\u003c/pre\u003e\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e(Optional) Replace annoying default SSH and ngircd motd login messages with\na figlet hostname banner\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003efiglet -f small $(hostname) | sudo tee /etc/motd\nsudo sed -i 's/^uname/#uname/' /etc/update-motd.d/10-uname\nfiglet -f small \"Welcome to $(hostname)\" | sudo tee /etc/ngircd/ngircd.motd\nsudo systemctl restart ngircd\n\u003c/pre\u003e\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e(Optional) Configure the ngircd server to have a persistent \u003ccode class=\"inline\"\u003e#sensors\u003c/code\u003e\nchannel, to have an operator user, and to disable some unnecessary default\nfeatures.\u003c/p\u003e\n\n\u003cp\u003eI've found these changes to be convenient when working on bots and bringing\nup a new sensor network installation with irc-display-bot,\n\u003ca href=\"https://github.com/samblenny/serial-sensor-hub\"\u003eserial-sensor-hub\u003c/a\u003e, and a\nregular \u003ccode class=\"inline\"\u003eirssi\u003c/code\u003e chat window. Without the config changes, channel op status\ncan get weird depending on which clients connect and disconnect in what\norder. The options stuff is just to make the protocol less chatty so my\nterminal doesn't fill up with useless log messages while working on bots.\u003c/p\u003e\n\n\u003cp\u003eFirst, begin editing the ngircd config file with:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003esudo nano /etc/ngircd/ngircd.conf\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eScroll down to the end of the file, paste this stuff at the bottom, edit the\noperator name and password to whatever you like, then save the file and exit\nnano:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003e[Channel]\nName = #sensors\nModes = n\n\n[Operator]\nName = admin\nPassword = notMyRealPassword\n\n[Options]\nDNS = no\nIdent = no\nMorePrivacy = yes\nScrubCTCP = yes\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eCheck that you didn't make any typos by running:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003engircd --configtest\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eIf configtest didn't complain about anything, then restart ngircd:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003esudo systemctl restart ngircd\n\u003c/pre\u003e\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e(Optional) Make systemd services to turn off the activity and power LEDs\u003c/p\u003e\n\n\u003cp\u003eYou can turn off the Raspberry Pi LEDs by echoing \"none\" into\n\u003ccode class=\"inline\"\u003e/sys/class/leds/ACT/trigger\u003c/code\u003e and \u003ccode class=\"inline\"\u003e/sys/class/leds/PWR/trigger\u003c/code\u003e, but the\neffect doesn't persist across a reboot. To make the change permanent, you\ncan make a simple systemd service to run a shell command at boot. I like\nthis so I don't get distracted by things blinking at me in my peripheral\nvision. Maybe you like the blinkenlights though. Your choice.\u003c/p\u003e\n\n\u003cp\u003eFirst, create a new systemd service file:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003esudo nano /etc/systemd/system/disable-leds.service\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003ePaste this stuff into the file, then save it:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003e[Unit]\nDescription=Disable ACT LED\nAfter=network.target\n\n[Service]\nType=oneshot\nExecStart=/bin/sh -c 'echo none \u0026gt; /sys/class/leds/ACT/trigger; \\\n                      echo none \u0026gt; /sys/class/leds/PWR/trigger'\n\n[Install]\nWantedBy=multi-user.target\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eFinally, enable the new service:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003esudo systemctl enable disable-leds.service\n\u003c/pre\u003e\u003c/code\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n","metadata":{"markdown":"This all assumes you're logged into the Pi over SSH or at its keyboard. Some of\nthese steps are optional aesthetic tweaks to modify default behaviors that I\npersonally find irritating. Maybe you don't care. In that case, feel free to\nskip those steps.\n\n1. Get updates and install useful packages including irssi and ngircd. When you\n    install ngircd, you will have a working IRC server with default settings,\n    bound to all your IP addresses (localhost and wifi):\n\n    ```\n    sudo apt update\n    sudo apt upgrade\n    sudo apt install figlet tio screen git vim irssi ngircd\n    ```\n\n2. (Optional) Replace annoying default SSH and ngircd motd login messages with\n    a figlet hostname banner\n\n    ```\n    figlet -f small $(hostname) | sudo tee /etc/motd\n    sudo sed -i 's/^uname/#uname/' /etc/update-motd.d/10-uname\n    figlet -f small \"Welcome to $(hostname)\" | sudo tee /etc/ngircd/ngircd.motd\n    sudo systemctl restart ngircd\n    ```\n\n3. (Optional) Configure the ngircd server to have a persistent `#sensors`\n    channel, to have an operator user, and to disable some unnecessary default\n    features.\n\n    I've found these changes to be convenient when working on bots and bringing\n    up a new sensor network installation with irc-display-bot,\n    [serial-sensor-hub](https://github.com/samblenny/serial-sensor-hub), and a\n    regular `irssi` chat window. Without the config changes, channel op status\n    can get weird depending on which clients connect and disconnect in what\n    order. The options stuff is just to make the protocol less chatty so my\n    terminal doesn't fill up with useless log messages while working on bots.\n\n    First, begin editing the ngircd config file with:\n\n    ```\n    sudo nano /etc/ngircd/ngircd.conf\n    ```\n\n    Scroll down to the end of the file, paste this stuff at the bottom, edit the\n    operator name and password to whatever you like, then save the file and exit\n    nano:\n\n    ```\n    [Channel]\n    Name = #sensors\n    Modes = n\n\n    [Operator]\n    Name = admin\n    Password = notMyRealPassword\n\n    [Options]\n    DNS = no\n    Ident = no\n    MorePrivacy = yes\n    ScrubCTCP = yes\n    ```\n\n    Check that you didn't make any typos by running:\n\n    ```\n    ngircd --configtest\n    ```\n\n    If configtest didn't complain about anything, then restart ngircd:\n\n    ```\n    sudo systemctl restart ngircd\n    ```\n\n4. (Optional) Make systemd services to turn off the activity and power LEDs\n\n    You can turn off the Raspberry Pi LEDs by echoing \"none\" into\n    `/sys/class/leds/ACT/trigger` and `/sys/class/leds/PWR/trigger`, but the\n    effect doesn't persist across a reboot. To make the change permanent, you\n    can make a simple systemd service to run a shell command at boot. I like\n    this so I don't get distracted by things blinking at me in my peripheral\n    vision. Maybe you like the blinkenlights though. Your choice.\n\n    First, create a new systemd service file:\n    ```\n    sudo nano /etc/systemd/system/disable-leds.service\n    ```\n\n    Paste this stuff into the file, then save it:\n    ```\n    [Unit]\n    Description=Disable ACT LED\n    After=network.target\n\n    [Service]\n    Type=oneshot\n    ExecStart=/bin/sh -c 'echo none \u0026gt; /sys/class/leds/ACT/trigger; \\\n                          echo none \u0026gt; /sys/class/leds/PWR/trigger'\n\n    [Install]\n    WantedBy=multi-user.target\n    ```\n\n    Finally, enable the new service:\n    ```\n    sudo systemctl enable disable-leds.service\n    ```"}},{"element_type":"text","content":"\n  \n        \u003ch2\u003eUpdating CircuitPython\u003c/h2\u003e\n\u003cp\u003eAs I write this (November 14, 2025), CircuitPython 10.0.3 is the most recent build. To keep your board up to date, you can use the \"DOWNLOAD .BIN NOW\" button on the download page of circuitpython.org:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003ca href=\"https://circuitpython.org/board/adafruit_metro_esp32s3/\" target=\"_blank\"\u003eMetro ESP32-S3\u003c/a\u003e\u003cspan\u003e\u0026nbsp;\u003c/span\u003epage\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eTo install the .BIN File:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eConnect your board to a computer with a USB data cable (\u003cem\u003echarge-only cables won't work!\u003cspan\u003e\u0026nbsp;\u003c/span\u003e\u003c/em\u003e)\u003c/li\u003e\n\u003cli\u003ePress and hold the board's boot button\u003c/li\u003e\n\u003cli\u003ePress and release the reset button\u003c/li\u003e\n\u003cli\u003eRelease the boot button\u003c/li\u003e\n\u003cli\u003e\n\u003cspan\u003eFollow the instructions in the\u0026nbsp;\u003c/span\u003e\u003ca href=\"https://learn.adafruit.com/circuitpython-with-esp32-quick-start/web-serial-esptool\" target=\"_blank\"\u003eWeb Serial ESPTool\u003c/a\u003e\u003cspan\u003e\u0026nbsp;section of the \"CircuitPython on ESP32 Quick Start\" learn guide to update your board with CircuitPython (I've had the best luck with using\u0026nbsp;\u003ca href=\"https://adafruit.github.io/Adafruit_WebSerial_ESPTool/\" target=\"_blank\"\u003eAdafruit WebSerial ESPTool\u003c/a\u003e to erase the whole flash then program the .BIN file)\u003c/span\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n      \n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eCircuitPython Code\u003c/h2\u003e\n\u003cp\u003eYou can view the code at the\u003cspan\u003e \u003ca href=\"https://github.com/samblenny/irc-display-bot\"\u003esamblenny/irc-display-bot\u003c/a\u003e\u0026nbsp;\u003c/span\u003eGitHub repository. To download a zip archive project bundle with the code and all the necessary libraries, use the \"Download Project Bundle\" button:\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"button","content":"https://github.com/samblenny/irc-display-bot/releases/download/v0.2.1/irc-display-bot-03ddafc.zip","metadata":{"button_type":"primary","icon":"","target":"_blank","zip_folder":"false"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eInstall \u0026amp; Setup\u003c/h2\u003e\n\u003cp\u003eYou will need to:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eUpdate your Metro ESP32-S3 to CircuitPython 10.0.3 *BEFORE* attaching the TFT display so that you can reach the Boot button\u003c/li\u003e\n\u003cli\u003eAttach the TFT display\u003c/li\u003e\n\u003cli\u003eGet the project bundle from the release page and copy its code and library files from the 10.x directory to your board's `CIRCUITPY` drive\u003c/li\u003e\n\u003cli\u003e\n\u003cstrong\u003eMOST IMPORTANTLY:\u003c/strong\u003e\u003cspan\u003e You need to put your \u003c/span\u003e\u003cstrong\u003ewifi credentials\u003c/strong\u003e\u003cspan\u003e and \u003c/span\u003e\u003cstrong\u003eIRC server\u0026nbsp;details\u003c/strong\u003e\u003cspan\u003e in\u0026nbsp;\u003c/span\u003e\u003cstrong\u003esettings.toml\u003c/strong\u003e\u003cspan\u003e. Without filling in settings, it won't work!\u003c/span\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3\u003eExample settings.toml\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"markdown","content":"\u003cp\u003eYour settings.toml file should have the following environment variables, but\nyou'll need to edit their values to match your wifi network and IRC server.\nThe connection code is built for easy setup on a local Raspberry Pi over\nprivate wifi. So, it doesn't do TLS, and it doesn't do passwords. The port is\nhardcoded to 6667, but you can edit the code to change it. If you want to use\nthis with my serial-sensor-bot code, you can pick the channel. Just make sure\nthey're both configured to use the same one.\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003eWIFI_SSID = \"your-wifi-ssid\"\nWIFI_PASSWORD = \"your-wifi-password\"\nIRC_SERVER = \"192.168.0.200\"\nIRC_NICK = \"tftbot\"\nIRC_CHAN = \"#sensors\"\n\u003c/pre\u003e\u003c/code\u003e","metadata":{"markdown":"Your settings.toml file should have the following environment variables, but\nyou'll need to edit their values to match your wifi network and IRC server.\nThe connection code is built for easy setup on a local Raspberry Pi over\nprivate wifi. So, it doesn't do TLS, and it doesn't do passwords. The port is\nhardcoded to 6667, but you can edit the code to change it. If you want to use\nthis with my serial-sensor-bot code, you can pick the channel. Just make sure\nthey're both configured to use the same one.\n\n```\nWIFI_SSID = \"your-wifi-ssid\"\nWIFI_PASSWORD = \"your-wifi-password\"\nIRC_SERVER = \"192.168.0.200\"\nIRC_NICK = \"tftbot\"\nIRC_CHAN = \"#sensors\"\n```"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eInstall Project Bundle\u003c/h3\u003e\n\u003cp class=\"p2\"\u003e\u003cspan class=\"s1\"\u003eTo copy the project bundle files to your CIRCUITPY drive:\u003c/span\u003e\u003c/p\u003e\n\u003col class=\"ol1\"\u003e\n\u003cli class=\"li3\"\u003e\u003cspan class=\"s1\"\u003eDownload the project bundle .zip file using the \"Download Project Bundle\" button above.\u003c/span\u003e\u003c/li\u003e\n\u003cli class=\"li3\"\u003e\u003cspan class=\"s1\"\u003eExpand the zip file by opening it, or use unzip in a Terminal. The zip archive should expand to a folder. When you open the folder, it should contain a README.txt file and a CircuitPython 10.x folder.\u003c/span\u003e\u003c/li\u003e\n\u003cli class=\"li3\"\u003e\u003cspan class=\"s1\"\u003eOpen the CircuitPython 10.x folder and copy all of its contents to your CIRCUITPY drive.\u003c/span\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp class=\"p2\"\u003e\u003cspan class=\"s1\"\u003eTo learn more about copying libraries to your\u003cspan\u003e\u0026nbsp;\u003c/span\u003e\u003cstrong\u003eCIRCUITPY\u003c/strong\u003e\u003cspan\u003e\u0026nbsp;\u003c/span\u003edrive, check out the\u0026nbsp;\u003ca href=\"https://learn.adafruit.com/welcome-to-circuitpython/circuitpython-libraries\" target=\"_blank\"\u003e\u003cspan class=\"s2\"\u003eCircuitPython Libraries\u003c/span\u003e\u003c/a\u003e\u0026nbsp;section of the\u0026nbsp;\u003ca href=\"https://learn.adafruit.com/welcome-to-circuitpython\" target=\"_blank\"\u003e\u003cspan class=\"s2\"\u003eWelcome to CircuitPython!\u003c/span\u003e\u003c/a\u003e\u0026nbsp;learn guide.\u003c/span\u003e\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eSource Code\u003c/h2\u003e\n\u003ch3\u003eCIRCUITPY/code.py\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"embed","content":"https://github.com/samblenny/irc-display-bot/blob/v0.2.1/code.py"},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eCIRCUITPY/boot.py\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"embed","content":"https://github.com/samblenny/irc-display-bot/blob/v0.2.1/boot.py"},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eCIRCUITPY/sb_chardisplay.py\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"embed","content":"https://github.com/samblenny/irc-display-bot/blob/v0.2.1/sb_chardisplay.py"},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eCIRCUITPY/sb_ircbot.py\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"embed","content":"https://github.com/samblenny/irc-display-bot/blob/v0.2.1/sb_ircbot.py"}]