{"id":50175502,"url":"https://github.com/techniker/wachendorff_controller","last_synced_at":"2026-05-25T03:05:07.331Z","repository":{"id":346454760,"uuid":"1190341010","full_name":"techniker/wachendorff_controller","owner":"techniker","description":"Control a Wachendorff PID Controller via Modbus","archived":false,"fork":false,"pushed_at":"2026-03-24T07:33:25.000Z","size":2282,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-03-24T22:42:30.758Z","etag":null,"topics":["controller","modbus","mqtt","pid","rs485","serial","wachendorff","webui"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/techniker.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-24T07:31:29.000Z","updated_at":"2026-03-24T07:32:45.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/techniker/wachendorff_controller","commit_stats":null,"previous_names":["techniker/wachendorff_controller"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/techniker/wachendorff_controller","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/techniker%2Fwachendorff_controller","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/techniker%2Fwachendorff_controller/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/techniker%2Fwachendorff_controller/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/techniker%2Fwachendorff_controller/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/techniker","download_url":"https://codeload.github.com/techniker/wachendorff_controller/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/techniker%2Fwachendorff_controller/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33458464,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-25T02:24:28.008Z","status":"ssl_error","status_checked_at":"2026-05-25T02:23:23.339Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["controller","modbus","mqtt","pid","rs485","serial","wachendorff","webui"],"created_at":"2026-05-25T03:05:02.914Z","updated_at":"2026-05-25T03:05:07.325Z","avatar_url":"https://github.com/techniker.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Wachendorff URDR Controller\n\nWeb-based interface for Wachendorff URDR0001 PID temperature controllers via RS485 Modbus RTU.\n\n## Features\n\n- **Live monitoring** — real-time process value, setpoint, and output percentage via WebSocket\n- **Temperature \u0026 output history** — Chart.js graphs with rolling time window\n- **PID parameter control** — read/write proportional band, integral/derivative time, cycle time\n- **PID visualization** — block diagram showing controller loop with live values\n- **Setpoint management** — 4 setpoints with +/- nudge buttons, alarms, controller start/stop/autotune\n- **MQTT integration** — publish controller values to MQTT broker, subscribe for remote control\n- **Per-endpoint MQTT config** — individual topics, publish intervals, QoS, enable/disable per endpoint\n- **Authentication** — session-based login, write operations protected, password change via UI\n- **RS485 configuration** — serial port, baud rate, slave address, delay — all configurable via UI\n- **Auto-discovery** — scan the RS485 bus for connected URDR devices\n- **Configuration persistence** — YAML config file survives restarts\n- **Docker-ready** — Dockerfile and docker-compose with serial device passthrough\n\n## Quick Start\n\n### Local (virtualenv)\n\n```bash\npython3 -m venv venv\nsource venv/bin/activate\npip install -r requirements.txt\ncp config.yaml.example config.yaml   # first time only\npython -m app.main\n```\n\nOpen http://localhost:5001\n\n### Docker\n\n```bash\ndocker-compose up -d\n```\n\nMake sure the container has access to the serial device. On Linux, add your user to the `dialout` group:\n\n```bash\nsudo usermod -aG dialout $USER\n```\n\nThe `docker-compose.yml` maps `/dev/ttyUSB0` and `/dev/ttyUSB1` by default. Edit as needed for your setup.\n\n## Authentication\n\nAll write operations (changing setpoints, PID parameters, configuration, controller start/stop) require authentication. Read-only monitoring (dashboard, live values, charts) is accessible without login.\n\n- **Default credentials**: `admin` / `admin`\n- **Session timeout**: 60 minutes (configurable)\n- **Password change**: available via the header button when logged in\n- **Storage**: bcrypt-hashed password in `config.yaml`\n\nThe login modal appears automatically when attempting a write action without being logged in.\n\n## MQTT\n\nPublish controller values to an MQTT broker and optionally receive remote commands. Configure via the MQTT tab in the web UI or in `config.yaml`.\n\n### Publish Endpoints (default)\n\n| Key | Topic | Interval |\n|-----|-------|----------|\n| process_value | urdr/process_value | 5s |\n| setpoint | urdr/setpoint | 10s |\n| heating_output | urdr/heating_output | 5s |\n| cooling_output | urdr/cooling_output | 5s |\n| controller_running | urdr/controller_running | 10s |\n| error_flags | urdr/error_flags | 10s |\n\n### Subscribe Endpoints (disabled by default)\n\n| Key | Topic | Payload |\n|-----|-------|---------|\n| setpoint_write | urdr/setpoint/set | Float value (e.g. `25.0`) |\n| controller_cmd | urdr/controller/cmd | `start`, `stop`, or `autotune` |\n\nEach endpoint can be individually enabled/disabled, with custom topics, publish intervals, and QoS levels. Settings are saved to `config.yaml` and persist across restarts. Connecting to a broker sets `mqtt.enabled: true` so it auto-reconnects on restart.\n\n## Configuration\n\nEdit `config.yaml` or use the web UI Configuration/MQTT tabs:\n\n```yaml\nserial:\n  port: /dev/ttyUSB0\n  baudrate: 19200        # 4800, 9600, 19200, 28800, 38400, 57600\n  slave_address: 1       # 1-254\n  timeout: 1.0\n  serial_delay_ms: 20\ncontroller:\n  poll_interval: 1.0\n  auto_connect: false\nweb:\n  host: 0.0.0.0\n  port: 5001\nmqtt:\n  enabled: false\n  broker: localhost\n  port: 1883\n  username: \"\"\n  password: \"\"\n  endpoints:             # Per-endpoint config (topic, interval, qos, enabled)\n    - key: process_value\n      topic: urdr/process_value\n      direction: publish\n      enabled: true\n      interval: 5.0\n      qos: 0\n    # ... more endpoints\nauth:\n  username: admin\n  password_hash: \u003cbcrypt hash\u003e\n  session_timeout_minutes: 60\n```\n\n## Modbus Protocol\n\n- **Protocol**: Modbus RTU over RS485\n- **Function codes**: 0x03/0x04 (read), 0x06 (write single), 0x10 (write multiple)\n- **Default**: 19200 baud, 8N1\n- **Temperature registers**: always in degrees x 10 (tenths), regardless of `d.P.` display setting\n\n## Architecture\n\n```\napp/\n├── main.py              # FastAPI entry point, lifespan, WebSocket endpoint\n├── config.py            # YAML config load/save\n├── auth.py              # Session-based authentication, bcrypt password hashing\n├── mqtt.py              # MQTT client with per-endpoint publish/subscribe\n├── modbus/\n│   ├── registers.py     # Complete URDR0001 register map\n│   ├── client.py        # Async Modbus RTU client (pymodbus)\n│   ├── scanner.py       # Auto-discovery scanner\n│   └── poller.py        # Background polling with subscriber callbacks\n├── api/\n│   ├── routes.py        # REST API endpoints (POST routes auth-protected)\n│   └── websocket.py     # WebSocket live data broadcast\n└── web/static/\n    ├── index.html       # Single-page app with login/password modals\n    ├── app.js           # Frontend logic + Chart.js + auth handling\n    └── style.css        # Dark theme, responsive\n```\n\n## API Endpoints\n\n| Method | Path | Auth | Description |\n|--------|------|------|-------------|\n| GET | `/api/status` | No | Current live data |\n| GET | `/api/setpoints` | No | Read setpoints |\n| GET | `/api/pid` | No | Read PID parameters |\n| GET | `/api/alarms` | No | Read alarm values |\n| GET | `/api/config` | No | Application configuration |\n| GET | `/api/scan` | No | Scan progress/results |\n| GET | `/api/auth/status` | No | Check login state |\n| WS | `/ws/live` | No | WebSocket live data stream |\n| POST | `/api/connect` | Yes | Connect to controller |\n| POST | `/api/disconnect` | Yes | Disconnect |\n| POST | `/api/setpoints` | Yes | Write setpoints |\n| POST | `/api/pid` | Yes | Write PID parameters |\n| POST | `/api/alarms` | Yes | Write alarm values |\n| POST | `/api/controller/start` | Yes | Start controller |\n| POST | `/api/controller/stop` | Yes | Stop controller |\n| POST | `/api/controller/autotune` | Yes | Start autotune |\n| POST | `/api/controller/mode` | Yes | Set auto/manual mode |\n| POST | `/api/config/serial` | Yes | Update serial config |\n| POST | `/api/config/controller` | Yes | Update polling config |\n| POST | `/api/scan` | Yes | Start auto-discovery |\n| POST | `/api/scan/select/{addr}` | Yes | Select discovered device |\n| GET | `/api/mqtt` | No | MQTT status, config, endpoints |\n| POST | `/api/mqtt/config` | Yes | Update broker settings |\n| POST | `/api/mqtt/endpoints` | Yes | Update endpoint config |\n| POST | `/api/mqtt/connect` | Yes | Connect to MQTT broker |\n| POST | `/api/mqtt/disconnect` | Yes | Disconnect from broker |\n| POST | `/api/auth/login` | No | Login |\n| POST | `/api/auth/logout` | No | Logout |\n| POST | `/api/auth/change-password` | Yes | Change password |\n\n## Hardware\n\n- **Controller**: Wachendorff URDR0001 (DIN rail mount, 24-230V AC/DC)\n- **Interface**: RS485 Modbus RTU (terminals 19=A, 20=B)\n- **Adapter**: USB-to-RS485 (CH340, FTDI, PL2303, etc.)\n\n## Documentation\n\nController manuals are in the `doc/` folder.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftechniker%2Fwachendorff_controller","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftechniker%2Fwachendorff_controller","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftechniker%2Fwachendorff_controller/lists"}