Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { defineConfig } from 'vitepress'
import { registerMediaSlots } from './media-slots.mts'
import { seoHead } from './seo.mts'
import { writeLegacyRedirects } from './seo-build.mjs'

export default defineConfig({
// Configuración compartida
Expand All @@ -10,9 +11,12 @@ export default defineConfig({
title: "SerialCraft",
description: "Conecta Arduino y ESP32 con Minecraft: instalación, circuitos, código y tutoriales de SerialCraft.",
base: "/",
// Keep .html URLs consistent with GitHub Pages, internal links and sitemap.
cleanUrls: false,
// Match the extensionless URLs already selected by Google and linked by the site.
cleanUrls: true,
transformHead: seoHead,
transformHtml: (html, _id, { pageData }) => html.replace(/<html lang="[^"]*"/,
`<html lang="${/^(?:en\/|versiones\/en\/)/.test(pageData.relativePath) ? 'en' : 'es'}"`),
buildEnd: writeLegacyRedirects,
head: [
['link', { rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' }],
['link', { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' }],
Expand Down Expand Up @@ -185,9 +189,11 @@ export default defineConfig({
{ text: 'I/O Module', link: '/en/tutorials/io-block' },
{ text: 'Arduino LED', link: '/en/tutorials/arduino-led' },
{ text: 'Potentiometer', link: '/en/tutorials/potentiometer' },
{ text: 'Digital and analog', link: '/en/tutorials/digital-analog' },
{ text: 'Visualizer', link: '/en/tutorials/visualizer' },
{ text: 'Telemetry', link: '/en/tutorials/telemetry' },
{ text: 'Automated tests', link: '/en/tutorials/testing' }
{ text: 'Automated tests', link: '/en/tutorials/testing' },
{ text: 'Troubleshooting', link: '/en/tutorials/troubleshooting' }
]
},
{
Expand Down Expand Up @@ -231,6 +237,8 @@ export default defineConfig({
},

themeConfig: {
// The custom language switcher maps translated filenames explicitly.
i18nRouting: false,
socialLinks: [
{ icon: 'github', link: 'https://github.com/leonardoaliaga/serialcraft' },
{ icon: 'instagram', link: 'https://instagram.com/aliaga1924' }
Expand Down
55 changes: 55 additions & 0 deletions docs/.vitepress/routes.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Public documentation routes shared by SEO, language navigation and build checks.
export const origin = 'https://serialcraft.xyz'
export const pairs = [
['index.md', 'en/index.md'], ['guide.md', 'en/guide.md'],
['protocol.md', 'en/protocol.md'], ['io-module.md', 'en/io-module.md'],
['ejemplos/index.md', 'en/examples/index.md'], ['acerca-de.md', 'en/about.md'],
['contacto.md', 'en/contact.md'], ['privacidad.md', 'en/privacy.md'],
['tutoriales/index.md', 'en/tutorials/index.md'],
['tutoriales/arduino-led-minecraft.md', 'en/tutorials/arduino-led.md'],
['tutoriales/bloque-io.md', 'en/tutorials/io-block.md'],
['tutoriales/sensor-redstone.md', 'en/tutorials/potentiometer.md'],
['tutoriales/digital-analogico.md', 'en/tutorials/digital-analog.md'],
['tutoriales/solucionar-errores.md', 'en/tutorials/troubleshooting.md'],
['tutoriales/visualizador.md', 'en/tutorials/visualizer.md'],
['tutoriales/telemetria.md', 'en/tutorials/telemetry.md'],
['tutoriales/pruebas-automatizadas.md', 'en/tutorials/testing.md'],
...['v0.3.6', 'v0.4.3'].map(v => [`versiones/${v}/index.md`, `versiones/en/${v}/index.md`]),
...['v0.3.6', 'v0.4.3'].flatMap(v => ['guide', 'protocol'].map(p =>
[`versiones/${v}/${p}.md`, `versiones/en/${v}/${p}.md`])),
['versiones/v0.4.3/ejemplos.md', 'versiones/en/v0.4.3/examples.md']
]

export const canonicalPath = file => '/' + file
.replace(/(^|\/)index\.md$/, '$1').replace(/\.md$/, '')
export const canonicalUrl = file => origin + canonicalPath(file)

// These obsolete public addresses have an equivalent replacement. Keep the
// redirect pages out of the sitemap; only the destinations should be indexed.
export const legacyRedirects = {
'/en/tutoriales/solucionar-errores.html': '/en/tutorials/troubleshooting',
'/tutorials/visualizer.html': '/en/tutorials/visualizer',
'/en/tutoriales/digital-analogico.html': '/en/tutorials/digital-analog',
'/en/tutoriales/telemetria.html': '/en/tutorials/telemetry',
'/en/contacto.html': '/en/contact',
'/ejemplos/esquemas-ia.html': '/ejemplos/'
}

function comparable(path) {
return path.replace(/[?#].*$/, '').replace(/\.html$/, '')
.replace(/\/index$/, '/').replace(/\/$/, '') || '/'
}

export function translatedPath(input, language) {
if (!['es', 'en'].includes(language)) throw new Error('Unknown documentation language')
const path = comparable(input)
const pair = pairs.find(files => files.some(file => comparable(canonicalPath(file)) === path))
if (pair) return canonicalPath(pair[language === 'en' ? 1 : 0])
// A page without a translation links to the corresponding archive or section,
// never to an invented filename. Anchors differ across translations.
const archive = path.match(/^\/versiones\/(?:en\/)?(v0\.3\.6|v0\.4\.3)(?:\/|$)/)
if (archive) return '/versiones/' + (language === 'en' ? 'en/' : '') + archive[1] + '/'
if (/^\/(?:en\/)?(?:tutoriales|tutorials)(?:\/|$)/.test(path))
return language === 'en' ? '/en/tutorials/' : '/tutoriales/'
return language === 'en' ? '/en/' : '/'
}
21 changes: 21 additions & 0 deletions docs/.vitepress/seo-build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { mkdir, writeFile } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import { legacyRedirects, origin } from './routes.mjs'

export async function writeLegacyRedirects(config) {
for (const [oldPath, target] of Object.entries(legacyRedirects)) {
const file = resolve(config.outDir, '.' + oldPath)
const destination = origin + target
await mkdir(dirname(file), { recursive: true })
// GitHub Pages cannot configure per-path HTTP 301s. An immediate HTML
// refresh is Google's supported permanent-redirect fallback for static hosts.
await writeFile(file, `<!doctype html>
<html lang="${target.startsWith('/en/') ? 'en' : 'es'}"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Page moved | SerialCraft</title>
<link rel="canonical" href="${destination}">
<meta http-equiv="refresh" content="0;url=${destination}">
</head><body><p>SerialCraft: this page has moved / Esta página se ha trasladado.</p>
<p><a href="${destination}">Continue / Continuar</a></p></body></html>\n`)
}
}
28 changes: 5 additions & 23 deletions docs/.vitepress/seo.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,13 @@ import { existsSync } from 'node:fs'
import { resolve } from 'node:path'
import type { HeadConfig, TransformContext } from 'vitepress'

const origin = 'https://serialcraft.xyz'
const pairs = [
['index.md', 'en/index.md'], ['guide.md', 'en/guide.md'],
['protocol.md', 'en/protocol.md'], ['io-module.md', 'en/io-module.md'],
['ejemplos/index.md', 'en/examples/index.md'], ['acerca-de.md', 'en/about.md'],
['contacto.md', 'en/contact.md'], ['privacidad.md', 'en/privacy.md'],
['tutoriales/index.md', 'en/tutorials/index.md'],
['tutoriales/arduino-led-minecraft.md', 'en/tutorials/arduino-led.md'],
['tutoriales/bloque-io.md', 'en/tutorials/io-block.md'],
['tutoriales/sensor-redstone.md', 'en/tutorials/potentiometer.md'],
['tutoriales/visualizador.md', 'en/tutorials/visualizer.md'],
['tutoriales/telemetria.md', 'en/tutorials/telemetry.md'],
['tutoriales/pruebas-automatizadas.md', 'en/tutorials/testing.md'],
...['v0.3.6', 'v0.4.3'].map(v =>
[`versiones/${v}/index.md`, `versiones/en/${v}/index.md`]),
...['v0.3.6', 'v0.4.3'].flatMap(v => ['guide', 'protocol'].map(p =>
[`versiones/${v}/${p}.md`, `versiones/en/${v}/${p}.md`])),
['versiones/v0.4.3/ejemplos.md', 'versiones/en/v0.4.3/examples.md']
]
const url = (path: string) => origin + '/' + path.replace(/(^|\/)index\.md$/, '$1').replace(/\.md$/, '.html')
import { origin, pairs, canonicalUrl as url } from './routes.mjs'

export function seoHead({ pageData, siteConfig }: TransformContext): HeadConfig[] {
const path = pageData.relativePath
if (path === '404.md') return [['meta', { name: 'robots', content: 'noindex' }]]
const en = path.startsWith('en/') || path.startsWith('versiones/en/')
const title = `${pageData.title || 'SerialCraft'} | SerialCraft`
const title = /SerialCraft/i.test(pageData.title || '') ? pageData.title : `${pageData.title || 'Documentation'} | SerialCraft`
const description = pageData.description || (en
? 'Learn to connect Arduino and ESP32 to Minecraft with SerialCraft: installation, circuits, code and troubleshooting.'
: 'Aprende a conectar Arduino y ESP32 con Minecraft usando SerialCraft: instalación, circuitos, código y solución de problemas.')
Expand Down Expand Up @@ -74,10 +56,10 @@ export function seoHead({ pageData, siteConfig }: TransformContext): HeadConfig[
'@context': 'https://schema.org',
'@graph': [
{ '@type': 'WebSite', '@id': origin + '/#website', name: 'SerialCraft',
url: origin + '/', inLanguage: ['es', 'en'], sameAs: [repository, 'https://modrinth.com/mod/serialcraft'] },
url: origin + '/', alternateName: 'SerialCraft Mod', inLanguage: ['es', 'en'], sameAs: [repository, 'https://modrinth.com/mod/serialcraft'] },
{ '@type': 'SoftwareSourceCode', '@id': origin + '/#project', name: 'SerialCraft',
description: 'Open-source Minecraft Fabric mod connecting Arduino and ESP32 with Minecraft through USB serial and local TCP.',
url: origin + '/', codeRepository: repository, programmingLanguage: 'Java',
url: origin + '/', codeRepository: repository, programmingLanguage: 'Java', version: '0.5.0-beta', inLanguage: ['es', 'en', 'zh-CN'],
license: 'https://github.com/LeonardoAliaga/SerialCraft/blob/main/LICENSE',
author: { '@type': 'Person', name: 'Leonardo Aliaga', url: 'https://github.com/LeonardoAliaga' },
sameAs: [repository, 'https://modrinth.com/mod/serialcraft'] }
Expand Down
37 changes: 37 additions & 0 deletions docs/.vitepress/theme/LanguageSwitcher.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<script setup lang="ts">
import { computed, watch } from 'vue'
import { useRoute, withBase } from 'vitepress'
import { translatedPath } from '../routes.mjs'

withDefaults(defineProps<{ mobile?: boolean }>(), { mobile: false })
const route = useRoute()
const english = computed(() => /^\/(?:en(?:\/|$)|versiones\/en(?:\/|$))/.test(route.path))
const language = computed(() => english.value ? 'es' : 'en')
const href = computed(() => withBase(translatedPath(route.path, language.value)))
// Archived English pages live outside /en/, so keep their document language
// correct after client-side navigation as well as in the generated HTML.
watch(() => route.path, () => {
if (typeof document !== 'undefined') document.documentElement.lang = english.value ? 'en' : 'es'
}, { immediate: true, flush: 'post' })
</script>

<template>
<a class="sc-language-switcher" :class="{ 'is-mobile': mobile }" :href="href"
:hreflang="language" :lang="language" rel="alternate"
:aria-label="english ? 'Leer esta documentación en español' : 'Read this documentation in English'">
{{ english ? 'Español' : 'English' }}
</a>
</template>

<style>
/* Replace the filename-based language menus with the explicit route map. */
.VPNavBarTranslations, .VPNavScreenTranslations, .VPNavBarExtra .translations { display: none !important; }
.sc-language-switcher {
display: inline-flex; align-items: center; margin-left: 12px; padding: 6px 8px;
white-space: nowrap; color: var(--vp-c-text-2); font-size: 13px;
}
.sc-language-switcher:hover { color: var(--vp-c-brand-1); }
.sc-language-switcher:focus-visible { outline: 2px solid var(--vp-c-brand-1); outline-offset: 2px; }
.sc-language-switcher.is-mobile { display: flex; margin: 12px 0; padding: 10px 0; }
@media (max-width: 767px) { .sc-language-switcher:not(.is-mobile) { display: none; } }
</style>
5 changes: 3 additions & 2 deletions docs/.vitepress/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ import { h } from 'vue'
import CookiePreferences from './CookiePreferences.vue'
import VersionSwitcher from './VersionSwitcher.vue'
import VersionNotice from './VersionNotice.vue'
import LanguageSwitcher from './LanguageSwitcher.vue'
import './home.css'
import './versions.css'

export default {
extends: DefaultTheme,
Layout: () => h(DefaultTheme.Layout, null, {
'layout-bottom': () => h(CookiePreferences),
'nav-bar-content-after': () => h(VersionSwitcher),
'nav-screen-content-after': () => h(VersionSwitcher, { mobile: true }),
'nav-bar-content-after': () => [h(LanguageSwitcher), h(VersionSwitcher)],
'nav-screen-content-after': () => [h(LanguageSwitcher, { mobile: true }), h(VersionSwitcher, { mobile: true })],
'doc-before': () => h(VersionNotice)
})
}
8 changes: 4 additions & 4 deletions docs/.vitepress/theme/version-routing.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ export function linkForVersion(input, targetVersion) {
if (targetVersion === 'v0.5.0-beta') {
if (section === 'index') return english ? '/en/' : '/'
if (section === 'examples') return english ? '/en/examples/' : '/ejemplos/'
return (english ? '/en/' : '/') + section + '.html'
return (english ? '/en/' : '/') + section
}
const base = '/versiones/' + (english ? 'en/' : '') + targetVersion + '/'
if (section === 'index') return base
if (section === 'examples') {
if (targetVersion === 'v0.4.3') return base + (english ? 'examples.html' : 'ejemplos.html')
return base + 'guide.html' // v0.3.6 has no archive examples page.
if (targetVersion === 'v0.4.3') return base + (english ? 'examples' : 'ejemplos')
return base + 'guide' // v0.3.6 has no archive examples page.
}
return base + section + '.html'
return base + section
}
7 changes: 5 additions & 2 deletions docs/en/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
# VitePress Home Page Configuration
layout: home
titleTemplate: false
title: SerialCraft — Arduino and ESP32 for Minecraft Fabric
description: SerialCraft by Leonardo Aliaga connects Arduino and ESP32 to Minecraft. Official documentation, source code, downloads and tutorials.

Expand Down Expand Up @@ -28,11 +29,13 @@ features:
- title: Code as Redstone
details: Control Redstone with real sensors or activate physical LEDs with in-game events.
- title: Native Multi-Language
details: The mod automatically detects your region. Available in English and Spanish (with localizations for Spain, Argentina, and Mexico).
details: The mod automatically detects your region. Available in English, Spanish (Spain, Argentina and Mexico), and Simplified Chinese.
---

# What is SerialCraft?

This is the official documentation for **SerialCraft**, the Minecraft Fabric mod by **Leonardo Aliaga**. The current version is **0.5.0-beta** for Minecraft 26.2 and Java 25. [Official Modrinth download](https://modrinth.com/mod/serialcraft) · [Source and releases on GitHub](https://github.com/LeonardoAliaga/SerialCraft).

**SerialCraft** is a mod for **Minecraft (Fabric)** that breaks the fourth wall, allowing real-time bidirectional communication between the game and external electronic devices.

### About the Authorship and Project Philosophy
Expand Down Expand Up @@ -62,7 +65,7 @@ The mod uses serial protocols (USB) and TCP sockets (Wi-Fi) to open a direct cha
SerialCraft is an **Open Source** project.
You are free to study the code, modify it, or use it in your modpacks.

* **Supported Languages:** English (US), Spanish (Spain, Argentina, Mexico).
* **Supported languages:** English (US), Spanish (Spain, Argentina, Mexico), and 简体中文 (Simplified Chinese).
* **Found a bug?** [Report it on GitHub](https://github.com/leonardoaliaga/serialcraft/issues).
---

Expand Down
73 changes: 73 additions & 0 deletions docs/en/tutorials/digital-analog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
title: Digital and analog signals between Minecraft and Arduino
description: Learn how SerialCraft converts redstone 0–15 to hardware values 0–255, and how digital signals differ from proportional readings and PWM.
---
# Digital and analog signals in SerialCraft

Minecraft redstone has **16 levels, from 0 to 15**. SerialCraft's hardware protocol uses **0–255 in both directions**. These ranges apply to **0.5.0-beta** on Minecraft 26.2.

Understanding the conversion helps you write compatible firmware and diagnose signals that appear stuck, dim or noisy.

## Digital: off or on

A module set to **Minecraft → Hardware** and **DIGITAL** sends zero when off and 255 when on. For a channel named `led_verde`, the messages are:

```text
led_verde:255
led_verde:0
```

Each message ends with a newline. In the other direction, any positive received value becomes full-strength redstone in DIGITAL mode. This is useful for buttons, switches and alarms.

## Analog: keep intermediate levels

<!-- SC046_MEDIA:I01 -->

ANALOG keeps intermediate numeric values. USB and TCP still carry digital text; Minecraft does not send an analog voltage through the cable.

```text
hardware value = redstone × 17
redstone ≈ hardware value / 17, rounded to the nearest level
```

| Redstone | Hardware value |
| ---: | ---: |
| 0 | 0 |
| 1 | 17 |
| 3 | 51 |
| 7 | 119 |
| 10 | 170 |
| 15 | 255 |

For example, an incoming value of 128 becomes redstone level 8 in ANALOG mode.

## PWM is not necessarily a continuous analog voltage

On an Arduino Uno R3, `analogWrite(9, 128)` normally produces **PWM**: rapid on/off pulses with approximately half the time spent on. This can control the apparent brightness of an LED, but it is not the same as a DAC output.

A potentiometer read with `analogRead(A0)` normally produces 0–1023 on an Uno R3. Map it to 0–255 before sending:

```cpp
int adc = analogRead(A0);
int wireValue = map(adc, 0, 1023, 0, 255);
Serial.print("pot_val:");
Serial.println(wireValue);
```

## Compare both modes

1. Configure an I/O Module as **Hardware → Minecraft**, channel `pot_val`.
2. Use the [potentiometer example](/en/tutorials/potentiometer).
3. Observe gradual redstone changes in ANALOG mode.
4. Change only the signal type to DIGITAL.
5. Compare the gradual reading with off/on output.

A lever has only two states; changing its module to ANALOG cannot create intermediate input levels.

## Common mistakes

- **A very dim LED:** old firmware may treat `1` as on; the current protocol sends `255` for digital on.
- **Readings jump between levels:** the sensor has more resolution than redstone and may also have electrical noise. Filter or add a small deadband in the firmware.
- **Direction and terminals are mixed up:** data direction and the green/red redstone connector roles are separate settings. See the [I/O Module guide](/en/tutorials/io-block).

Continue with the [protocol reference](/en/protocol) or [connection troubleshooting](/en/tutorials/troubleshooting).
2 changes: 2 additions & 0 deletions docs/en/tutorials/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ SerialCraft connects real-world microcontrollers to Minecraft Java. These guides
| [I/O Module: directions and channels](/en/tutorials/io-block) | Distinguish hardware-to-game direction from redstone input and output faces |
| [Arduino LED tutorial](/en/tutorials/arduino-led) | Drive an LED from a Minecraft redstone lever |
| [Potentiometer tutorial](/en/tutorials/potentiometer) | Send an analog reading to Minecraft |
| [Digital and analog signals](/en/tutorials/digital-analog) | Understand 0–15, 0–255 and PWM |
| [Visualizer and signal generator](/en/tutorials/visualizer) | Inspect RX/TX and waveforms |
| [Game telemetry](/en/tutorials/telemetry) | Send game states and events |
| [Testing the mod](/en/tutorials/testing) | JUnit, GameTests and CI |
| [Connection troubleshooting](/en/tutorials/troubleshooting) | Check USB, Wi-Fi, channels and redstone one layer at a time |

See also the detailed [official examples](/en/examples/) with Uno R3, ESP32 and Uno Q sketches, and the [protocol reference](/en/protocol) for baud rates, TCP port and signal conversions.

Expand Down
Loading
Loading