[{"content":"","date":"29 July 2026","externalUrl":null,"permalink":"/tags/ai/","section":"Tags","summary":"","title":"Ai","type":"tags"},{"content":"","date":"29 July 2026","externalUrl":null,"permalink":"/tags/discord/","section":"Tags","summary":"","title":"Discord","type":"tags"},{"content":"","date":"29 July 2026","externalUrl":null,"permalink":"/tags/discord.py/","section":"Tags","summary":"","title":"Discord.py","type":"tags"},{"content":"","date":"29 July 2026","externalUrl":null,"permalink":"/tags/docker/","section":"Tags","summary":"","title":"Docker","type":"tags"},{"content":"I design digital hardware and write about what I build. I am currently doing a master\u0026rsquo;s in computer engineering at USC, focused on processor architectures and hardware acceleration for machine learning.\nYou will find projects and guides here on self-hosted AI, home automation, 3D printing, health technology, or pretty much any project I\u0026rsquo;m working on.\nProjects About Contact ","date":"29 July 2026","externalUrl":null,"permalink":"/","section":"Emiliano Fernández Cervantes","summary":"I design digital hardware and write about what I build. I am currently doing a master’s in computer engineering at USC, focused on processor architectures and hardware acceleration for machine learning.\n","title":"Emiliano Fernández Cervantes","type":"page"},{"content":"You built a private AI assistant on your own hardware, you gave it your house manual, and it answers beautifully. So why is nobody at home using it?\nIf that sounds familiar, the problem is almost never the model. It is the doorway. Asking a family member to remember a URL, open a browser tab, sign in, and pick the right model from a dropdown is asking them to change their habits, and habits rarely lose. Instead of making everyone come to the assistant, put the assistant where they already are: in a chat app that is already open on their phone.\nIn my house that app is Discord, and this guide walks you through the bridge I built for it. By the end you will have a small Discord bot that takes !ask messages from a family server, forwards them to a self-hosted Open WebUI instance backed by Ollama, and posts the grounded answer back into the channel.\nMoreover, the detour is worth taking on its own merits. Along the way you will work with REST API integration, Docker networking, GPU memory budgeting, and retrieval-augmented generation, which is a genuinely transferable set of skills for a project you can finish over a weekend.\nOne assumption before we start: this guide picks up where the private AI leaves off. It is part two of this series, so if you do not have an assistant running yet, part one on deploying your private AI with Ollama builds that half of the stack, and everything below is the layer that finally makes it useful to everybody else in the house.\nNo new app, no login, no dropdown to get wrong. The model was never the hard part of this project, the doorway was. The Stack: A Deliberately Boring Bridge # The design principle behind the whole thing is that the bot should be boring. It does no AI work at all: it does not embed documents, it does not manage prompts, and it does not talk to Ollama. It only reads Discord messages and makes HTTP calls. That keeps the interesting parts (the system prompt, the knowledge base, the model choice) in a single place, where you can change them without touching one line of Python.\nFamily Discord server the interface everyone already has │ !ask Where is the water shutoff valve? ▼ Discord bot (Python, Docker) a thin bridge, no AI logic │ POST /api/chat/completions Authorization: Bearer \u0026lt;key\u0026gt; ▼ Open WebUI (Docker) system prompt + house manual knowledge base │ OLLAMA_BASE_URL ▼ Ollama (native service, GPU) the model runtime Everything except Discord itself runs in WSL2 on my desktop PC, which is where the GPU lives: an NVIDIA GeForce RTX 3070 Ti with 8 GB of VRAM, the same card from the PC I built myself. That 8 GB number will come back later, because it quietly decides which model you can run. Open WebUI and the bot are deliberately co-located in the same WSL instance with network_mode: host, so they reach each other and Ollama over localhost with no cross-host networking to debug. My home server was the obvious candidate for hosting the bot, although it has about 1.8 GB of RAM and is already busy, so keeping the three pieces together on the machine with the GPU turned out to be both simpler and faster.\nOne property is worth calling out, because it shapes the whole security story: the bot is outbound only. It dials out to Discord and out to Open WebUI, and nothing on the internet ever connects into my house to reach it. That was the deciding factor when I compared Discord against WhatsApp, whose official Cloud API expects an inbound webhook, while the unofficial libraries carry both a ban risk and a second runtime to maintain.\nStep 1: Create the Discord Bot # Head to the Discord Developer Portal and create a new application, then open the Bot tab.\nThree things matter here:\nCopy the token and keep it somewhere safe. It is a password for your bot, and it goes into a .env file that never gets committed. Enable the Message Content Intent under Privileged Gateway Intents. Without it your bot connects successfully, sits in the channel, and silently ignores every message, because Discord will not send it the text. It is the single easiest way to lose an afternoon on this project, so do it now. Invite the bot to your server with Send Messages, Read Message History, and Embed Links. Nothing more is needed, and giving a home automation bot fewer permissions is always the right instinct. Step 2: Put the Knowledge in Open WebUI, Not in the Bot # This is the decision that keeps the project small, so it is worth doing before writing any code.\nInside Open WebUI, go to Workspace → Models and create a custom model. Mine is called Family1, and it carries two things: a system prompt written in Spanish that tells the model it is a household assistant, and a knowledge collection with the house manual uploaded to it. That manual is a plain Markdown document describing the Wi-Fi setup, the smart home devices, the breaker panel, the appliances, and all the small pieces of knowledge that normally live in one person\u0026rsquo;s head.\nOpen WebUI handles the retrieval on its own. When a question arrives, it pulls the relevant chunks from the manual and hands them to the model as context, which is what makes the answers specific to your house instead of generically plausible. The important consequence is architectural: the prompt and the knowledge live in Open WebUI\u0026rsquo;s data volume, not in Ollama and not in the bot. That means you can rewrite the assistant\u0026rsquo;s personality or upload a new revision of the manual without rebuilding or restarting anything.\nThat same fact is also a warning. Everything that makes the assistant yours sits in one Docker volume, so back it up before any teardown:\ndocker run --rm -v open-webui:/data -v $PWD:/backup alpine \\ tar czf /backup/owui-data.tgz -C /data . Finally, generate the credential the bot will use: Settings → Account → API Keys → Generate new key. Copy it, because you cannot view it again later.\nStep 3: The Bridge Between Discord and the Open WebUI API # Now the code. The whole bot is one Python file with two commands, and that is not an accident. Every feature I was tempted to add turned out to belong upstream in Open WebUI instead.\nThe dependencies are minimal:\ndiscord.py\u0026gt;=2.3,\u0026lt;3 requests\u0026gt;=2.31,\u0026lt;3 python-dotenv\u0026gt;=1.0,\u0026lt;2 The core of it is a single function that posts a question and pulls the answer out of the response:\n# OpenWebUI uses the OpenAI-compatible chat completions endpoint. # Previously this was /api/chat (wrong) — the correct path is /api/chat/completions. ASK_ENDPOINT = f\u0026#34;{OPENWEBUI_URL}/api/chat/completions\u0026#34; def ask_openwebui(question: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;Send a question to OpenWebUI and return the model answer.\u0026#34;\u0026#34;\u0026#34; headers = {\u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34;} if OPENWEBUI_API_KEY: headers[\u0026#34;Authorization\u0026#34;] = f\u0026#34;Bearer {OPENWEBUI_API_KEY}\u0026#34; # No system message here on purpose: the OpenWebUI model \u0026#34;Family1\u0026#34; already # carries its own (Spanish) system prompt + the \u0026#34;Manual Casa\u0026#34; knowledge base. # Sending a second system message here competes with / overrides that prompt. payload = { \u0026#34;model\u0026#34;: OPENWEBUI_MODEL, \u0026#34;messages\u0026#34;: [ {\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: question}, ], \u0026#34;stream\u0026#34;: False, } log.info(\u0026#34;POST %s model=%s\u0026#34;, ASK_ENDPOINT, OPENWEBUI_MODEL) response = requests.post(ASK_ENDPOINT, json=payload, headers=headers, timeout=180) if not response.ok: log.error( \u0026#34;OpenWebUI returned HTTP %d: %s\u0026#34;, response.status_code, response.text[:400], ) response.raise_for_status() data = response.json() # Try a few common response shapes so the script is easier to adapt. # OpenWebUI\u0026#39;s /api/chat/completions returns the standard OpenAI shape: # {\u0026#34;choices\u0026#34;: [{\u0026#34;message\u0026#34;: {\u0026#34;content\u0026#34;: \u0026#34;...\u0026#34;}}]} if isinstance(data, dict): if \u0026#34;choices\u0026#34; in data and data[\u0026#34;choices\u0026#34;]: return data[\u0026#34;choices\u0026#34;][0][\u0026#34;message\u0026#34;][\u0026#34;content\u0026#34;].strip() if \u0026#34;message\u0026#34; in data and isinstance(data[\u0026#34;message\u0026#34;], dict): content = data[\u0026#34;message\u0026#34;].get(\u0026#34;content\u0026#34;) if content: return str(content).strip() if \u0026#34;content\u0026#34; in data and isinstance(data[\u0026#34;content\u0026#34;], str): return data[\u0026#34;content\u0026#34;].strip() return str(data) A few details in there are load-bearing, and each one came from getting it wrong first.\nUse the OpenAI-compatible path. Open WebUI exposes /api/chat/completions, not /api/chat. My first version used the latter and got nothing but errors that looked like an authentication problem.\nKeep OPENWEBUI_URL a bare base URL. The script appends the path itself, so putting a path in the variable quietly doubles it and produces a confusing 404.\nDo not send a system message. It feels natural to define the assistant\u0026rsquo;s persona in code, but doing so competes with the prompt already attached to the custom model, and the result is an assistant with two contradictory sets of instructions.\nParse defensively. Open WebUI returns the standard OpenAI shape, but checking for a couple of alternative shapes costs a handful of lines and makes the script easy to point at a different backend. Logging the status code and the first few hundred characters of a failed body costs about as little, and it is the difference between debugging from evidence and debugging from a shrug.\nThe Discord side is equally small. !ask runs inside a typing indicator, so the family can see the bot is working rather than assuming it is broken, every failure path ends in a sentence a human can read, and the answer is chunked before sending because Discord rejects messages over 2000 characters:\n@bot.command(name=\u0026#34;ask\u0026#34;) async def ask(ctx: commands.Context, *, question: str) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34;Ask the family assistant a question. Usage: !ask How do I turn on movie mode? \u0026#34;\u0026#34;\u0026#34; async with ctx.typing(): try: answer = ask_openwebui(question) except requests.RequestException as exc: status = getattr(getattr(exc, \u0026#34;response\u0026#34;, None), \u0026#34;status_code\u0026#34;, None) detail = f\u0026#34; (HTTP {status})\u0026#34; if status else \u0026#34;\u0026#34; await ctx.send(f\u0026#34;Sorry, I could not reach OpenWebUI{detail}: {exc}\u0026#34;) return except Exception as exc: # noqa: BLE001 - show a friendly error message await ctx.send(f\u0026#34;Something went wrong: {exc}\u0026#34;) return if not answer: await ctx.send(\u0026#34;I did not get an answer back.\u0026#34;) return # Discord message limit is 2000 characters. if len(answer) \u0026lt;= 2000: await ctx.send(answer) return # If the answer is long, split it into chunks. chunk_size = 1900 for start in range(0, len(answer), chunk_size): await ctx.send(answer[start : start + chunk_size]) A !ping command that replies pong rounds it out. It sounds trivial, but it answers the most common household question (\u0026ldquo;is it down, or is it just slow?\u0026rdquo;) without anyone needing to read a log.\nStep 4: Containerize and Run # The image is about as simple as a Python image gets, with the dependency layer cached separately from the source so code edits rebuild in seconds:\nFROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY family_discord_openwebui_bot.py . CMD [\u0026#34;python3\u0026#34;, \u0026#34;family_discord_openwebui_bot.py\u0026#34;] And the Compose file:\nservices: bot: build: . container_name: family-assistant-bot restart: unless-stopped env_file: .env network_mode: host network_mode: host is what lets the container reach Open WebUI at plain localhost:8080, since both live in the same WSL instance. restart: unless-stopped means the bot comes back on its own after a reboot, which matters for something the household is supposed to be able to rely on.\nYour .env holds four values:\nDISCORD_TOKEN=your_discord_bot_token OPENWEBUI_URL=http://localhost:8080 OPENWEBUI_MODEL=ollama-family1:latest OPENWEBUI_API_KEY=sk-... OPENWEBUI_MODEL deserves a second look, because it is the subtlest trap in the whole project. It must be the id of the custom model you created in Step 2, the one with the house manual attached. If you point it at the bare engine instead, everything appears to work: the bot connects, questions get answers, the answers are fluent. They are also completely ungrounded, because you have bypassed both the system prompt and the retrieval. Confirm the exact id under Workspace → Models rather than guessing, and do not copy it from my repository\u0026rsquo;s .env.example, which still shows the engine id there and is exactly the mistake this paragraph is warning you about.\nThen start it:\ndocker compose up -d docker compose logs -f bot The logs should show Logged in as ... and Bot is ready. In Discord, !ping should return pong, and !ask should return something only your own manual could have told it.\nThe Hard Parts: Four Obstacles Worth Knowing About # The bridge itself took an evening. Everything underneath it is where the real engineering went, and the four lessons below generalize far beyond this project, so they are the part I would read first if I were you.\nThe GPU that was not being used # For a while the assistant answered correctly but painfully slowly, and the logs explained why: Ollama kept falling back to library=cpu, reporting failure during GPU discovery ... failed to finish discovery before timeout after about thirty seconds, for both CUDA 12 and CUDA 13. Meanwhile nvidia-smi inside WSL happily listed the card. CUDA looked healthy and the GPU looked present, yet inference ran on the CPU.\nTracing the process revealed the actual mechanism. Ollama discovers GPUs by spawning a runner subprocess that listens on 127.0.0.1 on a random port, then connecting to it over loopback to ask what hardware exists. My WSL was configured with networkingMode=mirrored, and under mirrored mode those local process-to-process connections route through the Windows network stack and hang. The parent\u0026rsquo;s connect() sat unresolved until the timeout expired, the runner was killed, discovery \u0026ldquo;failed\u0026rdquo;, and Ollama concluded there was no GPU.\nTwo quick experiments confirmed it. A five-line Python script that listened and connected on 127.0.0.1 hung in exactly the same way, and a small PyTorch matrix multiply finished in 0.4 seconds on the GPU, which placed the fault in loopback rather than in CUDA or Ollama.\nThe fix was one line in C:\\Users\\fdeze\\.wslconfig:\n[wsl2] networkingMode=NAT localhostForwarding=true After wsl --shutdown, Ollama loaded the model at 100% GPU. As a bonus, NAT also restored my university VPN, which mirrored mode had quietly broken. The lesson I keep coming back to: when a symptom points at the exotic layer (drivers, CUDA, the GPU), verify the boring layer first. It was networking.\nChoosing a model that actually fits # My first engine was qwen3.5-tuned, a Qwen3.5 9.7B quantized to Q4_K_M with an 8192 token context. On paper it fit, and ollama ps cheerfully agreed: PROCESSOR read 100% GPU. The arithmetic is what tells the real story. That engine occupies 6.6 GB, the Windows desktop is already holding about 1.3 GB of the card before Ollama asks for anything, and 6.6 plus 1.3 lands at roughly 7.9 GB of the RTX 3070 Ti\u0026rsquo;s 8.2 GB. That is around 250 MB of headroom on a card that is 97 percent full.\nLiving at 97 percent is not a stable place to be. At rest the model was perfectly reproducible, generating at about 70 tokens per second across repeated runs. But while I was benchmarking, the very same model on the very same prompt intermittently collapsed to roughly 20 tokens per second, with prompt processing dropping from about 800 tokens per second to about 133, whenever the Windows desktop reached for VRAM and the last 250 MB were simply not there. That is the spill: the KV cache and compute buffers move to system memory and the work quietly stops being GPU work. An occasional, unpredictable slowdown is a worse property for a family service than a permanent one, because nobody can tell whether it is broken or just having a bad minute. Being a reasoning model made it worse still, because the long internal reasoning trace was precisely the part running at CPU speed.\nThe answer was not a bigger card, it was right-sizing. I built a smaller engine from a 4B base with a 4096 token context:\nFROM qwen3.5:4b PARAMETER num_ctx 4096 PARAMETER num_gpu 99 PARAMETER temperature 0.7 PARAMETER top_k 20 PARAMETER top_p 0.9 PARAMETER presence_penalty 1.5 PARAMETER repeat_penalty 1.1 ollama create qwen3.5-4b-tuned -f qwen-family-4b.Modelfile ollama ps # the acceptance test: PROCESSOR must read 100% GPU The 4B engine occupies 5.5 GB, which leaves 2.7 GB of the card unclaimed by the model and still about 1.4 GB genuinely free once the desktop takes its share. That margin is the whole point, and the numbers followed it: the 4B holds around 103 tokens per second, against the 9.7B\u0026rsquo;s 70 at its best, and it holds that rate consistently rather than sometimes. Right-sizing did not cost me speed, it bought speed.\nSo the real acceptance test is not PROCESSOR reading 100% GPU, since the oversized engine passed that too. It is 100% GPU with roughly a gigabyte of VRAM still free, and that free gigabyte is the difference between an assistant the family trusts and one they give up on. Although dropping from 9.7B to 4B sounds like a downgrade, the quality cost turned out to be small, precisely because the answers are grounded in the house manual rather than in the model\u0026rsquo;s own memorized knowledge. Retrieval let me spend my VRAM on speed instead of on trivia.\nThat argument does carry an assumption I had not tested, though: it only holds while the retrieval actually finds the right passage. I come back to that further down, because when I finally measured it, it turned out to be the weakest link in the whole stack.\nTurning off the thinking # The last obstacle was the strangest. My engine reasons by default, emitting hundreds of internal tokens even for a greeting, and combined with a large system prompt and retrieved manual chunks inside a 4096 token budget, that reasoning trace ate the answer. I did not want to leave that as an impression, so I reproduced it: the same question, the same seed, the context deliberately filled to the model\u0026rsquo;s full 4096 tokens to match the 1,547 to 2,284 tokens a real question carries after retrieval, run once with thinking off and twice with it on.\nthink wall clock output tokens reasoning answer returned false 3.7 s 114 none 420 characters, clean true 92.9 s 8,190 29,254 characters empty, 0 characters true 51.0 s 4,561 16,993 characters 464 characters That table is the whole failure in one place. With thinking on, the reasoning trace consumed the output budget before the answer ever started: the better of the two runs still took 51 seconds to deliver 464 characters, and the other spent 92.9 seconds emitting 29,254 characters of reasoning and then returned nothing at all. A blank message after a minute and a half is not a slow assistant, it is a broken one, and anyone in the house would reasonably conclude exactly that.\nOne nuance is worth stating, because it explains why this is specifically a retrieval problem: thinking is only catastrophic when the context is full. On a short unconstrained prompt the same engine merely slows from roughly 10 to 14 seconds up to 27 to 30. It is the combination of a large system prompt, retrieved chunks, and a 4096 token ceiling that turns a slowdown into an empty reply. That is why turning it off is mandatory on a RAG path rather than a nice optimization.\nThe widely suggested fix, putting /no_think in the prompt, did nothing on this build: I measured 479 reasoning tokens with the flag present. What worked was setting think: false as a parameter on the custom model in Open WebUI, under Workspace → Models → Params. It has to live on the model definition, because Open WebUI\u0026rsquo;s chat completions endpoint does not forward a request-level think field from the bot.\nOpen WebUI, Workspace → Models → Family1 → Params. This is the only screen where the setting sticks, so it is worth finding before you start editing anything else. With that in place, answers land in about four seconds. I went back and measured it properly rather than trusting the impression: nine questions sent end to end through Open WebUI against the Family1 model with the house manual attached, each one carrying between 1,547 and 2,284 prompt tokens after retrieval and producing between 75 and 601 tokens of answer. The fastest came back in 2.3 seconds, the typical one in three to six, and the slowest, which was the first request of the run, in 8.4 seconds. For a household question asked from a phone, that is indistinguishable from instant.\nCold starts used to be the frightening part. When the model still had to load and run its first retrieval, the first question of the day took 100 to 134 seconds, which is exactly why the bot\u0026rsquo;s HTTP timeout is 180 seconds rather than the 90 I started with: I was timing out on requests that were going to succeed. Those numbers no longer reproduce at all. I unloaded the Ollama engine and restarted the open-webui container to clear its caches, then timed four questions in a row, and the genuinely cold one came back in 9.8 seconds, followed by 6.2, 6.2 and 5.4 warm.\nI have kept the generous timeout anyway, because a timeout you never reach costs nothing, while one that is slightly too short costs you the exact request you most wanted to work. Every measurement in this post, along with the benchmark scripts that produce them, lives in BENCHMARKS.md in the bot\u0026rsquo;s repository, linked at the end of this post, so you can rerun them against your own hardware instead of taking my word for any of it.\nSurviving a reboot # NAT gave WSL a private IP that changes on every boot, which would break LAN access to Open WebUI. A netsh portproxy rule on Windows forwards the host\u0026rsquo;s ports 8080 and 11434 to the current WSL address, and because that address moves, a small PowerShell script refreshes the rules and opens the matching firewall entries, run by a scheduled task at logon. The Windows host IP never changes, so my reverse proxy configuration and public hostname never need touching.\nThe Weakest Link: Measuring Whether the Retrieval Works # Everything above rests on a claim I had never actually tested: that grounding the answers in the house manual is what lets a 4B model do this job. If that claim is true, retrieval is the most important component in the stack, which means it deserves a measurement of its own. So I finally gave it one, and the honest result is that it is also the weakest part of what I built.\nThe trick is to score retrieval with the model out of the room. For each of sixteen questions I asked Open WebUI for the top k chunks and checked whether the correct answer appeared verbatim in what came back. No generation, no judging of prose, just a yes or a no about whether the fact ever reached the context window at all. That separation matters, because a wrong answer from a RAG system has two completely different causes, and you cannot fix the one you have not identified.\nmanual k=3 k=5 k=8 production 11/16 12/16 12/16 embedded images stripped 12/16 12/16 12/16 Production runs with TOP_K = 3, so the number that describes my house is 11 out of 16. Roughly a third of the time the answer never reaches the model at all. When that happens the assistant is not wrong, it is uninformed, and from the outside those two failures look exactly alike.\nWhat surprised me was how little the obvious knobs moved. Raising k bought at most one question, and cleaning up the manual bought at most one. The reason is a remarkably clean split in the rankings: every question the retriever gets right, it gets right within the top four chunks, and every question it gets wrong sits at rank 10 or worse, at 15, 17, 19 and 24 on the production manual. There is nothing in between. No realistic k rescues those failures without first dragging ten chunks of noise into a 4096 token budget, which is the same context pressure that made the reasoning trace so destructive earlier.\nThe failures also have a shape, and that is what points at the fix. All four of the persistent ones are proper nouns living inside markdown tables: Wi-Fi network lookups, a hardware model number, that kind of question. This is precisely the query that keyword search handles well and dense vector embeddings handle badly, because an exact token match earns almost no extra credit in a space built to represent meaning. The lever is therefore not more chunks, it is better ordering: a cross-encoder reranker over a wider candidate list. Turning on hybrid search by itself is a no-op on my instance, since with no reranking model configured the merged candidates simply get re-scored by the same similarity metric that ranked them in the first place.\nOne more finding is worth carrying to your own knowledge base, because it costs nothing. My production manual is 836 KB, of which only about 17 KB is text. The rest is screenshots embedded as base64, which is why 698 of its 744 chunks are image noise instead of prose. Stripping them improved the rankings broadly even where the final verdict did not change. A knowledge base is not a folder you dump documents into, it is a document you maintain.\nSo the reflection I keep arriving at applies here too: retrieval is what let a small model do a big model\u0026rsquo;s job, and retrieval is also where the most headroom is left. That is worth knowing before you assume that attaching a document is the end of the work. The numbers are in section 6 of that same BENCHMARKS.md, and the scoring script is bench/bench_rag.py, so you can point it at your own knowledge base and find out what your assistant can actually see.\nWhat You Get Out of This # The everyday payoff is unglamorous, and that is exactly the point. Someone types !ask in a Discord channel they already had open, and three to six seconds later they know where the water shutoff valve is, how to get the projector back on the right input, or what the Wi-Fi password is. In my house nobody had to be taught a new tool, and nobody had to wait for me to answer, which is the closest thing to success a household project can have.\nThe engineering payoff is a stack with clean seams. The bot is stateless on purpose: each !ask sends only that one question, so follow-ups like \u0026ldquo;and how do I change it?\u0026rdquo; do not work. That is a deliberate trade rather than an oversight. Statelessness keeps the entire 4096 token context available for the system prompt and the retrieved manual chunks, and a household FAQ is overwhelmingly made of standalone questions. I will add per-channel history the day someone actually asks for it, and not before.\nFurther, the skills you pick up here transfer everywhere: REST API integration and defensive response parsing, Docker networking and container lifecycle, GPU memory budgeting, and the kind of methodical debugging that separates a symptom from a cause. Every obstacle along the way (the intent I forgot to enable, the endpoint that was wrong, the loopback that hung, the model that did not fit) turned into a piece of understanding I still carry forward. Each setback is simply another opportunity to learn from past mistakes and keep improving, and in a home lab those lessons compound faster than almost anywhere else.\nWhat\u0026rsquo;s Next # A few directions I think are worth exploring from here:\nA reranker in front of the retrieval. The failures I measured are ranking failures rather than budget failures, so the change that matches them is a cross-encoder rerank over a wider candidate list, not a larger k. It is the single item on this list I expect to move the assistant most. Conversational follow-ups. A short per-channel history in the messages array would enable natural back and forth, at the cost of context budget. Worth doing only alongside a larger context window. Slash commands. Discord\u0026rsquo;s native / commands with autocomplete would be friendlier than a ! prefix for family members who never learned the prefix convention. Write access, carefully. The assistant currently only answers. Letting it act on the smart home would be the natural next step, and it is the step that most needs guardrails: an explicit allowlist of actions, and confirmation before anything with physical consequences. Backups that actually run. The system prompt and the house manual live in a single Docker volume. A scheduled snapshot of it is the least glamorous and most valuable item on this list. The full bot is on GitHub at EmilianFC20/family-assistant. If you build your own version, I would love to hear which questions your household ends up asking it most.\n","date":"29 July 2026","externalUrl":null,"permalink":"/posts/discord-bot-private-ai-assistant/","section":"Posts","summary":"You built a private AI assistant on your own hardware, you gave it your house manual, and it answers beautifully. So why is nobody at home using it?\nIf that sounds familiar, the problem is almost never the model. It is the doorway. Asking a family member to remember a URL, open a browser tab, sign in, and pick the right model from a dropdown is asking them to change their habits, and habits rarely lose. Instead of making everyone come to the assistant, put the assistant where they already are: in a chat app that is already open on their phone.\n","title":"Give Your Private AI a Discord Bot Your Family Will Actually Use","type":"posts"},{"content":"","date":"29 de July de 2026","externalUrl":null,"permalink":"/es/series/ia-privada-en-casa/","section":"Series","summary":"","title":"IA Privada en Casa","type":"series"},{"content":"","date":"29 July 2026","externalUrl":null,"permalink":"/tags/ollama/","section":"Tags","summary":"","title":"Ollama","type":"tags"},{"content":"","date":"29 July 2026","externalUrl":null,"permalink":"/tags/open-webui/","section":"Tags","summary":"","title":"Open-Webui","type":"tags"},{"content":"","date":"29 July 2026","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"29 July 2026","externalUrl":null,"permalink":"/series/private-ai-at-home/","section":"Series","summary":"","title":"Private AI at Home","type":"series"},{"content":"Things I have built and documented end to end. Each one covers how I put it together, what I used, and what I got wrong along the way.\n","date":"29 July 2026","externalUrl":null,"permalink":"/tags/project/","section":"Tags","summary":"Things I have built and documented end to end. Each one covers how I put it together, what I used, and what I got wrong along the way.\n","title":"Projects","type":"tags"},{"content":"Cosas que he construido y documenté de principio a fin. Cada una incluye cómo la armé, con qué, y en qué me equivoqué en el camino.\n","date":"29 de July de 2026","externalUrl":null,"permalink":"/es/tags/proyecto/","section":"Tags","summary":"Cosas que he construido y documenté de principio a fin. Cada una incluye cómo la armé, con qué, y en qué me equivoqué en el camino.\n","title":"Proyectos","type":"tags"},{"content":"","date":"29 July 2026","externalUrl":null,"permalink":"/tags/rag/","section":"Tags","summary":"","title":"Rag","type":"tags"},{"content":"","date":"29 July 2026","externalUrl":null,"permalink":"/tags/self-hosted/","section":"Tags","summary":"","title":"Self-Hosted","type":"tags"},{"content":"","date":"29 July 2026","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","date":"29 July 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"29 July 2026","externalUrl":null,"permalink":"/tags/wsl/","section":"Tags","summary":"","title":"Wsl","type":"tags"},{"content":"What if you could describe a game in plain language, press enter, and watch an AI agent write and debug C code until something playable appeared in your terminal?\nYou can, and an afternoon is enough. With a CLI coding agent, a C compiler, and the ncurses library, you can build a Tetris-inspired falling-blocks game that runs natively in your terminal, in a single session. You do not need to be a C programmer to begin, and I was not one either: my own hand-written tetris.c never made it past the compiler, and that failure is where this project actually started. By the end you will have three things worth keeping: a binary you compiled yourself, a readable C codebase you can pick apart line by line, and a workflow you can reuse on the next idea you have.\nThat is exactly what this guide walks you through.\nThe finished game mid-play: the 10 by 20 playfield bordered with | and -, empty cells as dots, placed pieces as colored brackets, and the score beside the board, all drawn by ncurses. Update, August 2026. This post documents a session I ran in February 2026 with Gemini CLI. At Google I/O on May 19, 2026, Google announced that it is consolidating its developer tooling under the Antigravity brand and retiring the standalone Gemini CLI, and on June 18, 2026 Gemini CLI stopped serving requests for free users and for Google AI Pro and Ultra subscribers. Organizations on a Gemini Code Assist Standard or Enterprise license are unaffected. The successor is Antigravity CLI, and Step 1 below gives its current install command. I have left the rest of the post exactly as it happened, because what transfers to the new tool is the workflow, not the specific binary.\nA game that shaped history # In 1984, a Soviet computer scientist named Alexey Pajitnov sat down at an Electronika 60 terminal and wrote a program in Pascal. The concept was elegant: seven geometric pieces fall from the top of the screen, you rotate and place them to fill rows, and completed rows disappear. He called it Tetris.\nThat program became one of the most widely played games in history, sold across every platform imaginable for four decades. Although the idea is simple to describe, building even a working clone back then meant handling careful timing loops, terminal rendering, rotation math, and hours of patient debugging. It was real, respectable engineering work.\nToday you can prompt an AI agent to produce equivalent C code in a single session. That contrast is the actual subject of this project: forty years of engineering progress, compressed into one conversation with an agent.\nYou are allowed to build this. The mechanics of falling-block games (the piece shapes, the gravity, the line-clear logic) are not copyrightable. What makes Tetris Tetris as a brand (the trademarked name, the logo, the Korobeiniki music) belongs to The Tetris Company. What you are building here is an independent, Tetris-inspired clone, and the educational and engineering value is identical.\nWhere this project actually started # Before any of this was a guide, it was a file that would not compile.\nI was learning C at the time, so I wrote my own tetris.c by hand. It did not build, and after a while of staring at the errors I still could not see why. My first instinct was not to delete it and let an agent start over, it was to ask for a diagnosis, so I opened Gemini CLI in that directory and typed exactly this:\nhi, I was trying to do the tetris game but coul not compile it. Could you please check the code for any errors? @tetris.c Typos and all. The @tetris.c at the end is how Gemini CLI pulls a file into the conversation, so the agent read my actual code rather than a description of it.\nThe review was honest, and it was not the answer I was hoping for: the file was a mess. I agreed with that assessment in my very next message. Fourteen minutes after asking for a code review, I deleted my own attempt and asked the agent to start from zero.\nThat decision is worth naming, because it is a judgment call engineers make constantly and rarely talk about: knowing when to keep repairing a draft and when a clean rewrite is simply the faster path. If you are starting this project with a broken attempt of your own, you are not behind. You are exactly where I was.\nWhat you will need # A Linux terminal or WSL2 on Windows 11 GCC (the C compiler): sudo apt install gcc The ncurses development library: sudo apt install libncurses-dev A CLI coding agent: Antigravity CLI today, Gemini CLI in the session this post describes An account to authenticate the agent on first launch (Gemini CLI asked for a free Google account; follow whatever sign-in flow your tool prompts for) ncurses is what makes a terminal behave like a screen instead of a scrolling log. It gives you cursor positioning, keyboard input without waiting for the enter key, and color pairs, which is everything a falling-blocks game needs and nothing more.\nIf you are setting up WSL2 for the first time, the first steps of my post on deploying a private AI with Ollama cover the whole installation process.\nStep 1: Install a CLI coding agent # The current tool is Antigravity CLI, Google\u0026rsquo;s successor to Gemini CLI. It is a rewrite in Go, and it can run several agents in parallel in the background. It installs from a single script.\nOn macOS, Linux, or WSL:\ncurl -fsSL https://antigravity.google/cli/install.sh | bash On Windows, from PowerShell:\nirm https://antigravity.google/cli/install.ps1 | iex Then follow the tool\u0026rsquo;s own first-run instructions to authenticate.\nFor the record, and not as a step to follow, the agent I used in February 2026 was Gemini CLI, running on Gemini 2.5 Pro. That session\u0026rsquo;s own log does not record a model, but every Gemini CLI session on my machine from that period ran on Gemini 2.5, and four other sessions from that same day logged 2.5 Pro. The CLI did occasionally fall back to Gemini 2.5 Flash back then, so take that as the model the tool was serving me at the time rather than a field I can point at in the record.\nGemini CLI shipped as an npm package and needed Node.js 18 or later:\nnpm install -g @google/gemini-cli # retired on June 18, 2026 gemini Signing in with a free Google account was all it took back then. That is the path that stopped working, so if you have an older tutorial open in another tab, this is why nothing comes back.\nEither way, what you land in is an interactive terminal session with the agent. It works like a chat interface with hands: you describe what you want, and the agent reasons, writes code, and runs commands on your behalf.\nStep 2: Ask for the game # Here is the prompt that produced the entire game. It is one sentence, and I am quoting it exactly as I typed it:\nthank you but you are right, this code was a mess. Could you create the tetris game in c instead? I deleted the previous file so please start over That is the whole specification. I did not name a library, a board size, a control scheme, a scoring rule, or a compile command. What came back was a single C file of 234 lines, written and compiled by the agent itself, and every design decision in the list below belongs to the agent rather than to me:\nncurses as the rendering layer, a single file with #include \u0026lt;ncurses.h\u0026gt;, linked with -lncurses. A 10 by 20 playfield (BOARD_WIDTH 10, BOARD_HEIGHT 20) with borders drawn from | and -. All seven tetrominoes with four rotation states each, held in one const int PIECES[7][4][4][4] lookup table instead of computed rotation math. Arrow-key controls: left and right to move, up to rotate clockwise through (rotation + 1) % 4, down to soft-drop, plus q or Q to quit. Seven init_pair() color pairs, one per piece: cyan, yellow, magenta, green, red, blue, and white. Gravity on a fixed interval: the main loop sleeps 20 ms per pass and drops the piece once its counter passes 20, so roughly 400 ms per row, and it never accelerates. Quadratic scoring: score += lines_cleared * lines_cleared * 100, so four rows cleared in one placement are worth 4 × 4 × 100 = 1,600 points, against the 4 × 100 = 400 you would collect clearing them one at a time. Those are good defaults, and that is the genuinely interesting part. ncurses is the natural choice for a terminal game, 10 by 20 is the standard playfield, arrow keys are what a player will reach for first, and rewarding multi-line clears is what gives the game its risk-and-reward tension. A one-line request landed on conventions it would have taken me a while to research.\nThe trade-off still runs in one direction, though: whatever you leave unsaid, the agent decides for you, and it decides quietly. My own game is the proof. Gravity never speeds up, so there is no difficulty curve, and there is no next-piece preview and no hold slot. None of that is a bug. They are simply features I never asked for.\nSo the practical rule is not \u0026ldquo;be specific or it will fail,\u0026rdquo; because vagueness clearly did not fail here. It is this: be specific about the things you actually care about. If you want a wider board, WASD controls, wall kicks on rotation, or a speed that ramps with the score, name them in the prompt. Adding the compile command and a line like \u0026ldquo;make sure it builds and runs\u0026rdquo; is worth it too, because it turns a text-generation task into a verifiable one. Everything else you can happily delegate.\nWhatever you choose to spell out, submit the prompt and let the agent work.\nStep 3: Watch the debugging loop # This is the part worth paying attention to. Gemini CLI does not hand you a block of text to paste somewhere. It wrote tetris.c straight into my project directory and compiled it there, and when the session ended the built binary was sitting next to the source. The unit of work is a program that runs, not a snippet you still have to assemble.\nThe loop I can document in detail is the first one, the one that ran on my broken file. I handed the agent code that would not compile, it read the real file, it explained what was wrong with it, and the conclusion we arrived at was that a rewrite would get me to a working game faster than a repair would. That is the same cycle any developer runs: read the failure, reason about the cause, weigh a fix against a redesign, and then act. The difference is that it played out in minutes instead of over an evening.\nHow many attempts the agent needed on its own rewrite, I honestly cannot tell you, because my log preserves only my side of the conversation. What I can tell you is what ended up on disk: one self-contained C file that builds cleanly with gcc tetris.c -o tetris -lncurses and a playable game.\nWhat I can measure exactly is the clock, and it is the number most people want when they ask what an agent is worth. From my first prompt to a compiled, playable binary the whole thing took 24 minutes and 44 seconds. The rewrite on its own, from the \u0026ldquo;start over\u0026rdquo; message to a program that ran, took 11 minutes and 19 seconds, and the binary appeared fifteen seconds after the source file did. Those figures come from evidence rather than memory: the two prompt timestamps are in the Gemini CLI log at ~/.gemini/tmp/\u0026lt;hash\u0026gt;/logs.json (06:35:59 and 06:49:24 UTC), and the two file timestamps are the modification times of tetris.c (07:00:28) and of the binary next to it (07:00:43). The log runs on UTC and my machine was eight hours behind it, which is why the log says February 2 and this post is dated the evening of February 1. So I can tell you precisely how long it took without being able to tell you how many tries it took, and both halves of that are worth saying out loud.\nMoreover, reading the code that comes back is one of the most direct ways to pick up C and ncurses concepts. Tracing how is_valid_position() guards every move, how place_piece() shifts rows down after a clear, and how the color pairs are attached to piece types teaches more than a chapter on syntax would, and the learning happens as a natural side effect of building the project. If you want to read the whole file before you write a line of your own, that session\u0026rsquo;s output is public at EmilianFC20/tetris-in-c under an MIT license, committed exactly as the agent produced it and left unpolished on purpose. The compiled binary is deliberately not in there, because compiling it is your part.\nLines 7 to 31 of the generated tetris.c: the BOARD_WIDTH, BOARD_HEIGHT and PIECE_SIZE defines, and the start of the PIECES table with the I, O, T and S tetrominoes and their four rotation states each. Although the loop is fast, you are still the engineer in the room. The agent\u0026rsquo;s job is to produce a candidate. Yours is to read it, run it, and decide whether it actually does what you asked.\nStep 4: Compile and play # In my session the agent had already written tetris.c and compiled it before it handed the work back, so there was nothing left to build. If your agent stops at the source file, or if you are working through this by hand, this is the command:\ngcc tetris.c -o tetris -lncurses The -lncurses flag is the one people forget. It tells the linker to link your program against the ncurses library, and without it the code compiles fine and then fails at the link stage with undefined references to functions like initscr.\nIf the compiler cannot find ncurses.h, install the development headers first:\nsudo apt install libncurses-dev Then run the game:\n./tetris The whole build and run cycle: gcc returns silently, ls -l shows the 17320-byte tetris binary next to the 7089-byte tetris.c, and ./tetris ends by printing the final score. Controls:\nKey Action ← / → Move piece left or right ↑ Rotate piece clockwise ↓ Soft-drop (faster descent) q Quit Score is calculated by lines cleared per placement. Clearing multiple lines in one move scores significantly more than clearing them one at a time, so it pays to build the board up deliberately for multi-line clears rather than dropping pieces wherever they happen to fit.\nWhat you get out of building it # When that game launches in your terminal for the first time, there is a real moment of satisfaction. You described something, and working software appeared.\nUnderneath that moment, you walk away with three concrete things. First, a program you built and compiled yourself, which is a different feeling from downloading someone else\u0026rsquo;s binary. Second, a complete, self-contained C codebase you can read as a study text: game state, an input loop, timing, rotation logic, and terminal rendering, all in one file small enough to hold in your head. Third, a repeatable workflow, because nothing about the process was specific to Tetris.\nMine is published at EmilianFC20/tetris-in-c if you would like something to compare yours against, or simply to read the 234 lines and see how far one sentence of English got.\nThat last point is what makes AI CLI agents a genuine force multiplier. You do not need to master C memory management, terminal rendering APIs, or rotation matrices before you can build something that works. Instead of studying for months and then building, you can start with the result you want, read the generated code to understand what it does, and build a mental model of the language from the inside out. The distance between \u0026ldquo;I have an idea\u0026rdquo; and \u0026ldquo;I have a running program\u0026rdquo; has never been shorter.\nThis is exactly what I found when I built this project. I was learning C and exploring Gemini CLI at the same time, and the intersection turned out to be a good one: a concrete goal, a language I was still very much learning, and an agent that could take the first pass at the implementation while I focused on understanding what it produced. Although the finished game is the part you can play, the failed attempt is the part I learned the most from. Writing a file that would not compile, hearing an honest assessment of why, and choosing a clean rewrite over a rescue told me more about where my C actually stood than a working program ever would have. Each setback was simply another layer to look at, and another opportunity to keep improving.\nThe contrast with 1984 is worth keeping in mind, though. Pajitnov\u0026rsquo;s work represents a level of dedication and craftsmanship that deserves real respect, and what AI agents offer is not a replacement for that depth. It is a faster on-ramp to the point where you can start building genuine understanding of your own.\nWhat\u0026rsquo;s next # Once your game is running, several directions are worth exploring:\nIncreasing difficulty over time: raise the gravity speed as the score grows, so the game challenges the player to keep improving. Next-piece preview: add a small panel showing the upcoming piece so the player can plan ahead. Hold piece mechanic: let the player park one piece and swap back to it later. Persistent high score: write the best score to a file and display it at startup. Graphical rendering with SDL2: replace ncurses with SDL2 for a proper windowed game with pixel graphics and sound. Each of these is also a good second exercise in the same workflow: describe the change precisely, let the agent draft it, then read what came back before you accept it.\nIf you build your own version of this, I would love to hear what you extended or changed.\nTetris® is a registered trademark of The Tetris Company, LLC. This project is an independent educational reimplementation of the falling-blocks game mechanic and is not affiliated with or endorsed by The Tetris Company.\n","date":"1 February 2026","externalUrl":null,"permalink":"/posts/tetris-in-c-with-ai-cli/","section":"Posts","summary":"What if you could describe a game in plain language, press enter, and watch an AI agent write and debug C code until something playable appeared in your terminal?\nYou can, and an afternoon is enough. With a CLI coding agent, a C compiler, and the ncurses library, you can build a Tetris-inspired falling-blocks game that runs natively in your terminal, in a single session. You do not need to be a C programmer to begin, and I was not one either: my own hand-written tetris.c never made it past the compiler, and that failure is where this project actually started. By the end you will have three things worth keeping: a binary you compiled yourself, a readable C codebase you can pick apart line by line, and a workflow you can reuse on the next idea you have.\n","title":"Build a Tetris-Style Game in C with Gemini CLI","type":"posts"},{"content":"","date":"1 February 2026","externalUrl":null,"permalink":"/tags/c/","section":"Tags","summary":"","title":"C","type":"tags"},{"content":"","date":"1 de February de 2026","externalUrl":null,"permalink":"/es/tags/desarrollo-de-videojuegos/","section":"Tags","summary":"","title":"Desarrollo-De-Videojuegos","type":"tags"},{"content":"","date":"1 February 2026","externalUrl":null,"permalink":"/tags/gamedev/","section":"Tags","summary":"","title":"Gamedev","type":"tags"},{"content":"","date":"1 February 2026","externalUrl":null,"permalink":"/tags/gemini-cli/","section":"Tags","summary":"","title":"Gemini-Cli","type":"tags"},{"content":"","date":"1 February 2026","externalUrl":null,"permalink":"/tags/ncurses/","section":"Tags","summary":"","title":"Ncurses","type":"tags"},{"content":"","date":"1 de February de 2026","externalUrl":null,"permalink":"/es/tags/programacion/","section":"Tags","summary":"","title":"Programacion","type":"tags"},{"content":"","date":"1 February 2026","externalUrl":null,"permalink":"/tags/programming/","section":"Tags","summary":"","title":"Programming","type":"tags"},{"content":"What if the AI assistant your whole family uses ran on your own hardware, answered in seconds, and never sent a single word of your conversations to the cloud?\nYou can build exactly that. With free, open-source tools and a PC with a decent GPU, a private AI runs entirely on hardware you already own: no subscription fee, no account with a vendor, no data leaving your home network. Moreover, this is one of the most rewarding home-lab projects you can take on. By the end you will have worked through Linux, Docker, GPU drivers, and home networking, and you will understand how those four layers fit together.\nThis guide walks you through the whole build: a private, ChatGPT-style chatbot powered by Ollama and Open WebUI, accelerated by your NVIDIA GPU, and reachable from every phone, laptop, and tablet in the house.\nI have this running at home, and the hardest parts were never the AI. They were three small networking details that no tutorial warned me about, so I have written each of them down at the exact point where you will meet them.\nThis is the destination: a familiar chat window, except the model answering it lives on a GPU in my own house. What You Need Before You Start # A Windows 11 PC with an NVIDIA GPU. The GPU is what makes answers feel immediate rather than sluggish. Mine is an RTX 3070 Ti with 8 GB of VRAM, the card from my component-selection guide for building your own PC, and serving this assistant is what that build ended up being pointed at. Every number in this guide was measured on it, so you can scale your expectations against a mid-range card rather than a workstation. A Windows NVIDIA driver on version 471.11 or later. Nothing needs to be installed inside Linux. Free disk space for the model weights, which run to several gigabytes per model. Administrator access on Windows, because a few steps touch the firewall and the port forwarding table. The Stack # Back in 2024, Ollama did not have a native Windows installer, so the only way to run it on a Windows machine was through WSL2 (Windows Subsystem for Linux). Far from being a limitation, WSL2 is genuinely the best way to do this: you get a real Linux environment with full GPU access, running right alongside Windows on hardware you already own.\nThe full stack:\nWSL2 on Windows 11: a genuine Linux environment running alongside Windows, with direct access to the NVIDIA GPU. Ollama: handles downloading, managing, and serving open-weight LLMs locally. Open WebUI: a clean, ChatGPT-style browser interface that talks to Ollama over HTTP. Docker: runs Open WebUI as a container inside WSL, keeping the setup portable and clean. Portainer (optional): a visual dashboard for managing Docker containers without the command line. WSL2 with an automated port proxy: the configuration that makes the chatbot reachable from other devices on the home network, with a small scheduled task that keeps the forwarding rules from breaking on every reboot. Stacked up, a request from your phone travels like this:\nPhone, laptop, or tablet on the LAN the devices everyone already has │ http://\u0026lt;PC-LAN-IP\u0026gt;:8080 ▼ Windows host (netsh portproxy) re-pointed at every logon by a script │ 0.0.0.0:8080 -\u0026gt; \u0026lt;WSL-IP\u0026gt;:8080 ▼ Open WebUI (Docker, --network=host) the ChatGPT-style interface │ OLLAMA_BASE_URL=http://127.0.0.1:11434 ▼ Ollama (native service, NVIDIA GPU) downloads, manages, and serves the model Don\u0026rsquo;t be put off by WSL. It is a remarkably powerful tool: you get the full Linux command line (package managers, shell scripts, Docker, everything) while staying in the familiar Windows environment. If you have never used it before, this project is a great first encounter.\nStep 1: Enable WSL2 and Install Ubuntu # Open PowerShell as an administrator and run:\nwsl --install This enables WSL2 and installs Ubuntu by default. After a reboot, launch Ubuntu from the Start menu and complete the initial user setup (username and password).\nIf you already have WSL installed and want to confirm it is using version 2:\nwsl --list --verbose Look for VERSION 2 next to your distribution. If it shows version 1, upgrade it with wsl --set-version Ubuntu 2.\nStep 2: Verify Your NVIDIA GPU in WSL # WSL2 on Windows 11 exposes your GPU automatically, so you do not need to install a separate NVIDIA driver inside Linux. The driver lives on the Windows host; WSL bridges it. The only requirement is that your Windows NVIDIA driver is version 471.11 or later (any driver released after mid-2021 should qualify).\nInside the WSL terminal, run:\nnvidia-smi You should see a table listing your GPU name, driver version, and CUDA version.\nTwo fields decide whether the rest of the guide will work: the driver version in the header row, which must be 471.11 or later, and a CUDA version that appears at all. If both are there, Ollama will find the GPU without being told. If the command is not found or returns an error, open GeForce Experience (or download the latest driver from nvidia.com) and update your Windows driver, then try again.\nThis step matters: Ollama will automatically use the GPU for inference if CUDA is visible, making responses significantly faster.\nStep 3: Install Ollama and Pull Your First Model # Inside your WSL terminal, run the official install script:\ncurl -fsSL https://ollama.com/install.sh | sh The script detects CUDA and configures Ollama to use the GPU. Once finished, pull and run a model. I used llama3.1, which was the state-of-the-art open model at the time:\nollama run llama3.1 The first run downloads the model weights (several gigabytes, depending on the model). After that, you will land in an interactive chat prompt right in the terminal. Try a question to confirm everything works, then exit with /bye.\nOllama also starts a background HTTP server on http://127.0.0.1:11434. This is the API that Open WebUI will use.\nWhat I run today (August 2026): llama3.1 was the right pick in 2024, but open models move quickly and it is no longer installed on this machine. The engine that serves the house now is Qwen3.5 4B (qwen3.5:4b, 4.7B parameters, Q4_K_M quantisation, 3.4 GB on disk), pulled exactly the same way with ollama run qwen3.5:4b. Nothing else in this guide depends on which model you choose, so start with whatever is current when you read this. The measured results further down explain why a 4B ended up beating a much larger model on an 8 GB card.\nStep 4: Deploy Open WebUI with Docker # Install Docker Engine inside WSL. The quickest path is the official convenience script:\ncurl -fsSL https://get.docker.com | sh sudo usermod -aG docker $USER Log out and back into the WSL session so the group change takes effect, then start the Docker daemon:\nsudo service docker start Now run the Open WebUI container:\nsudo docker run -d \\ --network=host \\ -v open-webui:/app/backend/data \\ -e OLLAMA_BASE_URL=http://127.0.0.1:11434 \\ --name open-webui \\ --restart always \\ ghcr.io/open-webui/open-webui:main A note on OLLAMA_BASE_URL: the value http://127.0.0.1:11434 tells the container how to reach Ollama, and it is what I recommend writing. 0.0.0.0 is really a listen address, meaning \u0026ldquo;accept connections on every interface\u0026rdquo;, rather than a destination. Linux does accept it as a destination and routes it to localhost, so OLLAMA_BASE_URL=http://0.0.0.0:11434 works too (my own compose file has been running that way for a long time), but it works because of platform-specific behaviour and not because the address means what you intend. Writing 127.0.0.1 says exactly what you mean and carries over unchanged to any other platform. Because the container runs with --network=host, it shares the WSL network namespace with Ollama, so 127.0.0.1 resolves correctly.\nOpen a browser on your Windows machine and navigate to http://localhost:8080. You should see the Open WebUI login page. Create an account (no email verification required, it is entirely local) and start chatting.\nStep 5: Reach Your Private AI from Every Device in the House # At this point the chatbot is only reachable from the PC itself. To open it up to the rest of the house, you forward the relevant ports from the Windows host into WSL with netsh portproxy. This approach has one well-known catch: WSL2 gets a fresh internal IP every time Windows restarts, which means a static forwarding rule silently breaks after every reboot. The fix is not to abandon port forwarding, but to automate it: a small script re-points the forward at WSL\u0026rsquo;s current IP, run automatically by a scheduled task at every logon.\nWhy not \u0026ldquo;mirrored\u0026rdquo; mode? Windows 11 also offers networkingMode=mirrored, which gives WSL the host\u0026rsquo;s IP directly and removes the need for any forwarding. It is tempting, but it routes 127.0.0.1 connections through the Windows network stack, which breaks Ollama\u0026rsquo;s GPU discovery (the loopback probe hangs and Ollama silently falls back to CPU) and, in my case, interfered with a VPN. NAT mode plus an automated port proxy keeps the GPU working and is just as reliable once the refresh is scripted.\nKeep WSL on NAT networking # NAT is WSL2\u0026rsquo;s default mode, but make it explicit. Create or edit C:\\Users\\\u0026lt;you\u0026gt;\\.wslconfig on Windows (it lives in your Windows user folder, not inside WSL):\n[wsl2] networkingMode=NAT localhostForwarding=true Then shut down WSL from PowerShell so the change takes effect:\nwsl --shutdown Open the ports in WSL\u0026rsquo;s firewall # sudo ufw allow 8080/tcp sudo ufw allow 11434/tcp Port 8080 is Open WebUI. Port 11434 is the Ollama API; you will need it reachable on the network if you ever want to connect other tools (or, later, a Discord bot) directly to the model server.\nLet Ollama listen on the network # By default, Ollama only listens on 127.0.0.1. To make it reachable from other devices, override its service configuration:\nsudo systemctl edit ollama In the editor that opens, add:\n[Service] Environment=\u0026#34;OLLAMA_HOST=0.0.0.0:11434\u0026#34; Save and restart:\nsudo systemctl restart ollama Format matters: The value must be 0.0.0.0:11434 with no http:// prefix. Adding the prefix causes Ollama to parse it as a URL and bind to IPv6 [::] instead of IPv4, which does not reliably receive IPv4 LAN traffic. Keep it as a bare host:port.\nForward the ports from Windows into WSL # WSL\u0026rsquo;s internal IP changes on every boot, so hardcoding it would break after the next restart. Instead, write a small PowerShell script that looks up the current WSL IP and rebuilds the forwarding rules. Save it as C:\\Users\\\u0026lt;you\u0026gt;\\wsl-portproxy.ps1:\n# Re-points Windows port forwarding at WSL\u0026#39;s current internal IP so LAN # devices can reach the services inside WSL at the Windows host IP. # Run as Administrator. $ports = @(8080, 11434) # Get WSL\u0026#39;s current internal IPv4 address (first one from `hostname -I`) $wslIp = (wsl hostname -I).Trim().Split(\u0026#34; \u0026#34;)[0] if (-not $wslIp) { Write-Error \u0026#34;Could not determine WSL IP. Is WSL running?\u0026#34;; exit 1 } Write-Host \u0026#34;WSL IP: $wslIp\u0026#34; foreach ($p in $ports) { # Clear any stale rule for this port, then add a fresh one netsh interface portproxy delete v4tov4 listenport=$p listenaddress=0.0.0.0 2\u0026gt;$null | Out-Null netsh interface portproxy add v4tov4 listenport=$p listenaddress=0.0.0.0 connectport=$p connectaddress=$wslIp | Out-Null Write-Host \u0026#34;portproxy: 0.0.0.0:$p -\u0026gt; ${wslIp}:$p\u0026#34; # Ensure the Windows firewall allows inbound on this port (only added once) $ruleName = \u0026#34;WSL portproxy $p\u0026#34; if (-not (Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue)) { New-NetFirewallRule -DisplayName $ruleName -Direction Inbound -Action Allow ` -Protocol TCP -LocalPort $p | Out-Null Write-Host \u0026#34;firewall: allowed inbound TCP $p\u0026#34; } } Write-Host \u0026#34;`nDone. Current portproxy table:\u0026#34; netsh interface portproxy show v4tov4 Run it once from an Administrator PowerShell to apply the forwards immediately. The firewall rules it creates accept inbound on those ports from anywhere; if you would rather restrict them to your home network, add -RemoteAddress 192.168.1.0/24 to the New-NetFirewallRule call, substituting your own LAN range (the subnet ipconfig reports for your Wi-Fi or Ethernet adapter, which is often 192.168.0.0/24 or 10.0.0.0/24 instead).\nRefresh the forward automatically on every reboot # So you never have to run the script by hand, register it as a scheduled task that fires at logon. From an Administrator PowerShell:\n$action = New-ScheduledTaskAction -Execute \u0026#34;powershell.exe\u0026#34; -Argument \u0026#34;-WindowStyle Hidden -ExecutionPolicy Bypass -File C:\\Users\\\u0026lt;you\u0026gt;\\wsl-portproxy.ps1\u0026#34; $trigger = New-ScheduledTaskTrigger -AtLogOn Register-ScheduledTask -TaskName \u0026#34;WSL portproxy\u0026#34; -Action $action -Trigger $trigger -RunLevel Highest Now find your PC\u0026rsquo;s LAN IP address (ipconfig in PowerShell, look for the IPv4 address of your Wi-Fi or Ethernet adapter) and navigate to http://\u0026lt;PC-LAN-IP\u0026gt;:8080 from any other device on the same network. After a reboot, the task re-points the forward at WSL\u0026rsquo;s new internal IP automatically, so the LAN address stays the same.\nHeadless caveat: the task fires at logon. If you reach the machine without logging in (for example over \\\\wsl$ or a remote tool), run wsl-portproxy.ps1 once by hand to bring the forward up.\nStep 6: Portainer (Optional) # If you prefer a visual interface for managing your Docker containers rather than using the command line, Portainer is a lightweight option. Run it alongside Open WebUI:\nsudo docker run -d \\ --network=host \\ -v /var/run/docker.sock:/var/run/docker.sock \\ -v portainer_data:/data \\ --name portainer \\ --restart always \\ portainer/portainer-ce Portainer\u0026rsquo;s web UI will be available at http://localhost:9000, which is Portainer CE\u0026rsquo;s default HTTP port (9443 serves the same interface over HTTPS). Because the container runs with --network=host, it publishes nothing of its own, it simply listens on WSL\u0026rsquo;s ports, so reaching it from another device would need the same portproxy treatment as Step 5. It lets you start, stop, and inspect containers with a few clicks, which comes in handy when you want to update Open WebUI without remembering the exact docker run flags.\nOnce the stack has more than one container, a dashboard is faster than docker ps for answering the only question that usually matters: is everything still up? What I actually run, and why it sidesteps Step 5 entirely. There is no Portainer server inside WSL on my machine. Instead, WSL runs a Portainer Edge agent (portainer/agent:latest with EDGE=1 plus the edge ID and key that the server issues), while the Portainer server itself lives on my home NAS. The agent dials out to that server and holds the tunnel open, so it publishes no ports at all and never has to be found at WSL\u0026rsquo;s address. That is a neat inversion of the problem Step 5 spends so long solving: instead of teaching the network how to reach a moving target, the moving target keeps a connection open to something that does not move, which makes it immune to WSL\u0026rsquo;s changing IP and to reboots. If you already have a Portainer server somewhere on your network, this is the more robust way to manage the containers living inside WSL.\nHow Fast Is It Really? Measured Numbers on an 8 GB GPU # The whole reason for putting a GPU behind this stack is speed, so here are real figures instead of adjectives. I measured them in August 2026 on the RTX 3070 Ti (8 GB) with Ollama 0.24.0, calling the /api/generate endpoint directly and reading Ollama\u0026rsquo;s own eval_count / eval_duration counters, with think: false, three different prompt sizes, and the median of three runs at every point.\nModel Generation Prompt processing Time to first token VRAM in use, of 8,192 MiB llama3.2, 3.2B, Q4_K_M 195 tok/s 7,370 tok/s 0.10 s 3,017 MiB qwen3.5:4b, 4.7B, Q4_K_M 102 tok/s 513 tok/s 0.22 s 5,949 MiB Qwen3.5 9.7B, Q4_K_M (my tuned build) 70 tok/s 780 tok/s 0.20 s 7,921 MiB That last column is an absolute nvidia-smi reading taken during a quiet session, so it counts everything on the card, the model and whatever the Windows desktop happened to be holding (434 MiB at the time). The memory figure for the 4B was measured on my tuned build, which carries the same weights as qwen3.5:4b and occupies the same space.\nThree things are worth reading out of that table. First, about 100 tokens per second on a 4.7B model with 0.2 s to the first token means the answer starts appearing the moment you stop typing and then arrives far faster than anyone reads it, which is the practical bar for a family assistant. Second, the rate is flat with length: the 4B held 101 to 105 tok/s whether it produced 34 tokens or 534. Third, loading a model from disk costs 3.4 to 6.4 seconds, paid only on the first request after Ollama unloads it.\nThe \u0026ldquo;tuned build\u0026rdquo; in the last row is a Modelfile over the stock weights rather than retrained weights, which is why my tuned 4B measured 103 tok/s against the stock 4B\u0026rsquo;s 102: within a point of each other. The tuning changes the assistant\u0026rsquo;s behaviour, not its speed. That customisation is the subject of part 2 of this series.\nSize the Model to the Card, Not to the Disk # Here is the finding I did not expect, and the one most likely to save you an evening.\nA 6.6 GB model on an 8 GB card looks like a comfortable fit on paper. It is not, because the model is never the only tenant, and the other tenant will not hold still: the Windows desktop claimed 434 MiB of VRAM during a quiet session and 1,295 to 1,326 MiB while the machine was actually being used. Load the 9.7B model at num_ctx 8192 and the card fills to 7,921 MiB of its 8,192 MiB in the quiet case and to 7,883 to 7,939 MiB in the busy one, which is 250 to 270 MiB of margin either way, about 97 % full. The total lands in the same place under both conditions because Ollama sizes its allocation to whatever is free rather than taking a fixed amount. Nothing looked wrong from the outside, and ollama ps still reported 100% GPU.\nAt rest it behaved well and reproducibly: six consecutive long generations all landed between 69 and 72 tok/s, which is the honest steady-state figure for that model on this card. During one benchmark sweep, however, the same model answering the same prompt collapsed to 19 to 21 tok/s, with prompt processing falling from about 800 tok/s to 133. That is what a quarter of a gigabyte of margin actually buys: the margin is not merely small, it moves with whatever Windows is drawing, so the moment the desktop asks for more VRAM, part of the work slides off the GPU. The collapse is intermittent rather than permanent, and that is precisely what makes it a poor foundation for a service other people depend on. It is fast every time you test it and slow the one time someone else needs it.\nThe 4B model solves this not by being cleverer but by leaving room. Its footprint is a fixed 5,515 MiB, so what varies is the free space rather than the model: roughly 2.2 GB free when the desktop is idle and about 1.4 GB when it is busy. A margin that comfortably absorbs the desktop\u0026rsquo;s drift is what keeps the speed steady. Moving the family assistant from the 9.7B to the 4B is exactly the decision I walk through in part 2, and the reasoning generalises: when you choose a model, read the free VRAM after it loads, not the file size before it.\nWhat You Get Once It Is Running # You end up with an AI assistant that is fast, always available, and completely private: no subscription, no account, no data leaving your house. Every device on your network reaches it at http://\u0026lt;your-PC-IP\u0026gt;:8080, and it keeps serving answers whether or not anyone is sitting at the PC.\nThe use cases are more practical than they might sound. In my home, the family uses it to ask about the Wi-Fi password, which streaming service has a particular show, or how to reconnect a smart home device that dropped off the network. These are the kind of questions that used to require a search engine or a shout across the house. Your household will find its own everyday uses quickly.\nWhat the Project Teaches You # Beyond the assistant itself, this is one of the best home-lab projects you can take on as an engineer, because it makes you cross four boundaries in a single build: GPU passthrough into a virtualised Linux environment, container networking, a Linux firewall, and the bridge between the Windows host and WSL. Each of those skills transfers directly to the next thing you build.\nThe obstacles are the curriculum, and four of them taught me the most:\nThe tempting shortcut is not always the right one. Mirrored networking would have removed the port forwarding entirely, but it broke Ollama\u0026rsquo;s GPU discovery and interfered with my VPN. NAT plus a script was the less elegant option and the one that actually worked. Configuration formats are not suggestions. An http:// prefix on OLLAMA_HOST was enough to bind the server to IPv6 and make it invisible to the rest of the LAN. Reading how a program parses its own settings costs far less time than guessing at it. A fix you have to remember is not a fix. Re-pointing the forward by hand after every reboot is not something anyone keeps doing for long. Turning it into a script and a scheduled task is what turned a working demo into something the family relies on. Fitting is not the same as running well. The 9.7B model fit in 8 GB with 250 MiB to spare, and Ollama happily reported 100% GPU. It also lost most of its speed whenever the desktop wanted VRAM back. Measuring the headroom after a load, rather than trusting the file size, is a habit worth building early. Although none of those problems appeared in the guides I started from, each one left me understanding the stack better than a clean first attempt ever would have. That is the pattern I keep meeting in home-lab work: every setback is simply another opportunity to learn from past mistakes and keep improving.\nWhat\u0026rsquo;s Next # Once your private AI is running, the stack becomes a foundation rather than a finished product. Here are the directions worth exploring next:\nTrying different models: Ollama makes it trivial to pull and switch between models; comparing your initial pick with other open-weight options is a natural next step, and the table above is a template for benchmarking them honestly on your own card. Adding a custom system prompt: giving the assistant a persona and a set of house-specific context to make it even more useful for everyday family questions. Reaching it from outside your network: put Open WebUI behind a reverse proxy (Nginx Proxy Manager is a good starting point) and point a domain at it, so the chatbot is reachable from your phone even when you are not home. Wiring it into Discord: a small bot that relays messages to the Open WebUI API and posts the replies back to a channel, so the family can use it from an app they already have open without navigating to a URL. I built exactly that, and part 2 of this series walks through the whole bridge: Give Your Private AI a Discord Bot Your Family Will Actually Use. Whichever direction you take, the important part is that the assistant is yours: your hardware, your data, your rules. If you build your own version of this, I would love to hear how it goes.\n","date":"1 August 2024","externalUrl":null,"permalink":"/posts/deploy-private-ai-ollama/","section":"Posts","summary":"What if the AI assistant your whole family uses ran on your own hardware, answered in seconds, and never sent a single word of your conversations to the cloud?\nYou can build exactly that. With free, open-source tools and a PC with a decent GPU, a private AI runs entirely on hardware you already own: no subscription fee, no account with a vendor, no data leaving your home network. Moreover, this is one of the most rewarding home-lab projects you can take on. By the end you will have worked through Linux, Docker, GPU drivers, and home networking, and you will understand how those four layers fit together.\n","title":"Deploy Your Private AI with Ollama and Open WebUI on WSL2","type":"posts"},{"content":"","date":"1 August 2024","externalUrl":null,"permalink":"/tags/nvidia/","section":"Tags","summary":"","title":"Nvidia","type":"tags"},{"content":"","date":"13 January 2023","externalUrl":null,"permalink":"/tags/3d-printing/","section":"Tags","summary":"","title":"3d-Printing","type":"tags"},{"content":"","date":"13 January 2023","externalUrl":null,"permalink":"/tags/bigtreetech/","section":"Tags","summary":"","title":"Bigtreetech","type":"tags"},{"content":"","date":"13 January 2023","externalUrl":null,"permalink":"/tags/bltouch/","section":"Tags","summary":"","title":"Bltouch","type":"tags"},{"content":"","date":"13 January 2023","externalUrl":null,"permalink":"/series/ender-3-upgrade/","section":"Series","summary":"","title":"Ender 3 Upgrade","type":"series"},{"content":"","date":"13 January 2023","externalUrl":null,"permalink":"/tags/ender-3/","section":"Tags","summary":"","title":"Ender-3","type":"tags"},{"content":"","date":"13 January 2023","externalUrl":null,"permalink":"/tags/hardware/","section":"Tags","summary":"","title":"Hardware","type":"tags"},{"content":"","date":"13 de January de 2023","externalUrl":null,"permalink":"/es/tags/impresion-3d/","section":"Tags","summary":"","title":"Impresion-3d","type":"tags"},{"content":"Have you spent an evening chasing a perfect first layer, only to nail one side of the bed and lose the other? Manual bed levelling on a stock Ender 3 is a ritual: four knobs, a sheet of paper, and a first layer that is beautiful on the left side of the bed and translucent on the right. The problem is not that you are bad at turning knobs. It is that the bed is not flat, and no amount of adjusting four corners will fix a surface that bows in the middle.\nA BLTouch solves this differently. Instead of asking you to make the bed flat, it measures how unflat the bed is and tells the printer to follow that shape. This post covers adding one to an Ender 3 that already has a BIGTREETECH SKR Mini E3 V2.0 in it: printing a mount, wiring the probe, and (the part that actually determines whether it works) measuring the offsets between the probe and the nozzle.\nI did not work this out from first principles. The procedure below follows Teaching Tech\u0026rsquo;s BLtouch for any 3D printer - Comprehensive step by step guide, which is the video I had open on a second screen while I installed mine. What I add here is everything specific to this machine: the SKR Mini E3 V2.0 wiring, the offsets I measured on my own mount, and the numbers that only show up once you run the arithmetic for your own geometry.\nThis is part 3 of five. Part 2 covered the board swap that makes this part easy, and part 4 compiles the firmware that ties everything together.\nThe BLTouch mounted next to the hotend on a printed bracket, pin deployed. What the BLTouch actually is # The name suggests something exotic, but the mechanism is simple and clever. Inside the housing there is a small push pin and a solenoid, plus a Hall-effect sensor that detects the pin\u0026rsquo;s position magnetically.\nTo take a measurement, the firmware energises the solenoid, which drops the pin. The printer then lowers the Z axis until the pin touches the bed and is pushed back up into the body; the Hall sensor sees that motion and the probe sends a trigger signal, exactly like an endstop switch closing. The firmware records the Z height at that instant, then retracts the pin and moves on to the next point.\nDo that at a grid of points across the bed and you have a mesh: a map of how high or low the bed sits at each location. During a print, Marlin adjusts Z continuously against that mesh, so the nozzle traces the real surface instead of an imaginary flat plane.\nTwo practical consequences of the mechanism are worth knowing before you install one:\nIt touches the bed with a plastic pin, not with the nozzle. So it measures whatever surface you probe on, and it will happily probe a bed clip and give you a nonsense reading. It self-tests on power-up. When you switch the printer on, the pin should deploy and retract a couple of times and end up retracted, with a steady red light. That little dance is your first and best diagnostic: if it does not happen, or the pin ends up stuck out and blinking, the probe has a wiring or power problem and there is no point moving on to firmware. Printing the mount # The BLTouch does not come with a way to attach itself to an Ender 3. You print one.\nI used (Yet Another) Ender 3 BLTouch Mount by Thingiverse user StevenMLawson (licensed under Creative Commons). It replaces the front cover of the hotend fan shroud and holds the probe out to the left of the nozzle, which is what makes the X offset as large as it is later on.\nThe download contains two models:\nBLTouchMount.STL: the plain version, which is the one I printed. BLTouchMount-Laser.STL: a variant with provision for a laser module, which you only want if you are actually mounting one. Print it in PETG if you can. This part sits directly next to the heater block. PLA is fine for a while, but one hot summer print later your probe is quietly drooping a fraction of a millimetre out of position, which shows up as a bad first layer you will spend an evening chasing. PETG or ABS avoids the whole issue.\nSettings-wise nothing exotic: 0.2 mm layers, 4 perimeters, 30–40 % infill. This part holds the alignment of your measuring instrument, so it is worth printing solidly and worth checking that it came out without warping before you bolt a probe to it.\nMount it with the hotend fan screws, using M3 hardware. The general rule is to put the probe as close to the nozzle as you can without it failing from heat exposure, and to bolt it down tight, because any play or wobble in the bracket destroys the accuracy of every reading it takes. Three things to check once it is on:\nThe probe body is square to the bed, and there is no play in the mount. A tilted probe measures a tilted mesh, and a loose one measures a different bed every time. The retracted pin sits between 2.3 mm and 4.3 mm above the nozzle tip. That is Antclabs\u0026rsquo; own window, and the reason it has two edges is that the pin\u0026rsquo;s stroke is fixed, so the probe fails in a different way at each end. Mount it too high and the deployed pin no longer reaches far enough below the nozzle to touch the bed first: the nozzle arrives first, the probe never triggers, and Marlin keeps driving Z down because as far as it knows the bed is still out of reach. That is the dangerous end of the range, and it ends with the nozzle buried in your build surface. Mount it too low and probing itself works fine, but the retracted pin now hangs down into the work for the rest of the print. Setting the height is easier than it sounds. With the printer powered off, lower the print head by hand until the nozzle rests on the bed, then use a 3 mm allen key as a spacer to set the probe\u0026rsquo;s height against it. If the bracket does not let the probe slide, shim it with washers until it lands in the window. Nothing on the bed for the retracted pin to catch. This is the low end of that same concern, checked against your actual machine instead of against a number: deploy the pin by hand at various positions and confirm that it never lands on a bed clip when the printer probes near an edge, and that it has nothing to snag on as the head crosses a tall part. The printed BLTouch bracket fresh off the bed, before mounting. Wiring it up # A BLTouch comes with five wires, normally split into two connectors:\nA 3-pin connector: brown (GND), red (5 V), and orange/yellow (the control signal, which behaves like a servo signal and tells the pin to deploy or stow). A 2-pin connector: black (GND) and white (the trigger signal, which behaves like an endstop). On most boards you have to route those two connectors to two different places, which is exactly the awkwardness that made adding a probe to the stock Creality board annoying. The SKR Mini E3 V2.0 has a dedicated probe header that takes both, so this is a single plug.\nIn firmware terms, that header maps to two pins on the STM32: the trigger signal lands on PC14 and the servo control on PA1. You will see PC14 appear explicitly in the configuration in part 4, as:\n#define Z_MIN_PROBE_PIN PC14 Watch the polarity of the connector. The probe header is keyed, but the cable that ships with a BLTouch is not always crimped in the order you expect, and 5 V into the signal pin is a bad afternoon. Compare the wire colours against the pin labels silkscreened next to the header before you push it home.\nWhat about the original Z endstop switch? Leave it plugged in. The probe has its own dedicated pin, so the mechanical switch on the Z axis does not conflict with it, and keeping it costs nothing. The firmware will use the probe for homing Z (that is the USE_PROBE_FOR_Z_HOMING option) while the switch stays wired as the plain Z-min endstop.\nRoute the cable along the existing loom to the hotend, with enough slack that a full-travel move in X and Y does not tug on it, and enough restraint that it never dips into the path of the gantry. Cable ties and the existing sleeve are your friends here.\nThe BLTouch\u0026rsquo;s 5-pin connector plugged into the dedicated probe header on the SKR Mini E3 V2.0. Testing the probe before trusting it # Before any levelling, confirm the probe responds to commands. Connect over USB and send these one at a time:\nM280 P0 S10 ; deploy the pin M280 P0 S90 ; stow the pin M280 P0 S120 ; run the self-test (pin cycles repeatedly) M280 P0 S160 ; reset / clear an alarm state If S10 and S90 work, your servo signal is wired correctly and the firmware is talking to the probe. Send S120 and let it cycle a few times, then S160 to stop it.\nNext, confirm the trigger signal, which is a separate wire and can be wrong even when deploy and stow work perfectly. Deploy the pin, then send:\nM119 Look for the z_probe line, or z_min if your build has no separate probe pin. Once an auto bed levelling probe is configured, Marlin gives it a status line of its own, and this build has a dedicated Z_MIN_PROBE_PIN, so z_probe is the line that matters here.\nPush the pin up by hand and send M119 again: the state should flip. Here is the detail that sends people chasing imaginary faults: a BLTouch reports the opposite of what you expect from an endstop. With the pin retracted and idle it reads TRIGGERED, and with the pin deployed and waiting for the bed it reads open. Read that polarity the intuitive way around and you will conclude that a perfectly healthy probe is broken. What you are confirming is simply that the two states change when the pin moves. A probe that deploys beautifully but never changes state at all will drive the nozzle straight into the bed on the first G28, so do not skip this.\nThe part that actually matters: probe offsets # The BLTouch does not sit where the nozzle sits. It hangs off to one side, at a different height. Every measurement it takes is therefore taken somewhere else, and the firmware needs to know exactly where in order to convert probe readings into nozzle heights.\nThat is one setting, three numbers:\n#define NOZZLE_TO_PROBE_OFFSET { -41, -12, -1.925 } Those are the numbers I ended up with for this mount. Do not copy them blindly: X and Y depend on your bracket, and Z depends on your specific probe, bracket and how it is bolted on. Here is how to get your own.\nX and Y: where the probe is relative to the nozzle # The values are measured from the nozzle to the probe, in printer coordinates. Negative X means the probe is to the left of the nozzle; negative Y means it is in front.\nThe reliable low-tech method:\nTape a sheet of paper to the bed and home the printer. Move the nozzle down until it just touches the paper, and mark the exact point under the nozzle tip. Raise Z, then move the carriage so that the probe pin is over that mark, using the printer\u0026rsquo;s own jog controls, and deploy the pin so it touches the paper. Mark that point too. Read the X and Y distances between the two marks with a caliper, and work out the signs by which way you had to move. A caliper straight onto the hardware works as well if the geometry is accessible: measure the horizontal distance between the centre of the nozzle and the centre of the probe pin in each axis. For my mount that came out as 41 mm to the left and 12 mm forward, hence -41 and -12.\nMoreover, those numbers have a real cost, and it is worth doing the arithmetic instead of guessing at it. The carriage only travels so far, so a probe hanging 41 mm to one side simply cannot reach the far strip of the bed. Add the 10 mm PROBING_MARGIN that keeps probe points away from the edges (both values are set in part 4) and Marlin clamps the probed region on a 235 × 235 mm bed to:\nX: 10 mm to 194 mm, which is 184 mm of the 235 mm available. The far end comes straight from the offset (235 − 41 = 194) and the near end from the margin. Y: 10 mm to 223 mm, which is 213 mm. Here the 12 mm offset costs far less (235 − 12 = 223), and the margin again sets the near edge. So the probe actually touches about 184 × 213 mm out of 235 × 235 mm, which leaves close to 30 % of the bed area never measured directly. Run that same subtraction with your own offsets and margin and you will know your own number before you print anything.\nThat unmeasured border is exactly why part 4 enables EXTRAPOLATE_BEYOND_GRID, which extends the mesh past the outermost probe points so the edges and corners still receive a correction instead of falling off the map.\nZ: how far below the trigger point the nozzle sits # This is the number that decides whether your first layer is perfect or a smear, and unlike X and Y you cannot get it with a ruler.\nThe classic procedure:\nHome the printer with G28, then send M851 Z0 and M500 to zero out any stored offset and start from a known state. Move to the middle of the bed and lower Z in small steps with a sheet of paper under the nozzle, until the paper drags slightly. Note the Z value on the display. That negative number is your offset. Set it and save it: M851 Z-1.925 M500 Marlin also ships a guided version of exactly this, the probe offset wizard, which walks you through it from the LCD and stores the result for you. It is enabled in the firmware built in part 4, precisely because doing this by hand is fiddly and you will want to redo it any time you change nozzles or re-mount the probe. One detail that costs people an afternoon of searching: PROBE_OFFSET_WIZARD does not live in Configuration.h, where every other setting this series quotes lives, but in Configuration_adv.h, the second and much longer config file. Marlin even leaves a note about it in the first file, pointing across: \u0026ldquo;PROBE_OFFSET_WIZARD (configuration_adv.h) can be used for setting the Z offset.\u0026rdquo;\nThe other tool worth enabling for this is babystepping with BABYSTEP_ZPROBE_OFFSET, which lives in Configuration_adv.h as well. That lets you nudge Z live, during the first layer of a print, and have the adjustment written back into the probe offset instead of being lost when the print ends. Start a large first layer and watch it; on this build you reach the adjustment with a double-click on the status screen (that is DOUBLECLICK_FOR_Z_BABYSTEPPING, also enabled), then nudge until the extrusion looks right and save with M500. It is by far the fastest way to converge on a good number.\nLevelling, and making it stick # With offsets set, the actual levelling workflow is short:\nG28 ; home all axes (Z now homes with the probe) G29 ; probe the grid and build the mesh M500 ; save the mesh to EEPROM G28 first is not optional, and the reason is more specific than \u0026ldquo;measure only what you have homed.\u0026rdquo; Marlin\u0026rsquo;s own Configuration.h says it plainly: \u0026ldquo;Normally G28 leaves leveling disabled on completion.\u0026rdquo; Homing does not merely fail to enable compensation, it actively switches it off. That is why M420 S1 has to come afterwards to turn it back on, and it is the same reason the re-probe variant further down puts G29 after G28 and never before. With a probe installed, Marlin also insists on homing Z at a safe spot rather than at the corner (the Z_SAFE_HOMING option), because at the far corner the probe would be hanging off the edge of the bed with nothing under it.\nThen there is the step everyone forgets. Saving the mesh does not mean the printer uses it. The mesh has to be loaded and enabled at the start of every print, which is one line in your slicer\u0026rsquo;s start G-code:\nM420 S1 Put it after the G28 in the start sequence. Without it, the printer dutifully stores a perfect map of your bed and then ignores it, and you conclude that auto bed levelling does not work.\nIf you would rather re-probe before every print instead of relying on a stored mesh, replace M420 S1 with a G29 in the start G-code, still after the G28. It costs a minute or two per print and is more robust if you move the printer around or swap build surfaces often.\nThe bed mesh after a G29, showing how far from flat the bed actually is. Why tall prints stop following the bed # There is one behaviour that surprises almost everyone the first time they notice it: the correction fades out with height. ENABLE_LEVELING_FADE_HEIGHT is enabled by default in Marlin, with DEFAULT_LEVELING_FADE_HEIGHT 10.0, and both are live exactly as written in the firmware running on my machine. I did not choose that value, I inherited it, and after building the firmware myself I would rather know what it is doing than be surprised by it later. The comment in Configuration.h describes it precisely: \u0026ldquo;Gradually reduce leveling correction until a set height is reached, at which point movement will be level to the machine\u0026rsquo;s XY plane.\u0026rdquo;\nIn other words, my printer does all of the mesh\u0026rsquo;s work over the first 10 mm of a print and then tapers it away, and above that it moves in its own flat plane rather than following the shape of the bed. That is deliberate and it is the behaviour you want: bed compensation exists to rescue the first layers, not to skew a 200 mm tall part along the warp of the glass. If you ever want to change it, M420 Z\u0026lt;height\u0026gt; sets the fade height at runtime.\nWhen it does not work # The pin blinks red and stays down. That is an alarm state, usually caused by the pin being obstructed, the probe being knocked, or a bad connection. Send M280 P0 S160 to reset it. If it re-alarms immediately, check the pin can move freely.\nNo self-test at power-up. No self-test means no 5 V or no ground: a wiring problem, not a firmware one.\nDeploys and stows, but the printer crashes into the bed. The control signal is fine and the trigger signal is not. Go back to the M119 test above.\nThe mesh looks like a mountain range. Values varying by more than a few tenths of a millimetre usually mean something mechanical: a loose probe bracket, a bed spring with no tension left, or a probe that is not square to the bed. Fix it there, not in software.\nThe first layer is uniformly too high or too low. That is purely the Z offset. Babystep it during a print until it looks right and save with M500.\nWhat you gain, and what is next # With the probe mounted, wired, and its offsets measured, your Ender 3 no longer needs the four-knob ritual. It measures its own bed and Marlin corrects for it automatically, print after print. Mine went from a first layer that depended on which corner I babysat to one that is consistent from edge to edge, which is the kind of unglamorous, repeatable result that actually matters day to day.\nGetting there, though, assumed a firmware that already knows about the probe: that BLTOUCH is enabled, that the probe is on PC14, that bilinear bed levelling is turned on, that the offsets are stored. That firmware does not come from anywhere by magic.\nPart 4 builds it: VS Code, PlatformIO, Auto Build Marlin, and the specific set of configuration changes that turn a generic Marlin download into firmware for this exact machine.\n","date":"13 January 2023","externalUrl":null,"permalink":"/posts/ender-3-bltouch-install/","section":"Posts","summary":"Have you spent an evening chasing a perfect first layer, only to nail one side of the bed and lose the other? Manual bed levelling on a stock Ender 3 is a ritual: four knobs, a sheet of paper, and a first layer that is beautiful on the left side of the bed and translucent on the right. The problem is not that you are bad at turning knobs. It is that the bed is not flat, and no amount of adjusting four corners will fix a surface that bows in the middle.\n","title":"Installing a BLTouch on an Ender 3: Mount, Wiring, and Probe Offsets","type":"posts"},{"content":"","date":"13 de January de 2023","externalUrl":null,"permalink":"/es/series/mejorando-la-ender-3/","section":"Series","summary":"","title":"Mejorando La Ender 3","type":"series"},{"content":"","date":"12 de January de 2023","externalUrl":null,"permalink":"/es/tags/electronica/","section":"Tags","summary":"","title":"Electronica","type":"tags"},{"content":"","date":"12 January 2023","externalUrl":null,"permalink":"/tags/electronics/","section":"Tags","summary":"","title":"Electronics","type":"tags"},{"content":"Is your early Ender 3 the loudest thing in the room every time it starts a layer, or have you hit a wall trying to add features that the stock firmware simply will not fit? Both problems trace back to the same part: the mainboard Creality shipped it with. That whine is not a quirk of cheap printers in general. It is a very specific consequence of the drivers soldered onto that board, and it is only one of several things about it that quietly limit what the machine can do.\nThe fix is a straightforward swap, and it is one you can do in an evening even if you have never opened the printer before. This post walks through replacing the stock board with a BIGTREETECH SKR Mini E3 V2.0: what the stock board actually holds back, why this particular replacement is the easy answer for an Ender 3, and how to do the swap without releasing the magic smoke. It is part 2 of five. Part 1 printed the parts that fixed everything mechanical about this machine, part 3 adds a BLTouch probe, and part 4 compiles custom Marlin firmware for the whole thing in VS Code.\nThe BIGTREETECH SKR Mini E3 V2.0 next to the stock Creality board it replaced. What the stock board is actually costing you # The board in an early Ender 3 is a Creality design descended from the Melzi family. It is built around an ATmega1284P: an 8-bit AVR microcontroller running at 16 MHz, with 128 KB of flash and 16 KB of RAM. That is the same family of chip as an Arduino, and it does work. The printer prints. But there are four real limits baked into it.\nThe stepper drivers are soldered on. Those are A4988-class drivers, and they are not on sockets, so you cannot swap them for something quieter. Every move the printer makes is audible because of how those drivers chop current through the motor coils. This is the noise everyone complains about.\nMost of these boards ship without a bootloader. On a normal Arduino, a bootloader is the small program that lets the chip accept new firmware over USB. Creality left it off, which means that to flash your own firmware you first have to burn a bootloader onto the chip using a second device as an in-system programmer, wiring an Arduino Uno or a USBasp to the ICSP header on the board. Although that is not hard, it is a whole extra project standing between you and a firmware change.\n128 KB of flash is not much. Modern Marlin with the features you actually want (auto bed levelling, a levelling mesh, babystepping, linear advance, a probe wizard) does not comfortably fit. You end up trading features against each other and hitting \u0026ldquo;the sketch is too big\u0026rdquo; style build errors.\nThere is nowhere sensible to plug in a probe. The stock board has no dedicated probe or servo header, so adding a BLTouch means splitting its five wires between a servo signal you have to improvise and the Z endstop input.\nSo: noisy, hard to flash, out of space, and awkward to extend. Replacing the board fixes all four at once.\nWhy the SKR Mini E3 V2.0 specifically # BIGTREETECH designed this board as a drop-in replacement for the Ender 3 mainboard, and \u0026ldquo;drop-in\u0026rdquo; is meant literally. The mounting holes line up with the stock standoffs, the connectors are the same types in roughly the same places, and the stock LCD plugs straight into it. There is no adapter cable, no drilling, no rewiring of the display.\nWhat you get for that:\nA 32-bit STM32F103RC running at 72 MHz with 256 KB of flash. Twice the space and far more headroom for both features and motion planning. TMC2209 drivers, soldered but in UART mode. Soldered is fine here, because these are the quiet ones. In StealthChop mode the motors are genuinely near-silent, and UART control means the firmware can set current, microstepping and modes in software instead of you turning tiny trim pots with a screwdriver. Firmware flashing from the microSD card. Instead of burning a bootloader with an ISP programmer, you copy a firmware.bin file onto a card, put it in the board, and power on. That is the entire process. A dedicated 5-pin probe header, so a BLTouch plugs in with its own connector instead of being spliced across two ports. That is what part 3 relies on. Compatibility with the stock rotary-knob display, which in Marlin is the CR10_STOCKDISPLAY option. One thing to check before you order: which version you have matters. BIGTREETECH\u0026rsquo;s repository covers the V1.0, V1.2 and V2.0 of this board, and they are not the same board. The version is printed on the PCB itself. Everything in this series is the V2.0, and later on, in the firmware, that becomes the line #define MOTHERBOARD BOARD_BTT_SKR_MINI_E3_V2_0. Get that define wrong and the firmware will build cleanly and then behave like it is possessed, because it will be driving the wrong pins.\nBefore you unplug anything # Two habits that will save you a bad evening.\nPhotograph the wiring first. Take the base cover off, and before touching a single connector, take clear, well-lit photos of the whole board from directly above, then close-ups of each corner. When you are reconnecting an hour later and two black-and-red two-wire connectors look identical, those photos are the only source of truth you have.\nLabel the connectors. Masking tape and a pen. Hotend heater, hotend thermistor, part-cooling fan, hotend fan, bed heater, bed thermistor, X/Y/Z/E motors, X/Y/Z endstops, display. It takes five minutes and removes the entire category of mistakes where you plug the bed thermistor into the hotend thermistor input.\nAdditionally, unplug the printer from the wall and give the PSU a minute to bleed down before you start. And while you are in there, do not touch the little voltage-selector switch on the power supply if yours has one; it is set for your country\u0026rsquo;s mains voltage and nothing about this upgrade requires changing it.\nThe stock Creality board still in place, photographed before anything was disconnected. This is the reference photo you will want later. The swap, step by step # 1. Open the base # The mainboard lives in the enclosure under the printer. Lay the machine on its side, remove the screws around the cover plate, and lift it off. Everything from here is done with the printer unplugged.\n2. Disconnect everything from the stock board # Work around the board methodically rather than pulling connectors at random:\nSteppers: X, Y, Z and E. On an Ender 3 these are four identical four-pin JST connectors, which is exactly why you labelled them. Endstops: X, Y and Z, three two-pin connectors. Hotend: the heater cartridge (two thicker wires) and the thermistor (two thin wires). Fans: the part-cooling fan and the hotend fan. Bed: the heater wires (thick, usually screw terminals) and the bed thermistor. Display: the ribbon cable to the LCD. Power: the 24 V input from the PSU. Note the polarity now (it is printed on the board next to the terminal). Some connectors are held in by a small tab; pull on the plastic housing, never on the wires themselves.\n3. Unscrew and remove the old board # Four screws into the standoffs, then lift the board out. Keep the screws; you will reuse them.\n4. Mount the SKR Mini E3 # It goes onto the same standoffs with the same screws. If it does not line up, stop and check that you have the Ender 3 variant of the board rather than a different SKR model.\n5. Reconnect, using the silkscreen # Every port on the SKR Mini E3 is labelled on the PCB. Go through the same list as before, matching the label rather than the position: XM, YM, ZM, EM for the motors; X-STOP, Y-STOP, Z-STOP for the endstops; HE0 and TH0 for the hotend heater and thermistor; FAN0 and FAN1 for the part-cooling and hotend fans; HB and TB for the bed; EXP1 for the display.\nDo the 24 V input last, and double-check the polarity against what you noted in step 2. This is the one connector that can destroy the board if it goes on backwards.\nA note on the extruder motor. On this board the extruder connector ends up electrically reversed compared to the stock Creality wiring. Do not fix this by re-crimping the connector or flipping wires around. It is fixed in firmware with a single line, #define INVERT_E0_DIR true, which is part of the configuration in part 4. If you flash BIGTREETECH\u0026rsquo;s prebuilt firmware and the extruder runs backwards, this is why.\nThe SKR Mini E3 V2.0 mounted on the stock standoffs with everything reconnected, before the cover goes back on. Getting firmware onto it # A new SKR Mini E3 does not arrive knowing it is attached to an Ender 3. It needs firmware, and the flashing process is refreshingly simple:\nFormat a microSD card as FAT32. Cards of 32 GB or smaller are the safe choice; some boards are picky about larger ones. A 4096-byte allocation unit size is the usual recommendation. Copy a file named exactly firmware.bin to the root of the card. Not in a folder. With the printer powered off, insert the card into the board\u0026rsquo;s microSD slot. Power on and wait. The board reads the file, writes it to flash, and renames the file on the card to FIRMWARE.CUR to mark it as consumed. The LCD comes up when it is done. That rename is your success indicator. If the file is still called firmware.bin after a power cycle, the board never read it: wrong format, wrong filename, wrong card, or the file was in a subfolder.\nBIGTREETECH ships prebuilt binaries in their repository, under firmware/V2.0/, including firmware.bin, firmware-bltouch.bin and firmware-bltouch-for-z-homing.bin. They are a perfectly reasonable way to confirm the board works the first time you power it up, and a useful fallback if a build of your own misbehaves. But they are generic, and they will not know your probe offsets, your bed size or your preheat temperatures. Compiling your own is the point of part 4.\nFirst power-on: check before you print # Although it is tempting to load filament and start a Benchy right away, resist the urge. Do this instead, with the cover still off so you can reach the power switch quickly:\nPower on with nothing heating. The display should light up and show the usual Marlin status screen. If it stays dark, power off immediately and check the display ribbon and the 24 V connector.\nMove each axis from the menu. Small moves, 10 mm at a time. Each axis should move in the direction you asked. If an axis moves the wrong way, that is a firmware direction setting, not a wiring fault.\nTest the endstops. Connect over USB with a terminal (Pronterface, OctoPrint, or the serial monitor in VS Code) and send:\nM119 You get a report of every endstop\u0026rsquo;s state. Trigger each one by hand and send M119 again; the corresponding line should flip between open and TRIGGERED. If an endstop reports TRIGGERED when nothing is touching it, check that connector before you ever home the machine.\nThen home. G28, watching the machine with a finger over the power switch. Homing is when a wrong endstop or a wrong direction turns into a crashed axis.\nCheck the thermistors before the heaters. With everything cold, the hotend and bed temperatures on the display should both read something close to room temperature. If either reads a wild number, you have the thermistors swapped or a bad connection, and you do not want to find that out by turning on a heater.\nOnly once all of that is clean, put the cover back on and print something small.\nWhat you gain, and what is next # Immediately after the swap, two things change: your printer gets dramatically quieter, and flashing firmware becomes a drag-and-drop operation instead of a soldering project. That second one is what actually matters, because it turns the firmware from something fixed into something you can keep iterating on. When I did this swap, that shift was the whole point: it is what made the next two upgrades in this series possible in the first place.\nAdditionally, you now own a board with headroom you did not have before: twice the flash, a dedicated probe header, and drivers you control from software instead of a screwdriver. That headroom is exactly what the rest of this series spends. Part 3 installs a BLTouch probe on the hotend using a printed mount, wires it into the dedicated probe header this board provides, and works out the probe offsets. Part 4 sets up VS Code with PlatformIO and Auto Build Marlin, and walks through every configuration change needed to compile firmware that knows about all of it.\n","date":"12 January 2023","externalUrl":null,"permalink":"/posts/ender-3-skr-mini-e3-motherboard-upgrade/","section":"Posts","summary":"Is your early Ender 3 the loudest thing in the room every time it starts a layer, or have you hit a wall trying to add features that the stock firmware simply will not fit? Both problems trace back to the same part: the mainboard Creality shipped it with. That whine is not a quirk of cheap printers in general. It is a very specific consequence of the drivers soldered onto that board, and it is only one of several things about it that quietly limit what the machine can do.\n","title":"Ender 3 Motherboard Upgrade: Installing a BIGTREETECH SKR Mini E3 V2.0","type":"posts"},{"content":"","date":"23 de April de 2022","externalUrl":null,"permalink":"/es/tags/armado-de-pc/","section":"Tags","summary":"","title":"Armado-De-Pc","type":"tags"},{"content":"","date":"23 April 2022","externalUrl":null,"permalink":"/tags/gaming-pc/","section":"Tags","summary":"","title":"Gaming-Pc","type":"tags"},{"content":"Are you planning to build your own PC instead of buying a prebuilt one? If something is holding you back, it is probably not the screwdriver work. It is standing in front of a wall of parts and not knowing which ones actually work together.\nThat is genuinely good news, because compatibility is not a matter of luck, intuition, or years of experience. Every manufacturer publishes exactly what its part works with, which means the whole question can be settled on paper before you spend anything. A processor, a motherboard, memory, a graphics card, storage, a power supply, and a case all have to agree with each other on socket, chipset, form factor, and power. Get that agreement right, and the physical assembly turns out to be the easy part.\nThis guide is the component checklist I used to plan and build my own machine, an Intel Core i5-12600K paired with an RTX 3070 Ti. It runs in the order you should actually buy in, and I use that build throughout as a concrete example of every rule in action. By the end you will not only have a parts list, you will understand why each part is on it.\nInside the finished build: the AORUS Z690 motherboard, the Hyper 212 air tower, the Vengeance RGB Pro memory, and the RTX 3070 Ti, all lit up. Why Compatibility, Not Assembly, Is the Real Challenge # Every component in a PC has to satisfy the requirements of its neighbors. The processor dictates the socket and chipset the motherboard needs. The motherboard dictates which memory type is even possible. The graphics card dictates how much power the supply has to deliver. The case dictates how large everything else is allowed to be.\nNone of that is guesswork. Manufacturers state exactly what is compatible with what, and once you know where to look, choosing parts becomes a series of small, verifiable decisions instead of a leap of faith. That is the mindset this guide is built around: read the specification, confirm the match, move to the next component.\nBecause each choice narrows the next one, the decisions come in a natural order:\nThe processor, which fixes the socket and the chipset generation. The motherboard, which fixes the memory type and the physical size. The graphics card, which fixes how much power the supply has to deliver. The case and the cooling, which fix what physically fits and how well it breathes. Although it is tempting to start with the graphics card, since that is the part everyone talks about, starting at the processor is what keeps you from backtracking later. Follow the chain in order and every component you add is already constrained by the ones before it.\nHow to Choose Every Component (and Why It Matters) # Processor: Let It Tell You the Chipset # Start with the processor, because it decides almost everything downstream. Every manufacturer states, right in the product specifications, which chipset generation the chip is compatible with. For Intel Core processors, that compatibility follows the socket and the chipset naming pattern: sockets like LGA 1151, LGA 1200, and LGA 1700 (12th generation), paired with chipsets that follow an H_x_10 / B_x_60 / Z_x_90 naming pattern, where x marks the generation. For AMD Ryzen, the equivalent pattern is A_x_20 / B_x_50 / X_x_70 on the PGA AM4 socket, with the caveat that the Ryzen 7000 series moved to an LGA socket and is no longer AM4-compatible.\nIn my build: I chose the Intel Core i5-12600K, a 12th-generation, unlocked chip on the LGA 1700 socket. Following the naming pattern above, that meant the motherboard had to be a 600-series board, in my case a Z690, the top tier of that generation (the Z tier in Intel\u0026rsquo;s own Z_x_90 pattern).\nMotherboard: Socket, Chipset, and Physical Size All Have to Match # A motherboard has to clear three checks at once: the socket has to match the processor, the chipset has to match the processor\u0026rsquo;s generation, and the physical size has to fit inside the case. Sizes run from largest to smallest: EATX, ATX, mini-ATX, mini-ITX.\nIn my build: I picked the Gigabyte Z690 AORUS ELITE AX DDR4, an ATX board built around the Z690 chipset for LGA 1700. There is a detail in that name worth pausing on: this board comes in both a DDR4 and a DDR5 variant, and the two are not interchangeable, which brings us straight to the next component.\nThe Z690 AORUS ELITE AX DDR4 next to the i5-12600K: an LGA 1700 socket paired with a matching 600-series chipset. RAM: DDR4 or DDR5, the Motherboard Decides # The processor does not choose your memory type, the motherboard does, and currently no board supports both DDR4 and DDR5 at once. That choice gets made the moment you pick the board, not the moment you buy the memory. Moreover, the motherboard\u0026rsquo;s specification sheet states the maximum speeds it can actually reach, including any overclocked (OC) profiles, so it is worth reading before you pay for speed the board cannot use.\nIn my build: because I chose the DDR4 variant of the Z690 AORUS ELITE AX, my memory had to be DDR4 as well: Corsair Vengeance RGB Pro DDR4, 2 x 8 GB for 16 GB total, at 3600 MHz, and Intel XMP certified so the board can hit that rated speed without manual tuning.\n16 GB (2x8) of Corsair Vengeance RGB Pro DDR4 at 3600 MHz, matched to the motherboard\u0026rsquo;s memory type. Storage: M.2 NVMe or SATA # Modern storage mostly comes down to M.2 SSDs, which can be either SATA or NVMe (Non-Volatile Memory Express), alongside the older SATA 3 SSD form factor. Every M.2 slot is physically compatible with every PCIe generation, but check the exact generation your board supports, because that is what determines whether you actually get full performance out of the drive or just its floor speed.\nIn my build: I used a WD_BLACK SN850, a 1 TB NVMe SSD on PCIe Gen4, rated up to 7000 MB/s read. Matching a Gen4 drive to a board with Gen4 M.2 support, rather than to an older Gen3 slot, is exactly the kind of check that decides whether that rated speed is real or merely theoretical. Further down, in the measured results, I put that rated number to the test on this exact drive.\nThe WD_BLACK SN850, a 1 TB PCIe Gen4 NVMe SSD. Graphics Card: Let It Size Your Power Supply # The graphics card connects through a PCI Express slot (3.0, 4.0, and so on), and here is a detail that saves a lot of headaches: every PCIe generation is backward compatible, so a newer card will run fine in an older slot. The number that actually matters for your build is power. Once you know the card\u0026rsquo;s peak power draw, the rule of thumb I follow is simple: the power supply should be rated at at least double that peak draw. A card with a 200 W peak, for example, calls for a supply of at least 400 W.\nIn my build: I chose a ZOTAC GAMING GeForce RTX 3070 Ti Trinity OC, with 8 GB of GDDR6X memory. That card is what sized the power supply for everything else.\nThe ZOTAC RTX 3070 Ti Trinity OC, the component that set the power-supply requirement for the whole build. Power Supply: Leave Room to Grow # A good power supply is modular where possible, since that means individual cables you only plug in if you need them, cutting down on clutter inside the case. Internally, look for separate PCBs for better efficiency, and decide between a single-rail and a multi-rail design. Further, think past the current build: leaving headroom for expansion is what keeps a future upgrade from forcing you to buy a whole new supply.\nIn my build: I went with a Corsair RM750x, 750 W, 80 PLUS Gold certified, and fully modular. Following my own rule of doubling the peak draw, that comfortably clears the mark for the RTX 3070 Ti while leaving real room to grow.\nThe Corsair RM750x: 750 W, fully modular, 80 PLUS Gold. Cooling: Air or Liquid # Every build eventually asks the same question: air cooling or liquid? Both are valid answers, and the right one depends on the case, the processor, and how much you want to tinker with a loop.\nIn my build: I used a Cooler Master Hyper 212 RGB Black Edition, an air tower cooler that ships with its own RGB fan controller. It is the only CPU cooler in this machine, with no liquid loop involved.\nThe Cooler Master Hyper 212 RGB Black Edition, the air tower cooling the i5-12600K. Case: Fit, Airflow, and the Small Things That Add Up # A case has to satisfy more than looks: size, heat dissipation, and room for future expansion all matter. As a baseline, aim for a minimum of 2 fans, preferably 3 or 4. Before buying, confirm the graphics card and motherboard physically fit, and think ahead about temperatures once everything is installed. Beyond that, a few extras are worth prioritizing: removable GPU slot covers, proper cable management and protection, a tempered glass panel, dust filters, and a mid-tower form factor if you want the easiest balance of interior space and desk footprint.\nIn my build: everything went inside a Corsair iCUE 4000X RGB Tempered Glass Mid-Tower ATX, which houses the ATX motherboard with room to spare and checks essentially every item on that list.\nThe Corsair iCUE 4000X, fresh out of the box, tempered glass panel still taped. Fans and RGB: Airflow, Static Pressure, and Balanced # Fans are not all interchangeable. Beyond the obvious spec, size (commonly 120 mm or 140 mm), fans are built for one of three jobs: airflow fans that move a general volume of air, static pressure fans engineered to push air through a restriction like a radiator or a dense filter, and balanced fans that split the difference.\nIn my build: the iCUE 4000X came with three front-mounted Corsair SP120 RGB ELITE Performance PWM fans, and I added a pair of Corsair iCUE Lighting Node CORE controllers to bring all the RGB lighting, the fans, the memory, and the cooler, under one software-controlled system.\nThe three front RGB fans that came with the iCUE 4000X, visible through the glass. A Quick Connector Cheat Sheet # Before you start plugging anything in, it helps to recognize what you are looking at:\nEPS 8-pin: powers the processor, separate from the motherboard\u0026rsquo;s main power. CPU Fan / CPU Optional headers: either 4-pin PWM (speed-controlled by signal) or 3-pin (regulated by voltage). RGB header: white, either 4-pin at 12 V or 3-pin at 5 V, not interchangeable with each other. 24-pin: the main power connector for the motherboard itself. USB-C and USB 3.0: USB 3.0 uses a different physical connector from USB 2.0, so check your case\u0026rsquo;s front-panel cable before assuming it will fit. SATA: for SATA drives and some optical or legacy peripherals. PCIe 16x or 1x: 16x for the graphics card, 1x for smaller expansion cards. M.2 connector: for your NVMe or SATA M.2 SSD, mounted directly on the motherboard. The Finished Parts List: An i5-12600K and RTX 3070 Ti Build # Putting every decision above together, here is the complete parts list from this build, dated April 23, 2022:\nComponent What I chose Case Corsair iCUE 4000X RGB Tempered Glass Mid-Tower ATX (Black) CPU Intel Core i5-12600K (12th gen, LGA 1700, unlocked) Motherboard Gigabyte Z690 AORUS ELITE AX DDR4 (rev. 1.0) GPU ZOTAC GAMING GeForce RTX 3070 Ti Trinity OC, 8 GB GDDR6X RAM Corsair Vengeance RGB Pro DDR4, 2 x 8 GB (16 GB total), 3600 MHz, Intel XMP Storage WD_BLACK SN850 NVMe SSD, 1 TB, PCIe Gen4 PSU Corsair RM750x, 750 W, 80 PLUS Gold, fully modular CPU cooler Cooler Master Hyper 212 RGB Black Edition (air) Case fans Corsair SP120 RGB ELITE Performance PWM, 120 mm RGB controller Corsair iCUE Lighting Node CORE (x2) Every component unboxed and laid out before assembly began. Every line in that table traces back to a rule from the checklist above. The i5-12600K\u0026rsquo;s LGA 1700 socket forced a 600-series chipset, which is why the motherboard is a Z690. Choosing the DDR4 variant of that board is the reason the memory is DDR4 and not DDR5. The ATX motherboard fits an ATX mid-tower case. And the RM750x\u0026rsquo;s 750 W comfortably clears the \u0026ldquo;at least double the peak draw\u0026rdquo; rule for the RTX 3070 Ti. None of it is accidental, it is the same compatibility checklist applied one component at a time.\nAssembly and First Boot: What to Do When There Is No Video Signal # Once every part is verified on paper, the assembly itself is mostly patient screwdriver work. The moment that really tests you comes later, when you press the power button for the first time and the screen stays dark. That happened to me, and knowing in advance how to work through it is probably worth more to you than any other section of this guide.\nUpdate the BIOS Before You Trust the Board # Before anything else, I updated the motherboard\u0026rsquo;s BIOS. A firmware update is the one step that can, in principle, leave you with an expensive brick, so it deserves a deliberate decision instead of a reflex. In my case the risk was low, and for reasons I could verify in the board\u0026rsquo;s own specification: the Z690 AORUS ELITE AX supports Q-Flash Plus, which flashes the firmware directly from a USB stick with no processor, no memory, and no graphics card installed, and it carries DualBIOS, a second physical BIOS chip that can take over if the main one is damaged. A board that can recover from a failed flash without even a working CPU turns an intimidating step into a routine one.\nCheck for those two features on your own board before you start. If they are there, updating first is the cleaner order, because you begin from firmware that already knows about your processor instead of discovering a compatibility gap after everything is screwed in.\nThe First Power-On: No Video Signal # Then came the moment every first-time builder recognizes. I pressed the power button, the machine came to life, and the monitor answered with nothing at all: no image, just its red indicator light. No video signal.\nMy first suspicion was the graphics card. That is the natural reflex, since the GPU is the largest, most expensive, and most conspicuous part in the case, and it is the one the monitor cable plugs into. It is also, as it turned out, the wrong suspicion.\nIsolate One Variable at a Time # Instead of taking the whole machine apart at once, I changed a single variable. I removed the RTX 3070 Ti and booted on the integrated graphics of the i5-12600K, which took the most complex component out of the equation entirely. Then I reseated both memory modules in the same slots.\nIt booted. With a picture on screen and the cause identified, I reinstalled the graphics card and the build was complete.\nOnly then, with a machine that was known to be stable, did I go into the BIOS and enable XMP, the memory profile that lets the modules run at the speed they are rated for instead of the conservative default the board otherwise falls back to. That is the step that turns the 3600 MHz printed on the memory box into a number the system actually uses. Leaving it for the end is a good habit in general: get the machine booting reliably first, and tune it afterwards, so that if a setting does cause trouble you already know everything underneath it was fine.\nThat is the whole method, and it is worth internalizing: change one thing, test, and only then change the next. A first boot that fails is not a verdict on your build, it is a system with one unknown in it, and every component you can remove or re-test shrinks the search.\nTwo Lessons Worth Carrying Into Your Own Build # When a new build refuses to POST, suspect the memory before the graphics card. A module can look perfectly installed and still not be seated all the way down, and this is by far the most common cause of a dark screen on a first build. Reseating costs you five minutes, which makes it the cheapest hypothesis to eliminate. Notice, too, that the fix here was the same slots, not different ones: the problem was seating, not slot choice. A compatibility decision handed me a diagnostic tool I had not planned on. I chose the \u0026ldquo;K\u0026rdquo; version of the i5-12600K for its unlocked multiplier. Intel also sells a \u0026ldquo;KF\u0026rdquo; variant, the same processor without integrated graphics, and had I bought that one I would have had no way at all to get an image on screen without the very card I was trying to rule out. The integrated GPU became my second, independent path to a picture, which is exactly what let me isolate the problem in a single step. Although the benefit was accidental, the habit behind it is not: choosing the part that keeps more options open tends to pay you back in situations you did not anticipate. Measured Results: Temperatures, Power Draw, and Real Throughput # Planning a build on paper is one thing, and living with it is another. So if you are wondering what a machine assembled this way actually delivers, here are real measurements taken on this same computer, years after it was assembled and with the same air cooler it shipped with. Every figure below comes from the hardware\u0026rsquo;s own instrumentation under a controlled load, and I name the tool in each case, because a stated measurement method is what separates a measurement from a claim.\nOne more thing worth knowing before you read the numbers: this is a stock machine. The BIOS I flashed on the day I built it in 2022 is the same firmware it has been running ever since, and beyond enabling XMP I left the firmware alone. No overclocking, no tuning, just four years of ordinary use. That is precisely what makes these figures useful if you are planning a similar build, because they are what a comparable machine gives you out of the box rather than the reward of a tuning session.\nThe CPU: How the Hyper 212 Handles the i5-12600K # The test was a sustained load pinning all 16 threads of the i5-12600K at 100%, with the board\u0026rsquo;s sensors read by HWiNFO in sensors-only mode logging to CSV, sampled over a five-minute steady-state window (150 samples).\nIdle Sustained load Average core temperature 31.0 °C 54.6 °C Package power 23 W 98.8 W Around those two numbers, four results matter more than the temperatures themselves:\nThe hottest single core reached 62.8 °C on average, peaking at 64 °C. The i5-12600K\u0026rsquo;s TjMAX, the temperature at which it starts protecting itself, is 100 °C. With the cores averaging 54.6 °C, that leaves roughly 45 °C of thermal headroom (100 − 54.6 ≈ 45). Zero thermal throttling and zero power-limit events across all 150 samples. The processor was never once forced to slow down. The P-cores held 4500 MHz for the entire window, with no drop. Sustained clocks are the real proof that a cooler is keeping up, because a cooler that cannot will show up as falling frequency long before it shows up as an alarming temperature. It returns to idle temperature in under a minute once the load is removed. In other words, a well-chosen air tower, not a liquid loop, was enough for this processor under a heavy real workload. That is a useful data point if you are weighing the same trade-off and wondering whether air cooling is a compromise.\nOne honest caveat about that load. The work was openssl speed rsa2048, forked once per thread: heavy, realistic, and entirely integer. It drew about 99 W, which is below the chip\u0026rsquo;s 125 W rating, so a Prime95 small-FFT run or an AVX-512 torture test would certainly run hotter. Read these numbers as sustained real load, not as a worst case, and treat any figure you find online the same way: a temperature without its workload attached does not mean very much.\nAnd one honest caveat about the board. HWiNFO reported both power limits, PL1 and PL2, as 4095 W, which is how it says there is no limit at all: the Gigabyte board ships with Intel\u0026rsquo;s power limits removed, even though the i5-12600K\u0026rsquo;s own specification is 125 W PBP and 150 W MTP. Under this workload it changed nothing, since the chip only ever asked for about 99 W, but under an AVX-heavy load it certainly would, and it is also part of why the \u0026ldquo;zero power-limit events\u0026rdquo; line above reads the way it does: there was no limit there to hit. If you build on a similar board, it is worth opening a monitoring tool once and checking whether the ceiling your processor runs against is Intel\u0026rsquo;s decision or your motherboard\u0026rsquo;s.\nThe GPU: The RTX 3070 Ti Under AI Inference # For the graphics card I used a load it now runs regularly, serving local language models through Ollama, and read the card\u0026rsquo;s own telemetry with nvidia-smi sampled during sustained inference.\nIdle Serving a model Temperature 41 °C 60 °C Power draw 12.6 W ~248 W (310 W limit) Under that load the card held between 1920 and 1935 MHz. Additionally, 60 °C on a GPU working steadily is a comfortable place to be, and it says as much about the case airflow, with three front fans feeding the card, as it does about the card\u0026rsquo;s own cooler.\nWas the 750 W Power Supply the Right Call? # This is where the measurements pay back the planning, because that 248 W figure is the missing input for the sizing rule this whole guide is built on. Adding the parts up:\n248 W measured at the graphics card under sustained load 150 W maximum turbo power for the i5-12600K ~50 W for everything else: the drive, the fans, the memory, the board itself That comes to roughly 450 W of estimated peak draw against a 750 W supply, which puts the machine at about 60% of the supply\u0026rsquo;s capacity at its busiest. Call this an estimate and nothing more, since I measured the components individually rather than the whole system at the wall socket, and a proper number would need a plug-in power meter. Even so, it confirms the rule: doubling the graphics card\u0026rsquo;s peak draw produced a supply with genuine headroom, enough for a future GPU upgrade without a second purchase.\nWhat That Hardware Actually Produces # Numbers about heat and watts are only interesting because of the work underneath them. Generating 200 tokens at temperature 0 through Ollama on that RTX 3070 Ti, with the throughput taken from Ollama\u0026rsquo;s own timing fields on its /api/generate endpoint (eval_count divided by eval_duration) rather than from a stopwatch or a third-party benchmark:\nModel Generation throughput llama3.2 194.9 tok/s qwen3.5:4b 97.1 tok/s qwen3.5 (6.6 GB) 22.9 tok/s The drop on the last one is the most instructive line in the table. A 6.6 GB model against 8 GB of VRAM leaves almost no room to work with, and the throughput falls off accordingly. It is the same lesson as every other section of this guide, arriving one more time: the specification you chose at purchase, in this case 8 GB of GDDR6X, is the ceiling you eventually meet in practice.\nStorage: What the SN850 Actually Delivers # The box promises up to 7000 MB/s read, and putting a number like that to the test takes one precaution. With 32 GB of RAM in the machine, an ordinary read would be answered by Windows\u0026rsquo; file cache, and I would have measured my memory instead of my drive. So I wrote and read a 4 GiB file on C:, which is the SN850 itself, with the cache bypassed (FILE_FLAG_NO_BUFFERING), forcing every request to reach the device. The method is what makes the figure real, and it is the difference between a benchmark and a number that flatters you.\nOperation Buffer Measured WD rated Sequential write 4 MiB 5,031 MB/s 5,300 MB/s Sequential read 1 MiB 4,098 MB/s n/a Sequential read 4 MiB 5,391 MB/s n/a Sequential read 16 MiB 5,841 MB/s 7,000 MB/s Two results in that table are worth more than the headline speed:\nThe buffer size swings the read by 43%. Going from a 1 MiB buffer to a 16 MiB one takes the drive from 4,098 to 5,841 MB/s (5,841 ÷ 4,098 ≈ 1.43). With small requests the bottleneck is not the drive at all, it is the latency of each individual request, so a benchmark that reports a \u0026ldquo;slow\u0026rdquo; NVMe may simply be asking for the data in pieces too small to keep the drive busy. Writes essentially reach specification, reads do not. The write lands at 95% of its rating (5,031 ÷ 5,300 ≈ 0.95), while the best read reaches 83% of the 7,000 MB/s on the box (5,841 ÷ 7,000 ≈ 0.83). The most likely reason is not the drive\u0026rsquo;s age: it was 97.3% full, with 25.5 GB free out of 930.5 GB. A nearly full SSD has no room left for its SLC cache and none for the garbage collector to work in, and both of those are what the rated figure assumes. If you take one practical habit from this section, make it that one: a nearly full SSD is a slower SSD, so leave it real free space. As for the drive\u0026rsquo;s condition after four years of daily use, its own health counters report 0% wear, 41 °C at the time of the test, and 84 °C as the highest temperature it has ever recorded. Endurance, at least on this workload, has not turned out to be the limiting factor.\nHow Long It Takes to Boot, and Where That Time Really Goes # Boot time is the specification everyone quotes and almost nobody measures properly, usually because a stopwatch and a bit of optimism are involved. Windows, however, keeps the record itself: the Microsoft-Windows-Diagnostics-Performance/Operational log writes an event 100 for every boot, with the phases already separated. Reading it back gave me 12 real boots between July 18 and August 11, 2026, no stopwatch anywhere.\nPhase Median Main path (power button to a usable desktop) 19.7 s Post-boot (startup programs loading afterwards) ~36 s Total 56.2 s Across those 12 boots the main path ranged from 17.3 to 38.4 s and the total from 44.1 to 76.5 s, with zero boot-degradation events recorded.\nThe interesting part is not the total, it is the split. The stretch that actually depends on the components this guide is about, the NVMe drive and the processor, is roughly 20 seconds. The other 36 seconds are programs I installed loading after the desktop has already appeared. In other words, this machine is not slow to boot because of the hardware I chose so carefully, it is slow to boot because of what I put on top of it. That is worth remembering the next time a computer feels sluggish at startup: the parts list is rarely the culprit, and the startup list usually is.\nWhat You Gain by Building It Yourself # The finished machine is only half of the reward. The other half is that you come out the other side actually understanding your own computer, first-hand rather than from a spec sheet: why a socket has to match a chipset, why memory type is a motherboard decision instead of a preference, and why a power supply\u0026rsquo;s wattage is an engineering constraint instead of a number to eyeball.\nConcretely, working through this checklist gives you four things that a prebuilt machine rarely does:\nA computer matched to what you actually do, because you chose every part against your own workload instead of a marketing bundle. Knowledge that transfers, since reading a specification sheet and verifying a claim is the same skill whether the part is a motherboard, a microcontroller, or a sensor. An upgrade path you designed on purpose, from the spare wattage in the supply to the free slots on the board. The confidence to open it again, because a machine you assembled is a machine you can diagnose, clean, and repair. For me, applying my own checklist to a real purchase (an i5-12600K, a Z690 board, DDR4 memory, and a power supply sized correctly for the RTX 3070 Ti) was the proof that the method works end to end and not just on paper. Every component arrived already verified against its neighbors, so nothing in that box was ever a gamble.\nThe Trade-offs Worth Naming # No build is a set of purely optimal choices, and being honest about the trade-offs is part of the engineering:\nDDR4 instead of DDR5. The same Z690 AORUS ELITE AX exists in a DDR5 variant. Choosing the DDR4 version meant building on the mature, widely available memory standard of the moment, at the cost of not being on the newer platform. Air cooling instead of a liquid loop. The Hyper 212 is a simple, low-maintenance tower with no pump and no coolant to worry about, and it is a different design point from a liquid loop. A supply sized for the future, not just for today. Doubling the peak draw means paying for wattage the current build does not use, and that headroom is exactly what makes a future upgrade a component swap instead of a second purchase. A mid-tower instead of a compact case. It occupies more desk space than a mini-ITX build, and in exchange it gives room for the ATX board, the air tower, the front fan array, and whatever comes next. What This Project Taught Me # What stayed with me is not the parts list, it is the habit behind it: go to the primary source, verify the claim, and only then commit. Additionally, this build was a good reminder that a computer is a system rather than a pile of parts, and that the interesting constraints always live at the interfaces between components, in the socket, the memory standard, the power budget, and the physical volume of the case. That is the same instinct I bring to embedded systems and to any project where curiosity, patience, careful reading, and a willingness to be wrong on paper before being wrong with money all pay off. Each mistake caught in the planning stage is simply one you never have to pay for in hardware.\nThe finished machine, assembled and running: the three front intake fans, the Hyper 212 tower, and the RGB memory, all visible through the glass. What I\u0026rsquo;d Explore Next # Overclocking the i5-12600K, since it is an unlocked K-series chip and the Z690 AORUS ELITE AX supports it. A DDR5 platform on a future build, now that DDR5 boards and memory have matured since this one. Deeper cable management, using the iCUE 4000X\u0026rsquo;s routing channels to get the interior as clean as the RGB lighting deserves. Fine-tuning the fan curve and RGB lighting through Corsair\u0026rsquo;s iCUE software, now that the Lighting Node CORE controllers are in place. Getting the memory back to its rated speed. I have since added a second pair of sticks from a different kit, and the board settled all four at the slower kit\u0026rsquo;s profile instead of the 3600 MHz the Corsair pair is rated for. Mixing kits costs you the faster one\u0026rsquo;s speed, which is a compatibility rule I learned after this build rather than during it. The clean fix is a single matched kit rather than two that merely coexist. Once your machine is built, the more interesting question is what you point it at. This exact machine is the one running my private AI assistant: the same RTX 3070 Ti I sized the power supply around also serves local language models, entirely on my own hardware and without sending a single prompt to somebody else\u0026rsquo;s server. I walked through that whole setup in Deploy Your Private AI with Ollama.\nIf you are planning your own build, I would love to hear which components you are weighing against each other, and why.\n","date":"23 April 2022","externalUrl":null,"permalink":"/posts/build-your-own-pc/","section":"Posts","summary":"Are you planning to build your own PC instead of buying a prebuilt one? If something is holding you back, it is probably not the screwdriver work. It is standing in front of a wall of parts and not knowing which ones actually work together.\n","title":"How to Build Your Own PC: A Component Compatibility Guide","type":"posts"},{"content":"","date":"23 April 2022","externalUrl":null,"permalink":"/tags/pc-building/","section":"Tags","summary":"","title":"Pc-Building","type":"tags"},{"content":"","date":"23 de April de 2022","externalUrl":null,"permalink":"/es/tags/pc-gamer/","section":"Tags","summary":"","title":"Pc-Gamer","type":"tags"},{"content":"","date":"29 November 2020","externalUrl":null,"permalink":"/tags/arduino/","section":"Tags","summary":"","title":"Arduino","type":"tags"},{"content":"","date":"29 November 2020","externalUrl":null,"permalink":"/tags/biomedical-engineering/","section":"Tags","summary":"","title":"Biomedical-Engineering","type":"tags"},{"content":"Have you ever wondered what actually happens inside the monitor hanging above a hospital bed, the one drawing a green heartbeat line while a number blinks in the corner?\nBehind that screen there is a chain of sensors, amplifiers, filters, and deliberate design decisions. You can build a simplified version of that chain yourself, and by the end of it you will understand bedside monitors in a way no datasheet or lecture can teach you.\nThat is what this guide is for. You will acquire three physiological signals at the same time (electrocardiogram, photoplethysmography, and temperature), condition each one in the analog domain, and draw all three live on a single TFT-LCD, the way a real monitor does. Many Arduino health projects lean on ready-made sensor modules that hide the analog work. This build does not: the front end is made of discrete parts, so every gain, every cutoff, and every offset is a decision you get to make and defend.\nI built this version for BI3014.1, the Biomedical Technologies Lab at Tecnológico de Monterrey, Campus Ciudad de México. Everything below is the design that ended up working, including the obstacles that shaped it.\nOne clarification matters more than any circuit in this post: this is a didactic prototype built for a university lab, not a medical device. It was never intended, tested, or certified for patient diagnosis or monitoring, and it should not be used for that purpose. What it is genuinely good for is understanding, hands-on, how a vital signs monitor is put together.\nThe three signals running simultaneously on the TFT-LCD: temperature in yellow, photoplethysmography (PLETH) in cyan, and the electrocardiogram (ECG) in green. What a vital signs monitor actually does # Before designing anything, it helps to know what you are imitating.\nCENETEC (Mexico\u0026rsquo;s National Center for Health Technology Excellence) defines a vital signs monitor as a device that allows a patient\u0026rsquo;s physiological parameters to be continuously detected, processed, and displayed. In practice, a monitor acquires a signal from the body, amplifies it, conditions it, and renders it on a screen.\nMost clinical monitors are modular, so a physician can choose which variables to track: ECG, respiratory rate, non-invasive or invasive blood pressure, temperature, oxygen saturation (SpO2), venous oxygen saturation, cardiac output, carbon dioxide, intracranial pressure, or airway gas pressure during anesthesia. By mobility they split into fixed units (anesthesia, adult, neonatal) and transport units for moving patients within a hospital, between hospitals, or inside an ambulance.\nStructurally, though, every one of them comes down to four pieces: a power supply, an information-processing unit, a display, and one acquisition module per parameter. That structure is the blueprint you are about to scale down to something an Arduino can handle.\nThe architecture: five blocks, not one tangled circuit # Organize the project as five blocks: three signal-acquisition blocks (ECG, PLETH, and temperature), one integration block built around an Arduino MEGA 2560, and one visualization block, a TFT-LCD screen.\nThe ECG and PLETH blocks each go through three stages: signal acquisition, analog filtering and conditioning, and analog-to-digital conversion. Temperature skips the middle stage entirely and goes straight from the sensor to the ADC, because the LM35 already outputs a clean, linear signal that needs no conditioning.\nThinking in blocks rather than in one tangled schematic was the single most useful engineering decision I made, and it is the one I would recommend most strongly. It lets you design, build, and debug each acquisition chain on its own before worrying about how they will share one Arduino and one screen.\nThe blocks did not go straight to the breadboard either. I simulated the circuits in Proteus and Tinkercad first, and once they were built I verified them on the bench with an oscilloscope and a multimeter. Simulating before soldering and measuring after assembling is a habit worth keeping in any analog build: the simulation is cheap to iterate on, and the bench instruments are what tell you what your circuit is actually doing rather than what you assumed it would do.\nThe five-block architecture: three acquisition chains feeding into the Arduino MEGA 2560, which drives the TFT-LCD. What you need # Arduino MEGA 2560, the integration block that reads every channel and drives the display. A TFT-LCD screen with an HX8357 controller, for the visualization block. Ag/AgCl electrodes and lead wires, for the ECG. An AD620 instrumentation amplifier, for the ECG front end. An IR333C infrared LED and a PT333-3B phototransistor, for the PLETH front end. A TL081 operational amplifier, for the PLETH conditioning stage. An LM35 temperature sensor. Two 9 V batteries, for the bipolar supply the instrumentation stage needs. A second Arduino UNO, used purely as a 5 V supply (more on why below). The three signal chains at a glance # Signal Transducer Amplification Filtering Arduino pin ECG Ag/AgCl electrodes, Einthoven\u0026rsquo;s lead II AD620, gain 1000 V/V, 2.5 V offset Bandpass, 1.2 to 150 Hz A12 PLETH IR333C LED and PT333-3B phototransistor TL081 stage, gain 180 (set experimentally) Bandpass, 1.2 to 150 Hz A8 Temperature LM35 None, the sensor output is already usable None A10 The ECG chain: turning millivolts into something an ADC can see # Electrocardiography measures the electrical activity of the heart, and it starts with the transducer: a pair of Ag/AgCl (silver / silver-chloride) electrodes that convert the physiological signal into an electrical one. I used Einthoven\u0026rsquo;s lead II configuration for the acquisition.\nHere is the problem you have to solve. A typical adult ECG has an amplitude of only 0.5 to 4 mV, over a frequency range of 0.05 to 150 Hz. Feed that straight into an Arduino and you will see nothing but noise, because the signal is thousands of times smaller than the ADC\u0026rsquo;s step size.\nThe amplification comes from an AD620 instrumentation amplifier, which reads the signal differentially straight from the electrodes. Reading differentially matters: it is what rejects the interference that both electrodes pick up in common, which in a room full of mains wiring is most of what they pick up. I set the gain to 1000 V/V with a 2.5 V offset, so the amplified signal sits comfortably between 0 and 5 V, centered inside the Arduino\u0026rsquo;s input range instead of clipping against ground.\nAfter amplification, the signal passes through a bandpass filter from 1.2 to 150 Hz to remove baseline drift and high-frequency noise, and the conditioned signal is read on analog pin A12.\nThe ECG acquisition circuit: AD620 instrumentation amplifier, electrodes, and lead wires on the breadboard. The PLETH chain: reading a pulse with light # Photoplethysmography measures pulsatile changes in blood volume, which lets you recover heart rhythm optically instead of electrically. The transducer is an IR333C infrared LED shining through a capillary bed (a fingertip, in this case), paired with a PT333-3B phototransistor on the other side that detects the transmitted light and turns it into an electrical signal. With every heartbeat, arterial pulsations change how much light is absorbed, and that variation is the signal you are after.\nI used the phototransistor in a common-emitter configuration, feeding a conditioning stage built with a TL081 op-amp. The gain, 180, was determined experimentally, tuned for a clear trace on the display rather than derived analytically.\nTwo honest trade-offs are worth naming here, because they are the kind of decision that separates a working prototype from a wrong one:\nPLETH is qualitative here, by choice. So many external factors affect light absorption that it is a poor tool for measuring absolute blood volume. I treated it strictly as a qualitative indicator of blood flow, which is exactly enough to visualize a pulse waveform and no more than the setup can honestly support.\nThe filter band is shared, not optimal. PLETH is normally filtered between 0.5 and 5 Hz. For simplicity I filtered it in the same 1.2 to 150 Hz band used for the ECG, reusing one filter design across both acquisition chains. That costs some noise rejection and buys a much simpler build, which was the right call for a lab prototype. The signal is read on analog pin A8.\nThe PLETH circuit: an IR333C LED and PT333-3B phototransistor mounted in a finger clip, wired to the TL081 conditioning stage. The temperature chain: the one that stays simple # Temperature is the simplest of the three chains by design, and that is a feature. I used an LM35 sensor, chosen for its precision, its working range, and its linear response, which removes the need for any calibration curve. Its accuracy is roughly 0.25 °C, which is actually finer than what the Arduino resolves: with the default 5 V reference, one step of the 10-bit ADC is about 4.88 mV, and at the LM35\u0026rsquo;s 10 mV/°C that step works out to roughly 0.49 °C. In other words, the conversion sets the resolution of the measurement, not the sensor, and you can read that resolution straight out of the code: the 500.0 / 1023 constant in the sketch below is the step size itself, 0.489 °C per count. That is the intended outcome rather than a compromise, since half a degree per step is more than enough for human body temperature, the one quantity this monitor exists to display, and a reading shown with a single decimal over the clinically interesting range asks for nothing finer. More ADC bits would only have resolved digits that the sensor\u0026rsquo;s own accuracy does not back up, which is false precision: numbers that look more certain than the measurement truly is.\nBecause the LM35 already outputs a clean analog voltage proportional to temperature, it skips filtering and conditioning entirely and is read directly on analog pin A10. The conversion from raw ADC counts to degrees Celsius is the single line you will find in the sketch below:\nT = LM35 * (500.0 / 1023) where LM35 holds the number of steps measured by the 10-bit ADC. The 500 is not a magic number either: at the 5 V analog reference, full scale corresponds to 5000 mV, and since the LM35 outputs 10 mV/°C, that full scale spans 500 °C. Dividing those 500 °C by the 1023 counts of the ADC is what turns raw counts into degrees Celsius, which is the same arithmetic as the step size above seen from the other direction: the reference voltage and the sensor\u0026rsquo;s output scale folded together into a single figure.\nOne small refinement makes the reading pleasant to look at. Raw sample-to-sample variation makes the number on screen jitter constantly, so the displayed value is the average of the last five measured temperatures, printed with one decimal place. That average is refreshed five times per program cycle: in the sketch, the temperature branch runs whenever the column counter x1 is a multiple of 63, which lands at 0, 63, 126, 189 and 252 as the trace sweeps across the screen. Those are two different fives, worth keeping apart: one is how many samples go into the average, the other is how often the result is redrawn.\nThe LM35, wired directly into the Arduino MEGA with no intermediate conditioning stage. Power: the problem I did not see coming # Powering three very different acquisition chains from one board turned out to be trickier than any of the filter designs, and it is where most of my lab time went.\nThe AD620 instrumentation stage needed a bipolar supply, so I powered it from two 9 V batteries wired as a -9 V to +9 V split rail. The LM35, the infrared LED, the phototransistor, and the ECG circuit\u0026rsquo;s 2.5 V summing stage all needed a stable 5 V, but a single Arduino could not source enough current for all of them at once. The fix was to add a second Arduino UNO dedicated purely to supplying that 5 V rail, which solved the problem cleanly if not elegantly. The TFT-LCD shield, finally, runs off the 3.3 V rail of the Arduino MEGA itself.\nEarly on I also tried powering the whole setup from a single bench supply with multiple voltage rails. It did not work. Mains noise from the wall supply bled into the signals in a way the batteries never did, most likely because of the notch filters: they were part of the design from the beginning, but they were never correctly calibrated, since I did not have the equipment on hand to characterize them. Going back to battery power for the sensitive analog stages fixed it immediately.\nIf you are building your own front end, take that as a shortcut rather than a warning. There is a reason so much biomedical instrumentation still leans on isolated, battery-backed supplies for the input stage, and you will feel that reason the first time you watch 60 Hz ride on top of a QRS complex.\nRendering three signals at once on a TFT-LCD # The visualization block is a TFT-LCD built around an HX8357 controller, driven with the Adafruit_GFX and Adafruit_TFTLCD libraries. Getting there was its own project.\nThere is no single well-documented library that works across every variant of this screen, and the controller most often referenced online is the ILI9341, not the HX8357 I actually had. Identifying which controller was on my specific board, before anything would draw correctly, was one of the harder debugging sessions of the whole build. If your screen shows nothing but white, suspect the controller before you suspect your wiring.\nOnce the display is talking to the Arduino, the rendering logic is refreshingly simple. PLETH and ECG are drawn as live scrolling graphs: for each new sample, the program draws a straight line from the previous sample\u0026rsquo;s position to the current one. When the trace reaches the right edge of the screen, both graphs are erased and drawing restarts from the left edge, which marks the end of one program cycle. Temperature, being a single number rather than a waveform, is printed as text.\nOn screen, temperature appears in yellow in the top-right corner, PLETH in cyan in the upper half, and ECG in green in the lower half. That color scheme is not decoration: it is what lets the layout be read at a glance, the same way a real monitor is designed to be read.\nHere is the complete Arduino sketch, TFTLCDemi.ino. The in-code comments are in Spanish, since that is how I originally wrote it, and I left the working code untouched rather than tidying it after the fact:\n#include \u0026lt;Adafruit_GFX.h\u0026gt; // Core graphics library #include \u0026lt;Adafruit_TFTLCD.h\u0026gt; // Hardware-specific library #define LCD_CS A3 // Chip Select goes to Analog 3 #define LCD_CD A2 // Command/Data goes to Analog 2 #define LCD_WR A1 // LCD Write goes to Analog 1 #define LCD_RD A0 // LCD Read goes to Analog 0 #define LCD_RESET A4 // Can alternately just connect to Arduino\u0026#39;s reset pin // For the Arduino Mega, use digital pins 22 through 29 // (on the 2-row header at the end of the board). // D0 connects to digital pin 22 // D1 connects to digital pin 23 // D2 connects to digital pin 24 // D3 connects to digital pin 25 // D4 connects to digital pin 26 // D5 connects to digital pin 27 // D6 connects to digital pin 28 // D7 connects to digital pin 29 //Variables int Ox[316]; int ECG[316]; int x1 = 0; //Contador //Temperatura int LM35; float TEMPERATURA; float T1 = 0; float T2 = 0; float T3 = 0; float T4 = 0; #define BLACK 0x0000 #define BLUE 0x001F #define RED 0xF800 #define GREEN 0x07E0 #define CYAN 0x07FF #define MAGENTA 0xF81F #define YELLOW 0xFFE0 #define WHITE 0xFFFF Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET); void setup() { // put your setup code here, to run once: Serial.begin(9600); tft.reset(); tft.begin(0x8357); tft.setRotation(1); // establece posicion vertical tft.fillScreen(BLACK); // fondo de pantalla de color negro tft.setTextColor(YELLOW, BLACK); // texto en color amarillo tft.setTextSize(3); // escala de texto en 3 tft.setCursor(270, 30); // ubica cursor tft.print((char)247); tft.setTextSize(4); // escala de texto en 4 tft.setCursor(290, 30); // ubica cursor tft.print(\u0026#39;C\u0026#39;); // tft.fillRect(0, 0, tft.width(), 20, CYAN); // rectangulo azul naval a modo de fondo de titulo // tft.setTextColor(WHITE); // color de texto en blanco // tft.setTextSize(2); // escala de texto en 2 // tft.setCursor(25, 6); // ubica cursor // tft.print(\u0026#34;Panel de control\u0026#34;); // imprime texto // tft.setCursor(0, 35); // ubica cursor // tft.print(\u0026#34;Zona: 1\u0026#34;); // imprime texto // tft.setCursor(0, 55); // ubica cursor // tft.print(\u0026#34;Temperatura Humedad\u0026#34;); // imprime texto // tft.drawLine(0, 170, 240, 170, RED); // linea horizontal de color rojo // tft.setCursor(0, 185); // ubica cursor // tft.print(\u0026#34;Zona: 2\u0026#34;); // imprime texto // tft.setCursor(0, 205); // ubica cursor // tft.print(\u0026#34;Temperatura Humedad\u0026#34;); // imprime texto } void loop() { tft.fillRect(0, 70, tft.width(), tft.height() - 60, BLACK); // Se borra el display tft.setTextColor(CYAN, BLACK); // texto en color amarillo tft.setTextSize(1); // escala de texto en 3 tft.setCursor(10, 60); // ubica cursor tft.print(\u0026#34;PLETH\u0026#34;); tft.setTextColor(GREEN, BLACK); // texto en color amarillo tft.setTextSize(1); // escala de texto en 3 tft.setCursor(10, 170); // ubica cursor tft.print(\u0026#34;ECG\u0026#34;); while ( x1 \u0026lt; 315 ) { // Leer temperatura if (x1 % 63 != 0) { LM35 = analogRead(10); TEMPERATURA = (LM35 * 500.0) / 1023; //Fórmula para calcular la temperatura // SUMA = TEMPERATURA + SUMA; T4 = T3; T3 = T2; T2 = T1; T1 = TEMPERATURA; delay(1); } else { LM35 = analogRead(10); TEMPERATURA = (LM35 * 500.0) / 1023; //Fórmula para calcular la temperatura // SUMA = TEMPERATURA + SUMA; TEMPERATURA = (TEMPERATURA + T1 + T2 + T3 + T4) / 5; //Escribir en TFTLCD tft.setTextColor(YELLOW, BLACK); // texto en color amarillo tft.setTextSize(4); // escala de texto en 4 tft.setCursor(170, 30); // ubica cursor tft.print(TEMPERATURA, 1); //Temperatura con 1 decimal delay(2); // SUMA = 0; //Se borra la suma de las temperaturas } //Leer ECG ECG[x1 + 1] = analogRead(12); ECG[x1 + 1] = map(ECG[x1 + 1], 0, 1023, 0, 50); tft.drawLine( x1 + 4, 230 - ECG[x1], x1 + 5, 230 - ECG[x1 + 1], GREEN); delay(10); //Leer Pulsímetro Ox[x1 + 1] = analogRead(8); Ox[x1 + 1] = Ox[x1 + 1] * 6; if (Ox[x1 + 1] \u0026gt; 1023) { Ox[x1 + 1] = Ox[x1]; } else { Ox[x1 + 1] = map(Ox[x1 + 1], 0, 1023, 0, 50); tft.drawLine( x1 + 4, 120 - Ox[x1], x1 + 5, 120 - Ox[x1 + 1], CYAN); } // Ox[x1 + 1] = analogRead(8); // Ox[x1 + 1] = Ox[x1 + 1] * 6; // Ox[x1 + 1] = map(Ox[x1 + 1], 0, 1023, 0, 50); // tft.drawLine( x1 + 4, 120 - Ox[x1], x1 + 5, 120 - Ox[x1 + 1], CYAN); x1++; delay(10); // demora de 10 mseg. } ECG[0] = ECG[x1]; //Se guarda el último valor medido Ox[0] = Ox[x1]; x1 = 0; } One timing detail that will save you hours # The Arduino\u0026rsquo;s ADC has a conversion time of 13 clock cycles, and each analogRead() call needs that time to settle before you switch to the next channel. Skip that wait, or read channels faster than the ADC can multiplex between them, and you get incorrect readings or crosstalk between channels, where one signal visibly bleeds into another. That is why the delay() calls are sprinkled through the acquisition loop above, and it is the first thing to check if your ECG trace starts pulsing in time with your PLETH.\nWhat you end up with # The payoff is a screen that behaves like the thing you set out to imitate: three physiological variables, updating live, readable at a glance.\nIn my build, all three signals appeared simultaneously on the TFT-LCD, which was the entire point. Room-temperature readings from the LM35 hovered between 23.9 °C and 25.8 °C, matching what I measured independently through the serial monitor. The ECG trace showed clean, recognizable QRS complexes, and the PLETH trace showed clear pulsatile waveforms in sync with every heartbeat.\nThe complete build: two Arduinos, two breadboards, both battery rails, and the TFT-LCD, all working together to show three signals at once. Beyond the screen, you walk away with something more portable than the prototype: the ability to take a signal that exists in the physical world and carry it end to end into a display, making a defensible decision at every stage.\nLessons worth carrying to your next project # Integration is the hard part, not any single circuit. With five interdependent blocks, a failure anywhere breaks the whole system, so every connection has to be made deliberately and verified before you move on. Debugging a finished system that has never worked once is far harder than validating each block as you add it.\nDatasheets beat forum posts when the hardware is ambiguous. Identifying an undocumented display controller, planning a power budget across multiple rails and two boards, and adapting signals for a peripheral the Arduino was never designed to drive natively: none of that came from a single tutorial. It came from reading closely and working through each obstacle as it appeared, which is honestly most of what engineering is.\nChoosing the harder display was the right trade-off. A simple numeric display would have worked sooner. The TFT-LCD took far longer to get running, but it let me show two full waveforms in different colors alongside a numeric readout, so the result actually resembles a vital signs monitor instead of a row of numbers. When a project\u0026rsquo;s goal is to teach you what a real system feels like, pick the component that gets you closest to the real system.\nEvery setback here, the noisy bench supply, the silent screen, the Arduino that could not source enough current, ended up teaching more than the parts that worked on the first try.\nWhere to take it next # A properly calibrated notch filter, characterized with the right test equipment, so a bench supply becomes viable and the analog front end no longer depends on batteries. SpO2 estimation, extending the PLETH chain with a second-wavelength LED to move from a qualitative pulse trace toward an actual oxygen saturation measurement. Data logging, streaming the acquired signals over serial to a computer for storage and offline analysis on top of the real-time display. A single shared 5 V rail designed with enough current headroom from the start, removing the need for a second Arduino used only as a power source. If you are working through a biomedical instrumentation project of your own, I would love to hear what you built and what tripped you up along the way.\n","date":"29 November 2020","externalUrl":null,"permalink":"/posts/vital-signs-monitor-arduino/","section":"Posts","summary":"Have you ever wondered what actually happens inside the monitor hanging above a hospital bed, the one drawing a green heartbeat line while a number blinks in the corner?\nBehind that screen there is a chain of sensors, amplifiers, filters, and deliberate design decisions. You can build a simplified version of that chain yourself, and by the end of it you will understand bedside monitors in a way no datasheet or lecture can teach you.\n","title":"Build an Arduino Vital Signs Monitor: ECG, PLETH, and Temperature","type":"posts"},{"content":"","date":"29 November 2020","externalUrl":null,"permalink":"/tags/embedded-systems/","section":"Tags","summary":"","title":"Embedded-Systems","type":"tags"},{"content":"","date":"29 de November de 2020","externalUrl":null,"permalink":"/es/tags/ingenieria-biomedica/","section":"Tags","summary":"","title":"Ingenieria-Biomedica","type":"tags"},{"content":"","date":"29 de November de 2020","externalUrl":null,"permalink":"/es/tags/procesamiento-de-senales/","section":"Tags","summary":"","title":"Procesamiento-De-Senales","type":"tags"},{"content":"","date":"29 November 2020","externalUrl":null,"permalink":"/tags/signal-processing/","section":"Tags","summary":"","title":"Signal-Processing","type":"tags"},{"content":"","date":"29 de November de 2020","externalUrl":null,"permalink":"/es/tags/sistemas-embebidos/","section":"Tags","summary":"","title":"Sistemas-Embebidos","type":"tags"},{"content":"How many times have you sliced a model, copied it to a microSD card, walked across the room, pushed the card into the printer, and then walked back to your computer because you forgot to check the temperature? That little loop is invisible until somebody points it out, and then you cannot unsee it. OctoPrint on a Raspberry Pi deletes it: the job goes from your slicer straight to the machine over the network, and the printer becomes something you can watch, control and stop from the next room or from the other side of the city.\nThis is part 5 of five, and it is the one that changes how you use the printer rather than what the printer is. Part 1 printed the upgrades the machine could make for itself. Part 2 replaced the mainboard with a BIGTREETECH SKR Mini E3 V2.0. Part 3 added a BLTouch probe, and part 4 compiled the firmware that ties those two together. Everything before this point improved what the printer does with a file. This one improves how the file gets there.\nA confession about the order, because it matters for how you read this: OctoPrint was one of the earliest things I added to my Ender 3, long before I ever opened the electronics bay. It sits at the end of the series by topic, not by date. The happy consequence is that you do not need any of the previous four parts to do this one. A completely stock Ender 3 gets exactly the same benefit.\nThe OctoPrint web interface during a print, with the temperature graph and the webcam feed. What actually changes when the printer joins the network # The microSD card is not just an inconvenience. It is a disconnect. As long as the file travels on a card, the printer has no idea what your computer knows, and your computer has no idea what the printer is doing. Everything you want to know (is it still going, did the first layer take, how much time is left) requires you to physically go and look.\nPutting a Raspberry Pi between the two closes that gap, and four things change at once:\nJobs travel over the network. Slice, click print, done. No card, no walking, no \u0026ldquo;did I copy the new version or the old one?\u0026rdquo; You get a real terminal. Sending G-code by hand becomes trivial, which turns every calibration command in this series (M119, M303, M851, M500) into something you type instead of something you fight the LCD knob for. You can watch it. A camera pointed at the bed means you can check on a six-hour print without standing next to it. You can stop it. This is the one that pays for the whole project, and I will come back to it. None of this makes prints better by itself. It makes the loop around printing shorter, and a shorter loop is what makes you willing to iterate at all.\nWhat you need # A Raspberry Pi. I use a Raspberry Pi 4. OctoPrint is not a demanding piece of software and it runs on more modest boards, but a Pi 4 leaves comfortable headroom for the web interface and a camera stream at the same time. A microSD card for the Pi. Mine is a 16 GB card. The OctoPi image itself is small, so 16 GB is plenty for the system and for the G-code files you keep on hand. Before you pick a size, read the timelapse section further down, because that is the one feature that will eat a card alive, and 16 GB is exactly where I ran out of room. A proper power supply. A Pi 4 wants 5 V at 3 A over USB-C. An underpowered Pi throws undervoltage warnings and behaves strangely in ways that look like software bugs, which is a miserable way to spend an evening. A USB cable from the Pi to the printer. Check which connector your board actually has before you order one, since it differs between the stock Creality board and the aftermarket 32-bit boards. A USB webcam, optional but strongly recommended. Mine is a generic USB webcam with no brand I can remember and no fixed mount at all. It sits wherever it can see the bed. It has still been the most useful part of this whole setup, which tells you how low the bar is here. Your printer\u0026rsquo;s network situation sorted. The Pi can join over Wi-Fi or Ethernet. Mine runs over Wi-Fi, which is what puts the printer wherever the printer makes sense rather than wherever the router is, and it is configured before the Pi ever boots. Ethernet is the alternative if your machine happens to sit near a switch. Step 1: Flash OctoPi # There are several ways to end up with OctoPrint running. I took the simplest one: OctoPi, the official Raspberry Pi image that ships OctoPrint already installed and configured, flashed straight to a microSD card. No manual Python install, no containers, no dependency archaeology.\nUse the Raspberry Pi Imager:\nChoose your Pi model. Under Choose OS, go to Other specific-purpose OS → 3D printing → OctoPi, and take the stable build. Choose your microSD card. Now the step that saves you from ever plugging a keyboard and monitor into the Pi. Before writing the image, open the Imager\u0026rsquo;s advanced options (the gear icon) and configure, at minimum:\nThe hostname, which is how you will reach the machine. SSH enabled, with a password you actually chose. Wi-Fi SSID, password and country, so the Pi joins your network on its very first boot. That last one is the whole trick. A Pi flashed this way is a headless device: you write the card, put it in, power it on, and two or three minutes later it is simply on your network waiting for you. This is the same headless-first habit that makes running any small home server pleasant instead of tedious.\nThen plug the Pi into the printer over USB, power both on, and open OctoPrint in a browser. Two ways to find it:\nhttp://octopi.local # the mDNS name, if your network resolves it http://192.168.1.50 # or the Pi\u0026#39;s address on your own LAN That address is an example, not my network. To find yours, open your router\u0026rsquo;s admin page and look at the DHCP client list, or run ipconfig (Windows) or ip addr (Linux) on a machine already connected to see which range your network uses. Consider giving the Pi a DHCP reservation in the router while you are in there, so its address never moves and your bookmark keeps working.\nOn first load OctoPrint runs a setup wizard: create an account, set the printer profile (bed size, heated bed, number of extruders), and you are in.\nRaspberry Pi Imager with OctoPi selected and the advanced options open for Wi-Fi and SSH. Step 2: Connect the printer over USB # In the Connection panel on the left of the OctoPrint interface, you pick a serial port and a baud rate. AUTO for both usually works on the first try, and OctoPrint remembers the pair it found.\nIf you would rather be explicit, the port shows up as a Linux device name, typically /dev/ttyUSB0 or /dev/ttyACM0 depending on the USB-to-serial chip on your board, and the baud rate for a stock Marlin build is 115200, the same BAUDRATE value quoted in part 4.\nThere is one firmware setting that decides whether USB works at all on the BIGTREETECH SKR Mini E3 V2.0:\n#define SERIAL_PORT 2 On that board the USB port is wired to the STM32\u0026rsquo;s second serial peripheral, so port 2 is what your computer (or your Pi) talks to. Part 4 covers it in context rather than me repeating it here.\nWorth being clear about the chronology, though, because it is easy to assume these two upgrades belong together: my printer ran OctoPrint against the stock Creality board for a long time before that board was ever swapped. The two are completely independent. Any board that exposes a USB serial port will talk to a Pi, and the SERIAL_PORT question only arises if you are compiling your own firmware for a board that has more than one.\nWhen the connection succeeds, the temperature graph starts moving and the control tab comes alive. At that point you own a full G-code terminal: type M119 and read the endstop states, run M303 for a PID autotune, send M500 to save. Everything the previous parts of this series ask you to send over a serial monitor, you can now send from a browser tab.\nStep 3: From Cura straight to the printer # This is the part that removes the microSD card from your life, and it takes about two minutes.\nIn Cura, open the Marketplace and install the OctoPrint Connection plugin (maintained by fieldOfView). Restart Cura, then go to Preferences → Printers, select your Ender 3, and click Connect OctoPrint.\nCura needs an API key to talk to OctoPrint. You have two routes:\nPress the \u0026ldquo;Request\u0026hellip;\u0026rdquo; button in the plugin and approve the request in the OctoPrint web interface. This is the route I would recommend, because it issues an application key scoped to Cura instead of handing over your master key. Or paste the key manually, which you generate in OctoPrint\u0026rsquo;s own settings. Treat the API key like a password. It grants control of your printer to whatever holds it. Do not paste it into a forum post when you are asking for help, do not commit it to a repository, and crop it out of any screenshot before you publish one. Nothing in this post shows one, not even a fake, precisely because a plausible-looking key is exactly the thing people copy without thinking.\nPress Connect after entering or requesting the key. That last click matters: the plugin only stores the key once you connect with it.\nFrom then on, Cura\u0026rsquo;s print button becomes Print with OctoPrint, and the sliced job is uploaded and started without a card ever entering the picture.\nI still upload manually sometimes, and that is fine. When I slice on a different computer, or when I already have a G-code file that did not come out of my usual Cura setup, I just drag it into OctoPrint\u0026rsquo;s file list in the web interface and press print there. The plugin is the convenient path, not the only one, and it is worth knowing both because the manual upload works from any device with a browser, including a phone.\nThe OctoPrint Connection plugin in Cura, connected to the printer, with the API key field blanked out. Step 4: The camera, and the feature that actually earns its keep # Plug a USB webcam into the Pi and OctoPrint picks up the stream on its own. Mine, again, is a generic webcam with no mount, propped wherever it can see the bed. I never printed a bracket for it and I never bought a better one, and it has still changed how I print.\nHere is why, and it is not the reason people expect.\nThe value is not the novelty of watching plastic come out of a nozzle. It is that a failing print announces itself visually long before it announces itself any other way. When the part detaches and the nozzle starts dragging it around, or when the first layer clearly did not stick, you can see that in three seconds from a phone. And then you can do the thing that actually matters: cancel the print remotely.\nThat combination, live view plus remote abort, is the honest justification for the camera. A print that fails at hour one of six either wastes five hours of filament and machine time or it does not, and the only difference is whether somebody was able to look and press stop. I have done exactly that from outside the house more than once, and it has never once felt like a gimmick.\nAbout Octolapse # OctoPrint can record a timelapse out of the box. Octolapse is the plugin that takes it further: it moves the print head out of the frame before each snapshot, so the finished video shows the model rising smoothly out of the bed with no nozzle whipping across the shot. It is the effect behind essentially every satisfying 3D printing timelapse you have ever seen.\nI am going to be straight with you about this one, because I would rather be useful than impressive: I have the capability and I do not use it. The reason is storage. A timelapse means saving a frame for every layer and then rendering a video out of them, and my 16 GB card simply does not have the room for the frames and the finished files at the same time. So the feature sits there, available, unused.\nThat is not a criticism of Octolapse. It is a sizing decision I made without thinking it through: 16 GB is a comfortable card for running OctoPrint and a cramped one for filming it. That is exactly the kind of thing worth knowing before you buy your microSD card rather than after. If timelapses are part of why you want this project, plan the storage first.\nThe OctoPrint webcam feed showing a print in progress on the bed. Step 5: Watching it from outside the house # Everything above works on your local network. The moment you leave, it stops, and that is where remote access comes in.\nYou could do this with port forwarding and a reverse proxy, and I run exactly that kind of setup for other services. For a 3D printer specifically I would think twice. OctoPrint\u0026rsquo;s own documentation is unusually blunt about this, and it is right: exposing a machine that applies heat to plastic directly to the open internet is not a risk worth taking for convenience. A relay service, where the Pi opens an outbound connection to a provider and you reach it through them, avoids opening a single port on your router.\nUpdate, 2026: I switched tools for this. When I set this printer up in October 2020 I used AstroPrint, following CrossLink\u0026rsquo;s video \u0026ldquo;Access OctoPrint from ANYWHERE with AstroPrint\u0026rdquo; (April 2020), which is the guide I genuinely worked from at the time. A few months ago, in 2026, I moved to OctoEverywhere, and that is what runs on my Pi today. To be fair to AstroPrint: it is still around and still being maintained, so this was a move on my part, not an escape. The steps below describe the tool I currently use; the AstroPrint route is recorded here as history, not as an instruction.\nThe current path, if you want to follow what I actually run:\nIn OctoPrint, open Settings → Plugin Manager → Get More, search for OctoEverywhere, and install it. It is in the official OctoPrint plugin repository. Restart OctoPrint when it asks. Follow the plugin\u0026rsquo;s link to create an account and link your printer to it. Open the portal from your phone or any browser, anywhere. What you get, per the project\u0026rsquo;s own description, is remote access to the full OctoPrint interface (plugins included), webcam streaming, and print notifications, on a free tier, without any port forwarding. The bit I care about is that the remote view is the same interface as the local one, so the cancel button is exactly where my thumb already expects it.\nWhat this does not fix # I want to be honest about the boundaries of this upgrade, because \u0026ldquo;network-connected printer\u0026rdquo; sounds more transformative than it is.\nYou still walk over to the printer. Somebody has to pop the finished part off the bed, clear the skirt, wipe the surface and start the next job. OctoPrint eliminates the trip before the print, not the one after it.\nA networked printer does not improve a bad first layer. Not one millimetre. If your bed is not level and your Z offset is wrong, OctoPrint will faithfully deliver a beautiful file to a machine that then prints it badly, and now you can watch it happen in high definition. That problem belongs to the BLTouch and the firmware, which is exactly why those are separate posts.\nA camera is not failure detection. It only helps when somebody looks. It buys you the ability to catch a failure, not the guarantee.\nKeeping those straight is, I think, the most useful engineering habit this project taught me. Each upgrade solves one specific problem well, and the temptation to expect a good tool to fix an unrelated problem is where a lot of wasted weekends come from.\nUpdate, June 2026: the install aged out, and I had to reflash # Read this before you cut power to your Pi. This post is dated 2020, and the install it describes ran quietly for years. In June 2026 it died. The story is short, the fix is reproducible, and the lesson at the end is the one thing here that will save you a weekend.\nWhat happened. After a power-off, the OctoPrint web interface simply never came back. The root cause was EXT4 filesystem corruption on the microSD card, caused by an unclean shutdown that had interrupted an in-place update months earlier. The damage had been sitting there quietly ever since, waiting for a reboot to expose it.\nThe first thing that brought the printer back was forcing a filesystem check on boot. You do that from another computer, by mounting the card\u0026rsquo;s boot partition and adding one parameter to /boot/cmdline.txt:\nfsck.mode=force That is a genuinely useful trick to know, and it worked. It is also a patch rather than a repair: it fixes the damage it can reach and tells you nothing about what else the interrupted write left behind.\nWhy reflashing was the right answer anyway. My original card had been built on Raspberry Pi OS Buster with Python 3.7, and both of those had long since reached end of life. The current OctoPi image is built on Bookworm with Python 3.11. An OctoPi install ages out from underneath you even when nothing goes wrong, so a card that had already corrupted itself was not worth nursing. Worth noting for your sake: this post never pinned an OctoPi version, it tells you to take the stable build in the Imager, so the instructions above did not go stale. Only my particular install did.\nWhat the migration actually took. This is the reader-useful part, and it is the difference between an afternoon and a weekend:\nTake an OctoPrint backup first. OctoPrint exports its own .zip from the web interface, and I pulled one off the sick install before touching anything else. If your Pi still answers at all, do this before you do anything clever. Reflash the same card. I had no spare on hand, so the card that failed is the card that runs today, written fresh with the current stable OctoPi build from the Imager. Restore from the backup. Settings, G-code files and plugins all came back out of that .zip, and I did the restore over Ethernet rather than Wi-Fi. The lesson, stated plainly: always use OctoPrint\u0026rsquo;s own Shutdown command before cutting power to the Pi. Not the switch, not the plug. An SD card interrupted mid-write is exactly how this failure starts, and the interruption that killed mine happened months before the symptom appeared. Backups and a Shutdown click cost seconds; the alternative cost me an evening of diagnosis and a full rebuild.\nI would rather leave this here than quietly rewrite the post as though nothing had happened. Hardware that runs for nearly six years earns a maintenance story, and treating that story as another thing to learn is more useful than pretending the setup was perfect.\nWhere this goes next # Two things are on my list, and both point outward from the printer itself.\nMoving timelapse storage off the Pi. The storage limit that keeps Octolapse unused is not really a printer problem, it is a \u0026ldquo;this small computer has a small card\u0026rdquo; problem, and I already run a home server built from an old laptop whose whole purpose is holding files. Pointing the frames and rendered videos at network storage instead of at the Pi\u0026rsquo;s card is the obvious fix, and it is a nice illustration of how a home lab compounds: a machine you built for one reason quietly solves a problem you had somewhere else.\nHome Assistant. Bringing the printer into the same automation layer as the rest of the house is the natural next step, and it is the piece I would build out properly rather than sketch here.\nFive parts in, the Ender 3 that arrived as a kit is quieter, levels itself, runs firmware I compiled, wears parts it printed for itself, and now lives on the network. Not one of those steps required a skill I had when I started, which is really the point. Curiosity, a couple of guides open on a second screen, and a willingness to treat each failure as the next thing to learn will take you through every one of them.\nStart wherever your own printer annoys you the most. That is always the right first upgrade.\n","date":"15 October 2020","externalUrl":null,"permalink":"/posts/octoprint-raspberry-pi-ender-3/","section":"Posts","summary":"How many times have you sliced a model, copied it to a microSD card, walked across the room, pushed the card into the printer, and then walked back to your computer because you forgot to check the temperature? That little loop is invisible until somebody points it out, and then you cannot unsee it. OctoPrint on a Raspberry Pi deletes it: the job goes from your slicer straight to the machine over the network, and the printer becomes something you can watch, control and stop from the next room or from the other side of the city.\n","title":"Control Your Ender 3 Over the Network with OctoPrint on a Raspberry Pi","type":"posts"},{"content":"","date":"15 October 2020","externalUrl":null,"permalink":"/tags/cura/","section":"Tags","summary":"","title":"Cura","type":"tags"},{"content":"","date":"15 October 2020","externalUrl":null,"permalink":"/tags/octoprint/","section":"Tags","summary":"","title":"Octoprint","type":"tags"},{"content":"","date":"15 October 2020","externalUrl":null,"permalink":"/tags/raspberry-pi/","section":"Tags","summary":"","title":"Raspberry-Pi","type":"tags"},{"content":"You have finished assembling your Ender 3, the test print came out fine, and now you are staring at the machine wondering what to actually make with it. My answer is always the same: make parts for the printer. A 3D printer is one of the very few tools that can build its own upgrades, and on an Ender 3 that is not a party trick. It is genuinely how the machine goes from a kit that prints to a machine you enjoy using.\nThis post is a checklist of the Ender 3 printed upgrades that are still bolted to mine years later. You will find sixteen numbered parts below, each one with a photo, a short description of the problem it fixes, and the link to download the model. They are grouped by the area of the printer they improve, so you can jump straight to whatever is annoying you today and print that first.\nIt is part 1 of five. Part 2 replaces the mainboard with a BIGTREETECH SKR Mini E3 V2.0, part 3 adds a BLTouch probe, part 4 compiles custom Marlin firmware in VS Code, and part 5 puts the printer on the network with OctoPrint on a Raspberry Pi.\nThe Ender 3 after a few rounds of printed upgrades, most of them made by the printer itself. The first useful thing this printer makes is parts for itself # There is something quietly satisfying about a machine that improves itself, but the practical case is even stronger than the poetic one. Printed upgrades cost a few pesos of filament instead of a shipping fee and a two-week wait. They are reversible, because nothing here required cutting, drilling, or soldering. And every one of them doubles as a calibration exercise: a part that has to bolt onto real hardware will tell you far more about your printer\u0026rsquo;s dimensional accuracy than another decorative print ever will.\nI printed the first batch in August 2020, shortly after the printer arrived, and the list kept growing over the following couple of years as new annoyances surfaced. Everything below was printed in PLA, and all of it is still in service.\nTwo habits are worth adopting before you download anything. First, read the model page instead of just the thumbnail: some parts need hardware you do not have yet, some come in variants for different frame extrusions, and the licence terms differ from model to model. Second, note where credit is due. Where I know the author, I name and link them below. Where I no longer know which of many community versions I downloaded, I say exactly that, because a wrong attribution is worse than a missing one.\nFilament that feeds cleanly # The stock spool holder sits on top of the frame and does its job, right up to the moment your spool is not the size Creality assumed. This is the group I would print first, because a spool that drags shows up in every print afterwards, usually as under-extrusion you spend an evening blaming on the hotend.\n1. Filament holder for 80 mm spools # What it fixes: small sample spools and many refill spools have a narrower core than the standard one, so they wobble or bind on the stock holder. Model: Creality (Ender 3) Filament holder 80mm spools by mjoaris (CC-BY).\nRead the model page before you print this one, because the author\u0026rsquo;s own note is easy to miss: \u0026ldquo;To be clear, you will need two 608ZZ bearings.\u0026rdquo; Those are common skateboard bearings, cheap and easy to find, but the part is useless sitting on your desk while they are in the mail.\nThe 80 mm spool holder with its two 608ZZ bearings, carrying a narrow refill spool that the stock holder cannot handle. 2. Side spool mount # What it fixes: the long, high, wobbly filament path from the top of the frame, plus a heavy spool sitting right where it does the machine\u0026rsquo;s vibration no favours. Model: Ender-3 Side Spool Mount and other Printers with 20/40mm by DrStreet.\nMoving the spool to the side of the frame shortens the path to the extruder and lowers the centre of mass. The model is written for 20/40 mm extrusion, so it adapts to more machines than just this one. Be aware that it changes the geometry of everything upstream of the extruder, which, as you will see further down, quietly retired another part of my list.\nThe side spool mount, which shortens the filament path to the extruder and lowers the machine\u0026rsquo;s centre of mass. 3. Filament guide # What it fixes: filament scraping across a frame edge on its way to the extruder instead of running on a smooth, controlled curve. Model: Ender 3 filament guide by Jonasen.\nThis is the smallest part in the whole list and one of the ones I would miss most. It slides straight onto the frame, so installing it takes seconds, and the gap in the ring means you can drop the filament in sideways instead of threading a whole spool through a closed loop.\nThe filament guide, the smallest part in the list, turning a scraping edge into a smooth curve into the extruder. Better prints where the plastic comes out # These two upgrades are the ones that show up in the printed object itself, which is why I rank them above every cosmetic part further down. Both come from the same author, and both address the small volume around the nozzle where print quality is actually decided.\n4. Mistral-E filament cooling duct # What it fixes: poor part cooling, which is what turns overhangs droopy and slim pointed features into soft, half-melted shapes. Model: Mistral-E Filament Cooling Duct by Leo_N, published in December 2018. My file is Mistral-E_v1.3_Leo_N.stl.\nIf you print only one thing from this entire post, make it this one, because a cooling duct decides how quickly each layer freezes in place. Its author was refreshingly explicit about the trade-offs he designed for: use the printer\u0026rsquo;s original fan rather than demanding new hardware, blow a high volume of air onto the filament just past the nozzle, keep that air off the heater block, stay compact enough not to interfere with auto-level sensors, and keep both noise and extruder weight down. That constraint about auto-level sensors aged particularly well on my machine, since part 3 of this series eventually put a BLTouch probe right next to the hotend.\nThe Mistral-E cooling duct in place beside the nozzle, the single upgrade that changed my print quality the most. 5. MK8 extruder insert # What it fixes: clogs forming inside the extruder body, where the filament has room to bend instead of being guided straight through. Model: Creality Mk8 Extruder Insert by Leo_N. My file is Extruder_Insert_Leo_N.stl.\nThis is the least visible upgrade in the post and one of the most useful, because a clog costs you a failed print plus the half hour it takes to find the cause. Two notes from the author are worth carrying into your own build: the PTFE tube needs cutting to length, so budget for a bit of manual work rather than a pure print-and-bolt job, and version 1.1 thickened the wall between the two PTFE tubes specifically for strength, which tells you where the stress lives in that part.\nThe MK8 extruder insert, with the PTFE tube cut to length, guiding the filament straight through the extruder body. Cables that stop rubbing # Motion is what wears cables out. On a stock Ender 3, the bundle heading to the hotend and the one heading to the bed both flex thousands of times per print, and they will happily rub against the frame while doing it.\n6. Cable chain # What it fixes: unguided cable looms that bend wherever they feel like and rub against the frame every time an axis moves. Model: Ender 3 Cable Chain by johnniewhiskey (CC-BY).\nAn articulated drag chain guides the loom along a controlled path so it bends where you decided it should bend. It is also by far the most laborious print in this list: the author\u0026rsquo;s own instructions call for \u0026ldquo;15 Links for Heatbed\u0026rdquo; and \u0026ldquo;10 Links (or more) for X gantry\u0026rdquo;, so you are looking at roughly 25 to 30 links before you even start on the covers, the mounts and the bed corner. One more line from that same README saves you a rebuild: \u0026ldquo;Use a little higher print temp than usual for stronger part. Too low temp will cause part break while assembly.\u0026rdquo; The links snap together by hand, and a link printed cold will break under exactly that pressure, so print this set hotter than your usual profile.\nThe printed cable chain in place. It is the most laborious print in the set and the one that changes how the machine looks the most. 7. Cable clips # What it fixes: stray cables dangling off the frame and into the path of a moving axis. Model: Ender 3 Cable Clips by Holspeed, a remix of jn-gr\u0026rsquo;s ribbon cable clip.\nThe low-effort complement to the chain: small clips that snap onto the aluminium extrusion and hold cables flat against it. The remix adds 45 degree fillets inside the arms and tags so you can pop a clip off with a flat screwdriver, includes a 90 degree variant for anchoring the ribbon cable where it crosses an extrusion, and was modified to take the two 3 mm power wires coming from the PSU. Print a handful; you will use all of them.\nCable clips holding the wiring flat against the extrusion, the low-effort complement to the cable chain. The control box: noise and protection # Some of an Ender 3\u0026rsquo;s noise comes from the stepper drivers, and no printed part will fix that (that is what part 2 is for). The rest is air, and air is very much a printed-parts problem.\n8. Power supply fan silencer # What it fixes: the constant droning of the PSU fan, which you hear the whole time the printer is powered on, including while it sits idle. Model: a common community model I cannot attribute honestly.\nA printed duct redirects and diffuses the fan\u0026rsquo;s exhaust, which takes the sharp edge off the noise without blocking the airflow the supply actually needs. Look specifically for a version made for your power supply, since Creality shipped more than one.\nThe printed duct on the power supply fan, which diffuses the exhaust and takes the sharp edge off the droning. 9. Mainboard fan guard # What it fixes: fingers, cables and zip ties finding their way into the fan that cools the mainboard, down in the control box under the printer. Model: Creality Ender 3 Board Fan Guard - Slimmed by Phryxus. My file is Fan_Guard_V2.STL.\nNote what this part guards: the electronics fan, not the hotend or part cooling fan, so it protects the airflow keeping your stepper drivers alive. I chose this version for two reasons from the model page. The slimmed design uses roughly 60% of the material and time of the finned version, and its V2 opens up around 30% more cross-sectional airspace, so the guard interferes less with the cooling it is protecting. A guard that chokes the fan would be worse than no guard at all.\nThe slimmed mainboard fan guard, chosen because it uses less material and leaves more airspace than the finned version. Controls you can actually turn # Two of the printer\u0026rsquo;s most-used controls are, out of the box, barely controls at all.\n10. Extruder knob # What it fixes: loading and unloading filament by hand, which otherwise means gripping a small metal extruder arm and turning it. Model: Ender 3 Yoda Extruder Knob by JaZzSuperman.\nA printed knob presses onto the shaft and turns an awkward pinch into a deliberate, comfortable motion. I started with a perfectly ordinary round knob, and then I found this one, which is the same idea with Yoda\u0026rsquo;s head on it. It works just as well, and it has been on the machine ever since. Not every engineering decision has to be austere.\nThe Yoda extruder knob, which turns loading filament by hand into a comfortable, deliberate motion. 11. Z axis knob # What it fixes: raising and lowering the gantry by hand during levelling, nozzle changes, and every time you need the bed out of the way. Model: a common community model I cannot attribute honestly.\nThe knob presses onto the top of the Z leadscrew, so you turn the screw directly instead of wrestling the coupler or forcing the motor. It costs almost nothing to print and you will reach for it constantly.\nThe Z axis knob on top of the leadscrew, for raising and lowering the gantry by hand. Keeping the machine tidy and protected # This last group is not about print quality at all. It is about the printer being a pleasant, durable object to have in your space, which is more motivating than it sounds when the alternative is a cluttered desk you avoid.\n12. Drawer under the base # What it fixes: nozzles, spare bowden couplings and hex keys migrating around the room instead of living with the printer. Model: Drawer for Ender3, Ender 3 by Jaypirnts.\nIt mounts underneath the printer\u0026rsquo;s base and turns dead space into storage. This is the part visitors always comment on.\nThe printed drawer under the base, where the nozzles, hex keys and spare parts finally have somewhere to live. 13. Display PCB cover # What it fixes: the stock LCD leaving its board exposed at the back, where it collects dust and takes the occasional knock. Model: Ender 3 Display LCD PCB Cover by Rocco81-92.\nNote the licence on this one, CC-BY-NC-SA, which is stricter than the others here: non-commercial, and derivatives must be shared alike. If you remix it or sell prints, that matters.\nRocco81-92\u0026rsquo;s PCB cover closing the back of the display, where the board would otherwise sit exposed to dust and knocks. 14. Front screen cover # What it fixes: the light of the LCD during overnight prints, when the display glows in a dark room and you do not need to read it anyway. Model: a common community model I cannot attribute honestly. My file is Ender_3_Screen_Cover.stl.\nTo be clear, this is a different part from the PCB cover above: Rocco81-92\u0026rsquo;s model closes the back of the display where the board sits, and this one covers the front. Blocking that glow is the reason it stays on my machine, and if the printer shares a room with you at night you will appreciate it. That it also dresses up the front face is a welcome side effect rather than the point.\nThe front screen cover, which blocks the display\u0026rsquo;s light during overnight prints. 15. Rail cover # What it fixes: debris and dust from printing settling on the frame rail. I fitted it only on the side where the spool sits. Model: a common community model I cannot attribute honestly. My file is New_Ender_3_rail_cover.stl.\nKeeping that rail clean is one less surface where dust builds up around a moving machine, and it is a five-minute print. It also makes the machine look finished, and I will admit that counted too, but the debris is the reason I printed it. Good candidate for a leftover end of filament.\nThe rail cover on the spool side of the frame, keeping print debris and dust out of the rail. 16. Scraper holder # What it fixes: the bed scraper ending up under a pile of paper on the other side of the room. Model: a common community model I cannot attribute honestly.\nIt sounds trivial and it is trivial, but a tool you can reach is a tool you actually use.\nThe scraper holder, so the bed scraper lives with the printer instead of somewhere across the room. What I printed and stopped using # Not everything on that list earned its place, and I think that is worth saying out loud in a roundup like this one. Three parts came off the machine.\nTwo extruder knobs. Before the Yoda knob I printed an ordinary round one, and I also printed a variant designed for the CR-10 extruder. Both worked. Both came off anyway, once I found a version that fit better and that I simply liked more.\nA second filament guide. I printed 2020 / Ender 3 Filament Guide by Filboyt, and I stopped using it the moment I switched to the side spool mount. Nothing was wrong with the part. Moving the spool to the side of the frame changed the filament path so completely that the guide no longer had a job to do.\nThat second story is the more useful one, and it is a pattern worth expecting: on a machine you are upgrading incrementally, one improvement can make an earlier one obsolete. Although a print that ends up in a drawer feels like wasted filament, I have come to read those three differently. They cost almost nothing, they taught me which dimensions actually mattered, and they are the reason I recognised the better solution when I saw it. Printing the ordinary version first is not a failure. It is how you learn what \u0026ldquo;better\u0026rdquo; would even mean.\nWhat matters when you print these # I printed all of it in PLA and it has held up, which is the honest summary. Beyond material choice, three things are worth thinking about before you hit print:\nHeat proximity. PLA is the weak point of this list wherever a part sits near warm air, so give the PSU duct, the board fan guard and especially the cooling duct next to the hotend a look after their first long prints. If you have PETG on hand, those are the sensible candidates for it.\nLayer orientation. Cable chain links and clips are small parts that get flexed repeatedly, and a printed part is always weakest between its layers. Orient them so the bending load runs along the layers rather than trying to peel them apart.\nThe hardware you need is not in the download. The 80 mm spool holder needs its two 608ZZ bearings, the extruder insert needs PTFE tube cut to length, and several of these parts want M3 or M4 screws and T-nuts to attach to the frame. Check what each model calls for before you start, so the finished part does not sit on the shelf waiting for a delivery.\nWhere this goes next # Printed upgrades fixed almost everything mechanical about this machine, and they did it for the price of filament. What they cannot fix is what happens inside the electronics: the noise the stepper drivers make, the firmware that will not grow, and the missing header where a probe should plug in. That is exactly where the rest of this series goes.\nPart 2 swaps the stock 8-bit board for a BIGTREETECH SKR Mini E3 V2.0, which makes the printer dramatically quieter and turns firmware flashing into a drag-and-drop operation. Part 3 installs a BLTouch probe, and it is worth pointing out here that it starts with one more printed part: the mount that holds the probe next to the hotend. Part 4 compiles Marlin firmware that knows about all of it, and part 5 hands control of the whole machine to OctoPrint on a Raspberry Pi.\nIf you only take one thing from this post, let it be the habit rather than the list: when something on your printer annoys you, check whether the printer can solve it before you reach for your wallet. More often than you would expect, it can.\n","date":"2 August 2020","externalUrl":null,"permalink":"/posts/ender-3-printed-upgrades/","section":"Posts","summary":"You have finished assembling your Ender 3, the test print came out fine, and now you are staring at the machine wondering what to actually make with it. My answer is always the same: make parts for the printer. A 3D printer is one of the very few tools that can build its own upgrades, and on an Ender 3 that is not a party trick. It is genuinely how the machine goes from a kit that prints to a machine you enjoy using.\n","title":"Ender 3 Printed Upgrades: The Parts Your Printer Can Make for Itself","type":"posts"},{"content":"","date":"2 de August de 2020","externalUrl":null,"permalink":"/es/tags/piezas-impresas/","section":"Tags","summary":"","title":"Piezas-Impresas","type":"tags"},{"content":"","date":"2 August 2020","externalUrl":null,"permalink":"/tags/printed-parts/","section":"Tags","summary":"","title":"Printed-Parts","type":"tags"},{"content":"My name is Emiliano. I work on digital hardware: RTL design for integrated circuits, and hardware acceleration for machine learning. I like understanding how things work, and I like building technology that has a meaningful impact on the people who use it.\nRight now I am pursuing my Master\u0026rsquo;s degree in Computer Engineering at USC as a Fulbright–García Robles grantee. My focus is processor architecture and the arithmetic underneath machine learning: designing efficient datapaths in Verilog, and pushing performance further with CUDA. Alongside my studies I work on the virtualization and storage team at Keck Medicine of USC, on a Nutanix estate spanning a datacenter and three hospitals.\nI studied biomedical engineering at Tecnológico de Monterrey, graduated with honors as the highest GPA in my class, and my capstone project became a first-author article in IEEE Transactions on Neural Systems and Rehabilitation Engineering. After that I spent four years at PPD, part of Thermo Fisher Scientific, coordinating support for clinical trials under regulatory scrutiny, where I co-built an Excel/VBA tool that cut a manual reconciliation from over five hours to about thirty minutes, and ended up adopted across most of the company\u0026rsquo;s studies.\nWhat I work on # Digital design and RTL. An ARM-compatible processor with a 5-stage pipeline and 4-way fine-grained multithreading, plus a SIMT GPU, both written in Verilog, synthesized, and tested on a NetFPGA board. Hardware for machine learning. A pipelined 16-bit MAC unit with full-custom layout in Cadence Virtuoso and DRC/LVS verification. Currently building a co-designed INT8 attention accelerator for the Arty Z7-20, from cycle-accurate C++ modeling and Verilog RTL through Zynq integration as a PyTorch operator. Infrastructure and home lab. An OpenMediaVault server based on Debian, running containerized services with Docker Compose and an NGINX reverse proxy with SSL and WebSocket support, on a multi-subnet mesh network routed with OpenWrt, plus a local inference server running on an NVIDIA GPU. Education and recognition # M.S. Computer Engineering, University of Southern California (2025–2027) — Viterbi Endowment Scholarship Fulbright–García Robles Grant, COMEXUS B.S. Biomedical Engineering, Tecnológico de Monterrey — graduated with honors and highest GPA in the class I am currently looking for full-time engineering roles for after I graduate in May 2027 so let\u0026rsquo;s get in touch.\nSee my projects ","externalUrl":null,"permalink":"/about/","section":"Emiliano Fernández Cervantes","summary":"My name is Emiliano. I work on digital hardware: RTL design for integrated circuits, and hardware acceleration for machine learning. I like understanding how things work, and I like building technology that has a meaningful impact on the people who use it.\n","title":"About Me","type":"page"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"I am always glad to talk about hardware design, machine learning acceleration, biomedical engineering, or a home lab project that got out of hand. The fastest way to reach me is LinkedIn.\nI am currently looking for full-time engineering roles in RTL and computer architecture, hardware acceleration for machine learning, or AI and datacenter infrastructure.\nWhere to find me # Email fdezemi@emilian.website LinkedIn emiliano-fernandez-cervantes GitHub EmilianFC20 Based in Los Angeles, California, originally from Mexico City. I read messages in both English and Spanish, so write in whichever you prefer.\nScan the code to save my details to your phone Download the contact card (.vcf) Send me a message # Name Email Message Send message ","externalUrl":null,"permalink":"/contact/","section":"Emiliano Fernández Cervantes","summary":"I am always glad to talk about hardware design, machine learning acceleration, biomedical engineering, or a home lab project that got out of hand. The fastest way to reach me is LinkedIn.\n","title":"Contact","type":"page"}]