Skip to content
Open
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
2 changes: 1 addition & 1 deletion docs/BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ node --version # v18+ required
```bash
cp secrets.ini.example secrets.ini
```
Edit `secrets.ini` with your WiFi SSID and password. Optionally change the mDNS hostname (default: `ztrain`) and `MAX_PWM` to tune top speed (default: `500` — about half voltage to the track, which is plenty for Z-scale). These are plain PlatformIO `build_flags` (`-D WIFI_SSID='"..."'` etc. — note the single-quote wrapping, required so values with spaces survive PlatformIO's flag tokenizer) merged in via `extra_configs` in `platformio.ini` — `secrets.ini` is gitignored, so nothing here ever gets committed.
Edit `secrets.ini` with your WiFi SSID and password. Optionally change the mDNS hostname (default: `ztrain`), `MAX_PWM` to tune top speed (default: `1000` — full voltage to the track), and `PWM_FREQ` to tune the motor's PWM switching frequency in Hz (default: `20000` — above the audible range, so the motor doesn't whine). `analogWrite` on the ESP8266 is software PWM sharing one hardware timer across all 4 active channels (motor + R/G/B status LED), so pushing `PWM_FREQ` too high risks starving the WiFi/WebSocket stack — the build fails outright above `40000` as a safety ceiling. These are plain PlatformIO `build_flags` (`-D WIFI_SSID='"..."'` etc. — note the single-quote wrapping, required so values with spaces survive PlatformIO's flag tokenizer) merged in via `extra_configs` in `platformio.ini` — `secrets.ini` is gitignored, so nothing here ever gets committed.

3. **Install frontend dependencies:**
```bash
Expand Down
14 changes: 11 additions & 3 deletions firmware/z-duino/z-duino.ino
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@
#include <MotorLogic.h>
#include <Command.h>

#define VERSION "2.3.0"
#define VERSION "2.4.0"

// analogWrite on ESP8266 is software PWM sharing one hardware timer across
// all 4 active channels (motor + R/G/B status LED). Above ~40kHz the
// interrupt load starts contending with the WiFi/WebSocket stack, risking
// jitter and dropped connections.
#if PWM_FREQ > 40000
#error "PWM_FREQ exceeds the 40kHz safe ceiling for this board's shared software-PWM timer"
#endif

// WiFi credentials from secrets.ini
char wifi_ssid[] = WIFI_SSID;
Expand Down Expand Up @@ -186,8 +194,8 @@ void setup() {
pinMode(stby, OUTPUT);
digitalWrite(stby, HIGH);

// PWM config: 20kHz (above audible range), 0-1000 range
analogWriteFreq(20000);
// PWM config: frequency from secrets.ini (above audible range), 0-1000 range
analogWriteFreq(PWM_FREQ);
analogWriteRange(1000);

// Initialize LittleFS
Expand Down
4 changes: 2 additions & 2 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "z-duino-frontend",
"version": "2.3.0",
"version": "2.4.0",
"private": true,
"type": "module",
"scripts": {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/DebugModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
:class="i % 2 === 0 ? 'bg-neutral-800/50' : ''"
>
<td class="px-3 py-1.5 font-medium text-neutral-400 whitespace-nowrap">{{ row.label }}</td>
<td class="px-3 py-1.5 text-white break-all">{{ row.value }}</td>
<td class="px-3 py-1.5 break-all" :class="row.danger ? 'font-bold text-red-500' : 'text-white'">{{ row.value }}</td>
</tr>
</tbody>
</table>
Expand All @@ -34,7 +34,7 @@ import { useTrainController } from '../composables/useTrainController'
const { getDebugInfo } = useTrainController()

const isOpen = ref(false)
const rows = ref<{ label: string; value: string }[]>([])
const rows = ref<{ label: string; value: string; danger?: boolean }[]>([])

function open() {
rows.value = getDebugInfo()
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/SpeedController.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div class="mb-3">
<div class="flex justify-between text-sm text-neutral-400 mb-1">
<span><span class="font-bold">Speed:</span> {{ currentSpeed === 0 ? 'stopped' : Math.round(currentSpeed * 100) + '%' }}</span>
<span><span class="font-bold">Track:</span> {{ (12 * currentSpeed).toFixed(1) }}V</span>
<span :class="trackVoltageDanger ? 'font-bold text-red-500' : ''"><span class="font-bold">Track:</span> {{ trackVoltage.toFixed(1) }}V</span>
</div>
<div class="flex w-full">
<button
Expand Down Expand Up @@ -61,6 +61,8 @@ const {
connected,
currentSpeed,
currentDirection,
trackVoltage,
trackVoltageDanger,
getSegmentClass,
setSpeed,
toggleDirection,
Expand Down
15 changes: 11 additions & 4 deletions frontend/src/composables/useTrainController.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ref } from 'vue'
import { computed, ref } from 'vue'

// WebSocket protocol types
interface OutgoingSpeed { cmd: 'speed'; value: number }
Expand Down Expand Up @@ -48,6 +48,8 @@ const DIRECTION_PAUSE = 500
const RECONNECT_BASE = 1000
const RECONNECT_MAX = 30000
const PING_INTERVAL = 10000
const SUPPLY_VOLTAGE = 12
const TRACK_VOLTAGE_DANGER_THRESHOLD = 10

export const powerLevels = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]

Expand Down Expand Up @@ -76,6 +78,9 @@ const ledBright = ref(true)
const activeLedColor = ref<string | null>(null)
const partyMode = ref(false)

const trackVoltage = computed(() => SUPPLY_VOLTAGE * currentSpeed.value)
const trackVoltageDanger = computed(() => trackVoltage.value > TRACK_VOLTAGE_DANGER_THRESHOLD)

let rampTimer: ReturnType<typeof setInterval> | null = null
let partyTimer: ReturnType<typeof setTimeout> | null = null
let reconnectDelay = RECONNECT_BASE
Expand Down Expand Up @@ -347,7 +352,7 @@ function togglePartyMode() {

const WS_READY_STATES = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'] as const

function getDebugInfo(): { label: string; value: string }[] {
function getDebugInfo(): { label: string; value: string; danger?: boolean }[] {
const wsUrl = socket.value ? socket.value.url : `ws://${location.hostname || 'localhost'}:81/`
const wsState = socket.value ? WS_READY_STATES[socket.value.readyState] : 'N/A'

Expand All @@ -359,8 +364,8 @@ function getDebugInfo(): { label: string; value: string }[] {
{ label: 'WebSocket State', value: wsState },
{ label: 'Reconnect Delay', value: `${reconnectDelay}ms` },
{ label: 'Current Speed', value: currentSpeed.value === 0 ? 'stopped' : `${Math.round(currentSpeed.value * 100)}%` },
{ label: 'Supply Voltage', value: '12V' },
{ label: 'Track Voltage', value: `${(12 * currentSpeed.value).toFixed(1)}V` },
{ label: 'Supply Voltage', value: `${SUPPLY_VOLTAGE}V` },
{ label: 'Track Voltage', value: `${trackVoltage.value.toFixed(1)}V`, danger: trackVoltageDanger.value },
{ label: 'Direction', value: currentDirection.value ? 'FWD' : 'REV' },
{ label: 'Direction Inverted', value: String(directionInverted.value) },
{ label: 'Ramping', value: String(rampTimer !== null) },
Expand Down Expand Up @@ -390,6 +395,8 @@ export function useTrainController() {
currentSpeed,
currentDirection,
directionInverted,
trackVoltage,
trackVoltageDanger,
ledTestMode,
ledBright,
activeLedColor,
Expand Down
3 changes: 2 additions & 1 deletion secrets.ini.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ build_flags =
-D WIFI_PASS='"YourPasswordHere"'
-D MDNS_HOSTNAME='"ztrain"'
-D RAILROAD_NAME='"Z-Duino"'
-D MAX_PWM=500
-D MAX_PWM=1000
-D PWM_FREQ=20000
Loading