jmri_server.topic_help
Human help for each JSON API topic.
Rendered as an accordion under the "Topics available on the JSON API"
card on the home tab. Each TopicHelp entry gets one badge on
the home page that, when clicked, expands to show the summary, a
methods table, and an example round-trip.
Content policy — every method entry needs at minimum a
one-sentence description and either an example request or a note
saying no data is required. Keep examples short: they're for
"remind me what the shape is," not full protocol docs. Full protocol
docs live in the generated pdoc reference under /docs/.
Adding a new service:
- Register a new
TopicHelpinTOPIC_HELP. - If you can't fill in an example immediately, use
example_request=None— the UI hides the block cleanly. - The
sourcefield is a short pointer for developers reading the reference; usuallyjmri_server.services.<module>.<Class>.
1"""Human help for each JSON API topic. 2 3Rendered as an accordion under the "Topics available on the JSON API" 4card on the home tab. Each :class:`TopicHelp` entry gets one badge on 5the home page that, when clicked, expands to show the summary, a 6methods table, and an example round-trip. 7 8**Content policy** — every method entry needs at minimum a 9one-sentence description and either an example request or a note 10saying no data is required. Keep examples short: they're for 11"remind me what the shape is," not full protocol docs. Full protocol 12docs live in the generated pdoc reference under ``/docs/``. 13 14Adding a new service: 15 16* Register a new :class:`TopicHelp` in :data:`TOPIC_HELP`. 17* If you can't fill in an example immediately, use 18 ``example_request=None`` — the UI hides the block cleanly. 19* The ``source`` field is a short pointer for developers reading the 20 reference; usually ``jmri_server.services.<module>.<Class>``. 21""" 22from __future__ import annotations 23 24from dataclasses import dataclass, field 25 26 27@dataclass(frozen=True, slots=True) 28class MethodHelp: 29 """One method (``list`` / ``get`` / ``put`` / ``post`` / ``delete``).""" 30 31 name: str 32 description: str 33 example_request: str | None = None 34 example_response: str | None = None 35 36 37@dataclass(frozen=True, slots=True) 38class TopicHelp: 39 """Everything the accordion needs to render one topic's help panel.""" 40 41 topic: str 42 summary: str 43 methods: list[MethodHelp] = field(default_factory=list) 44 # Short "read the code" pointer — module.ClassName, no leading dots. 45 source: str = "" 46 # Any topic that isn't in JMRI Java's original API gets ``True`` so 47 # the UI can badge it as rewrite-specific. Keeps client-side JMRI 48 # tooling from expecting it against a legacy server. 49 rewrite_extension: bool = False 50 51 52# ---- shared examples --------------------------------------------------- 53 54# Every bean list follows this shape, so keep one canonical example 55# to avoid drift across the eight bean services. 56_BEAN_LIST_EXAMPLE = '{"type": "turnout", "method": "list"}' 57_BEAN_LIST_RESPONSE = ( 58 '{"type": "list", "data": [\n' 59 ' {"name": "IT1", "userName": "Main", "state": 2, "inverted": false}\n' 60 "]}" 61) 62_BEAN_GET_EXAMPLE = ( 63 '{"type": "turnout", "method": "get", "data": {"name": "IT1"}}' 64) 65_BEAN_POST_EXAMPLE = ( 66 '{"type": "turnout", "method": "post",\n' 67 ' "data": {"name": "IT1", "state": 2}}' 68) 69 70 71def _bean_methods(kind: str) -> list[MethodHelp]: 72 """The standard four-method surface every bean service exposes.""" 73 return [ 74 MethodHelp( 75 "list", f"Every registered {kind} with its current state.", 76 example_request=f'{{"type": "{kind}", "method": "list"}}', 77 example_response=_BEAN_LIST_RESPONSE.replace("turnout", kind), 78 ), 79 MethodHelp( 80 "get", f"One {kind}'s current state by system name.", 81 example_request=_BEAN_GET_EXAMPLE.replace("turnout", kind), 82 ), 83 MethodHelp( 84 "post", f"Change one {kind}'s state (throw/close, active/inactive).", 85 example_request=_BEAN_POST_EXAMPLE.replace("turnout", kind), 86 ), 87 MethodHelp( 88 "put", f"Create or update a {kind}. Idempotent — a repeated " 89 "put with the same fields is a no-op.", 90 ), 91 ] 92 93 94# ---- topic help registry ---------------------------------------------- 95 96 97TOPIC_HELP: dict[str, TopicHelp] = { 98 # ---- meta --------------------------------------------------------- 99 "hello": TopicHelp( 100 topic="hello", 101 summary=( 102 "WebSocket handshake response. Emitted by the server as soon " 103 "as a client connects; carries the server's clock heartbeat " 104 "interval so a client can tell when to expect keep-alives." 105 ), 106 source="jmri_server.services.meta.HelloService", 107 ), 108 "ping": TopicHelp( 109 topic="ping", 110 summary=( 111 "Liveness probe. Send ``{\"type\": \"ping\"}`` and get " 112 "``{\"type\": \"pong\"}`` back — useful to keep an idle " 113 "WebSocket connection alive through a stateful firewall." 114 ), 115 methods=[ 116 MethodHelp( 117 "get", "Round-trip ping — returns pong.", 118 example_request='{"type": "ping"}', 119 example_response='{"type": "pong", "data": {}}', 120 ), 121 ], 122 source="jmri_server.services.meta.PingService", 123 ), 124 "version": TopicHelp( 125 topic="version", 126 summary=( 127 "Server version string. Matches the JMRI Java ``version`` " 128 "topic so a legacy JMRI client can gate features by server " 129 "version. Ours reports the ``jmri-server`` package version." 130 ), 131 methods=[ 132 MethodHelp( 133 "get", "Report the running server's version.", 134 example_request='{"type": "version"}', 135 example_response='{"type": "version", "data": {"version": "0.1.0b3"}}', 136 ), 137 ], 138 source="jmri_server.services.meta.VersionService", 139 ), 140 "railroad": TopicHelp( 141 topic="railroad", 142 summary=( 143 "Layout metadata — the railroad's user-set name from the " 144 "active profile. Read-only in the current build; JMRI Java " 145 "allows editing here but we defer to profile-edit for now." 146 ), 147 methods=[ 148 MethodHelp( 149 "get", "Fetch the railroad name.", 150 example_request='{"type": "railroad"}', 151 ), 152 ], 153 source="jmri_server.services.meta.RailroadService", 154 ), 155 "node": TopicHelp( 156 topic="node", 157 summary=( 158 "This server's identity as a networking peer — hostname, " 159 "profile id, uuid. Consumed by external tools that need to " 160 "distinguish multiple JMRI instances on one LAN." 161 ), 162 methods=[ 163 MethodHelp("get", "Report node identity.", 164 example_request='{"type": "node"}'), 165 ], 166 source="jmri_server.services.meta.NodeService", 167 ), 168 "power": TopicHelp( 169 topic="power", 170 summary=( 171 "Track power state (ON / OFF / UNKNOWN). Wired through the " 172 "active connection's ``PowerManager``. For OpenLCB, " 173 "``set`` emits the well-known power events; for LocoNet / " 174 "NCE / DCC-EX, it sends the driver-specific power packet." 175 ), 176 methods=[ 177 MethodHelp( 178 "get", "Current power state.", 179 example_request='{"type": "power"}', 180 example_response=( 181 '{"type": "power", "data": {"state": 2}}\n' 182 "// state: 2 = ON, 4 = OFF, 0 = UNKNOWN (JMRI convention)" 183 ), 184 ), 185 MethodHelp( 186 "post", "Set power on or off. State ``2`` = ON, ``4`` = OFF.", 187 example_request='{"type": "power", "method": "post", "data": {"state": 2}}', 188 ), 189 ], 190 source="jmri_server.services.meta.PowerService", 191 ), 192 193 # ---- beans -------------------------------------------------------- 194 "turnout": TopicHelp( 195 topic="turnout", 196 summary=( 197 "Turnout points (switch machines). State: 2 = closed, " 198 "4 = thrown. Post the target state to command a change; " 199 "listeners on the WebSocket get pushes on every observed " 200 "transition, including hardware-initiated ones." 201 ), 202 methods=_bean_methods("turnout"), 203 source="jmri_server.services.beans.TurnoutService", 204 ), 205 "sensor": TopicHelp( 206 topic="sensor", 207 summary=( 208 "Track occupancy / block detection sensors. State: 2 = " 209 "active (occupied), 4 = inactive. Typically read-only from " 210 "hardware; the post method is available for testing or " 211 "simulated layouts." 212 ), 213 methods=_bean_methods("sensor"), 214 source="jmri_server.services.beans.SensorService", 215 ), 216 "light": TopicHelp( 217 topic="light", 218 summary=( 219 "Layout lights (buildings, streetlights, tower lamps). " 220 "Modeled as two-state beans on most drivers; some support " 221 "variable intensity via CV writes." 222 ), 223 methods=_bean_methods("light"), 224 source="jmri_server.services.beans.LightService", 225 ), 226 "signalHead": TopicHelp( 227 topic="signalHead", 228 summary=( 229 "Individual signal heads — single-aspect indicators driven " 230 "by a bank of outputs. Composed into signal masts by the " 231 "``signalMast`` service for multi-aspect signalling." 232 ), 233 methods=_bean_methods("signalHead"), 234 source="jmri_server.services.beans.SignalHeadService", 235 ), 236 "signalMast": TopicHelp( 237 topic="signalMast", 238 summary=( 239 "Signal masts — multi-aspect signals composed of one or " 240 "more heads with a named signalling system (e.g. AAR-1946, " 241 "NORAC). State is an aspect string like ``\"Clear\"`` or " 242 "``\"Approach Medium\"``." 243 ), 244 methods=_bean_methods("signalMast"), 245 source="jmri_server.services.beans.SignalMastService", 246 ), 247 "memory": TopicHelp( 248 topic="memory", 249 summary=( 250 "Memory variables — free-form named values used by LogixNG " 251 "logic and panel scripts. Not persistent by default; " 252 "declaring a Memory in PanelPro.xml makes it stick." 253 ), 254 methods=_bean_methods("memory"), 255 source="jmri_server.services.beans.MemoryService", 256 ), 257 "reporter": TopicHelp( 258 topic="reporter", 259 summary=( 260 "Reporters — beans that carry a last-seen value (typically " 261 "a loco address from a Digitrax transponding zone or an " 262 "RFID reader). Different from sensors because they carry " 263 "structured payloads, not just on/off state." 264 ), 265 methods=_bean_methods("reporter"), 266 source="jmri_server.services.beans.ReporterService", 267 ), 268 "block": TopicHelp( 269 topic="block", 270 summary=( 271 "Blocks — segments of track with associated occupancy and " 272 "the loco address currently in them. Feeds routing / " 273 "signalling / dispatcher logic." 274 ), 275 methods=_bean_methods("block"), 276 source="jmri_server.services.beans.BlockService", 277 ), 278 "route": TopicHelp( 279 topic="route", 280 summary=( 281 "Routes — named collections of turnout positions activated " 282 "together (e.g. ``\"Yard Lead Left\"`` throws the ladder " 283 "into a specific yard track). Static; for conditional " 284 "routing use LogixNG." 285 ), 286 methods=_bean_methods("route"), 287 source="jmri_server.services.beans.RouteService", 288 ), 289 "audio": TopicHelp( 290 topic="audio", 291 summary=( 292 "Layout audio sources — sound files triggered by scripts " 293 "or LogixNG. State controls playback (play, stop, loop)." 294 ), 295 methods=_bean_methods("audio"), 296 source="jmri_server.services.audio.AudioService", 297 ), 298 "idTag": TopicHelp( 299 topic="idTag", 300 summary=( 301 "ID tags — the values a Reporter sees. Modeling RFID / " 302 "transponder tokens so LogixNG can match against the " 303 "*identity* rather than the reporter that saw it." 304 ), 305 methods=_bean_methods("idTag"), 306 source="jmri_server.services.id_tag.IdTagService", 307 ), 308 "layoutBlock": TopicHelp( 309 topic="layoutBlock", 310 summary=( 311 "Layout blocks — a routing-graph abstraction over one or " 312 "more ``block`` beans. Adds physical connectivity used by " 313 "the automatic dispatcher and by SSL signalling." 314 ), 315 methods=_bean_methods("layoutBlock"), 316 source="jmri_server.services.layout_block.LayoutBlockService", 317 ), 318 "oblock": TopicHelp( 319 topic="oblock", 320 summary=( 321 "OBlocks (\"occupancy blocks\") — the Warrant system's " 322 "richer block model, with per-block path definitions and " 323 "signalling state. Used by Warrants and the automatic " 324 "dispatcher." 325 ), 326 methods=_bean_methods("oblock"), 327 source="jmri_server.services.oblock.OBlockService", 328 ), 329 330 # ---- programming + throttles -------------------------------------- 331 "throttle": TopicHelp( 332 topic="throttle", 333 summary=( 334 "Loco control — acquire an address, set speed, set " 335 "functions, release. One throttle per DCC address on the " 336 "active connection." 337 ), 338 methods=[ 339 MethodHelp( 340 "put", "Acquire a throttle for a DCC address.", 341 example_request=( 342 '{"type": "throttle", "method": "put",\n' 343 ' "data": {"address": 3, "long_address": false}}' 344 ), 345 ), 346 MethodHelp( 347 "post", "Update speed / direction / function state.", 348 example_request=( 349 '{"type": "throttle", "method": "post",\n' 350 ' "data": {"address": 3, "speed": 0.5, ' 351 '"direction": "forward", "functions": {"0": true}}}' 352 ), 353 ), 354 MethodHelp( 355 "delete", "Release the throttle (clean release, keeps " 356 "the loco rolling at its last commanded speed until " 357 "the CS purge timeout).", 358 example_request=( 359 '{"type": "throttle", "method": "delete",\n' 360 ' "data": {"address": 3}}' 361 ), 362 ), 363 ], 364 source="jmri_server.services.throttle.ThrottleService", 365 ), 366 "programmer": TopicHelp( 367 topic="programmer", 368 summary=( 369 "CV read / write on the programming track (service mode) " 370 "or on a running loco (ops mode). Ops-mode reads require " 371 "RailCom on the CS + decoder. Requests block for 1–3s per " 372 "CV on service mode." 373 ), 374 methods=[ 375 MethodHelp( 376 "get", "Read one CV.", 377 example_request=( 378 '{"type": "programmer", "method": "get",\n' 379 ' "data": {"cv": 29, "mode": "service"}}' 380 ), 381 ), 382 MethodHelp( 383 "post", "Write one CV.", 384 example_request=( 385 '{"type": "programmer", "method": "post",\n' 386 ' "data": {"cv": 29, "value": 6, "mode": "service"}}' 387 ), 388 ), 389 ], 390 source="jmri_server.services.programmer.ProgrammerService", 391 ), 392 "roster": TopicHelp( 393 topic="roster", 394 summary=( 395 "Roster entries — the profile's loco list, each with a " 396 "decoder family/model/productID and per-loco function " 397 "labels. Backed by ``<profile>/roster.xml``." 398 ), 399 methods=[ 400 MethodHelp( 401 "list", "Every roster entry in the active profile.", 402 example_request='{"type": "roster", "method": "list"}', 403 ), 404 MethodHelp( 405 "get", "One entry by roster ID.", 406 example_request=( 407 '{"type": "roster", "method": "get", "data": {"id": "3601"}}' 408 ), 409 ), 410 MethodHelp( 411 "put", "Create or update a roster entry.", 412 ), 413 MethodHelp( 414 "delete", "Remove a roster entry from the profile.", 415 ), 416 ], 417 source="jmri_server.services.roster.RosterService", 418 ), 419 420 # ---- automation --------------------------------------------------- 421 "consist": TopicHelp( 422 topic="consist", 423 summary=( 424 "Loco consists (multi-unit lash-ups). Add / remove locos, " 425 "set direction-relative-to-consist, kill the consist. " 426 "Backed by the active connection's ``ConsistManager``." 427 ), 428 methods=[ 429 MethodHelp( 430 "list", "Every registered consist.", 431 example_request='{"type": "consist", "method": "list"}', 432 ), 433 MethodHelp( 434 "put", "Create a new consist at a given address.", 435 example_request=( 436 '{"type": "consist", "method": "put",\n' 437 ' "data": {"address": 20}}' 438 ), 439 ), 440 MethodHelp( 441 "post", "Add or remove a member, set direction.", 442 example_request=( 443 '{"type": "consist", "method": "post", "data":\n' 444 ' {"address": 20, "action": "add", "loco": 3601,\n' 445 ' "direction_normal": true}}' 446 ), 447 ), 448 MethodHelp( 449 "delete", "Kill the consist.", 450 example_request=( 451 '{"type": "consist", "method": "delete",\n' 452 ' "data": {"address": 20}}' 453 ), 454 ), 455 ], 456 source="jmri_server.services.consist.ConsistService", 457 ), 458 "logixng": TopicHelp( 459 topic="logixng", 460 summary=( 461 "LogixNG programs — declarative automation trees. Each " 462 "program has a trigger (turnout thrown, sensor active, " 463 "clock time, …) and an action tree (set turnout, run " 464 "route, invoke a script). Stored per-profile as YAML." 465 ), 466 methods=[ 467 MethodHelp( 468 "list", "Every loaded LogixNG program.", 469 example_request='{"type": "logixng", "method": "list"}', 470 ), 471 MethodHelp( 472 "get", "One program's structure by system name.", 473 ), 474 MethodHelp( 475 "post", "Enable / disable a program at runtime.", 476 ), 477 ], 478 source="jmri_server.services.logixng.LogixNGService", 479 ), 480 "signalSystem": TopicHelp( 481 topic="signalSystem", 482 summary=( 483 "Loaded signal systems (AAR-1946, NORAC, custom). Read-" 484 "only; systems are defined by XML files under the profile " 485 "and enumerated on load." 486 ), 487 methods=[ 488 MethodHelp( 489 "list", "Every loaded signal system with its aspects.", 490 example_request='{"type": "signalSystem", "method": "list"}', 491 ), 492 MethodHelp("get", "One signal system by name."), 493 ], 494 source="jmri_server.services.signal_system.SignalSystemService", 495 ), 496 497 # ---- infra -------------------------------------------------------- 498 "time": TopicHelp( 499 topic="time", 500 summary=( 501 "Fast clock — layout time separate from wall-clock time. " 502 "Rate is a multiplier; setting rate to 0 pauses it. " 503 "``time`` is an ISO-8601 timestamp of the current fast-" 504 "clock reading." 505 ), 506 methods=[ 507 MethodHelp( 508 "get", "Current fast-clock reading + rate.", 509 example_request='{"type": "time"}', 510 ), 511 MethodHelp( 512 "post", "Set the time or the rate.", 513 example_request=( 514 '{"type": "time", "method": "post",\n' 515 ' "data": {"rate": 4.0, "run": true}}' 516 ), 517 ), 518 ], 519 source="jmri_server.services.fast_clock.TimeService", 520 ), 521 "panel": TopicHelp( 522 topic="panel", 523 summary=( 524 "Panel definitions — the operator's on-screen layout " 525 "diagram. Currently exposes the list of loaded panels; the " 526 "full panel-render pipeline is a future item." 527 ), 528 methods=[ 529 MethodHelp( 530 "list", "Every loaded panel in the profile.", 531 example_request='{"type": "panel", "method": "list"}', 532 ), 533 ], 534 source="jmri_server.services.panel.PanelService", 535 ), 536 "metadata": TopicHelp( 537 topic="metadata", 538 summary=( 539 "Server metadata — JMRI version, copyright, activation " 540 "profile, Java version (N/A in our Python build), OS. " 541 "Consumed by legacy JMRI clients for feature detection." 542 ), 543 methods=[ 544 MethodHelp("list", "Every metadata field."), 545 MethodHelp( 546 "get", "One metadata value by name.", 547 example_request=( 548 '{"type": "metadata", "method": "get",\n' 549 ' "data": {"name": "JMRIVERCANON"}}' 550 ), 551 ), 552 ], 553 source="jmri_server.services.util.MetadataService", 554 ), 555 "networkService": TopicHelp( 556 topic="networkService", 557 summary=( 558 "Advertised network services — WiThrottle TCP port, " 559 "WebSocket URL, HTTP root. Legacy JMRI clients enumerate " 560 "these to auto-configure." 561 ), 562 methods=[ 563 MethodHelp("list", "Every advertised network service."), 564 ], 565 source="jmri_server.services.util.NetworkServiceService", 566 ), 567 "systemConnection": TopicHelp( 568 topic="systemConnection", 569 summary=( 570 "The active hardware connection (NCE / LocoNet / DCC-EX / " 571 "OpenLCB / internal). Read-only enumeration; connection " 572 "config lives in ``ConnectionConfig.xml`` under the " 573 "profile." 574 ), 575 methods=[ 576 MethodHelp("list", "Every configured system connection."), 577 ], 578 source="jmri_server.services.util.SystemConnectionService", 579 ), 580 581 # ---- rewrite-specific --------------------------------------------- 582 "logs": TopicHelp( 583 topic="logs", 584 summary=( 585 "Server log tail — the last N lines from the running " 586 "jmri.log. Used by the home-tab log strip and the /logs " 587 "diagnostics page." 588 ), 589 methods=[ 590 MethodHelp( 591 "list", "Last N lines from jmri.log.", 592 example_request='{"type": "logs", "method": "list", "data": {"tail": 50}}', 593 ), 594 ], 595 source="jmri_server.services.logs.LogsService", 596 rewrite_extension=True, 597 ), 598 "configUser": TopicHelp( 599 topic="configUser", 600 summary=( 601 "User management + self-service password/token change. " 602 "Admin-only for list/get/put/delete; the ``change_password`` " 603 "and ``regenerate_token`` post actions are self-service for " 604 "any authenticated user (admin can act on anyone). Never " 605 "returns the password hash — only whether one exists." 606 ), 607 methods=[ 608 MethodHelp("list", "Every registered user with role + has-token."), 609 MethodHelp( 610 "put", "Create or update a user.", 611 example_request=( 612 '{"type": "configUser", "method": "put", "data": {\n' 613 ' "username": "alice",\n' 614 ' "password": "secret",\n' 615 ' "role": "operator"\n' 616 "}}" 617 ), 618 ), 619 MethodHelp( 620 "delete", "Remove a user. Refuses self-delete + last-admin.", 621 ), 622 MethodHelp( 623 "post", 624 "action=change_password → change your own or (as admin) " 625 "someone else's password. action=regenerate_token → mint " 626 "a new API token; the old one stops working immediately.", 627 example_request=( 628 '{"type": "configUser", "method": "post", "data": {\n' 629 ' "action": "regenerate_token",\n' 630 ' "username": "alice"\n' 631 "}}" 632 ), 633 ), 634 ], 635 source="jmri_server.services.config_user.ConfigUserService", 636 rewrite_extension=True, 637 ), 638 "configProfile": TopicHelp( 639 topic="configProfile", 640 summary=( 641 "Create / delete / list JMRI profiles under the settings " 642 "root. Delete only unregisters the profile from " 643 "``profiles.xml`` — the on-disk directory is left in " 644 "place so an accidental UI click can't destroy layout " 645 "data." 646 ), 647 methods=[ 648 MethodHelp("list", "Every registered profile."), 649 MethodHelp( 650 "put", "Create a new profile.", 651 example_request=( 652 '{"type": "configProfile", "method": "put",\n' 653 ' "data": {"name": "production"}}' 654 ), 655 ), 656 MethodHelp( 657 "delete", "Unregister a profile (dir left on disk).", 658 example_request=( 659 '{"type": "configProfile", "method": "delete",\n' 660 ' "data": {"id": "production.abcd1234"}}' 661 ), 662 ), 663 ], 664 source="jmri_server.services.config_profile.ConfigProfileService", 665 rewrite_extension=True, 666 ), 667 "beanTransfer": TopicHelp( 668 topic="beanTransfer", 669 summary=( 670 "Copy beans (turnouts / sensors / lights) between " 671 "profiles. Preview shows what would be added; apply " 672 "commits the copy. When the target profile is the active " 673 "one, live managers refresh so the change is visible " 674 "immediately without a profile reload." 675 ), 676 methods=[ 677 MethodHelp("list", "Every profile available as source/target."), 678 MethodHelp( 679 "post", 680 "Preview or apply a transfer. ``action=preview`` returns " 681 "the diff; ``action=apply`` commits it.", 682 example_request=( 683 '{"type": "beanTransfer", "method": "post", "data":\n' 684 ' {"action": "preview", "source": "dev.abcd",\n' 685 ' "target": "prod.efgh", "kinds": ["turnout"]}}' 686 ), 687 ), 688 ], 689 source="jmri_server.services.bean_transfer.BeanTransferService", 690 rewrite_extension=True, 691 ), 692 "scripts": TopicHelp( 693 topic="scripts", 694 summary=( 695 "Python scripts loaded from ``<profile>/python/`` and " 696 "``<settings>/python/``. List, save, start, stop, delete. " 697 "Shared scripts (settings-root) are operator-managed and " 698 "can't be deleted through the API." 699 ), 700 methods=[ 701 MethodHelp( 702 "list", "Running scripts, or with data={\"library\": true}, " 703 "the whole on-disk library.", 704 example_request='{"type": "scripts", "method": "list"}', 705 ), 706 MethodHelp( 707 "get", "One script's status by name.", 708 example_request=( 709 '{"type": "scripts", "method": "get",\n' 710 ' "data": {"name": "morning_setup"}}' 711 ), 712 ), 713 MethodHelp( 714 "put", "Start a script from inline source. " 715 "``save=true`` also writes it to the profile's " 716 "``python/`` dir so it autoloads next boot.", 717 example_request=( 718 '{"type": "scripts", "method": "put", "data":\n' 719 ' {"name": "hello", "source": "print(\\"hi\\")",\n' 720 ' "save": true}}' 721 ), 722 ), 723 MethodHelp( 724 "post", 725 "action=stop → stop a running script; " 726 "action=delete → also remove the on-disk file.", 727 ), 728 ], 729 source="jmri_server.services.scripts.ScriptsService", 730 rewrite_extension=True, 731 ), 732} 733 734 735def get(topic: str) -> TopicHelp | None: 736 """Return the help entry for *topic*, or None if none is registered. 737 738 The web layer falls back to a "no help authored yet" panel when 739 this returns None — the topic still renders as a badge, just 740 without content. That way a new service can land without the 741 help wire-up blocking it. 742 """ 743 return TOPIC_HELP.get(topic)
28@dataclass(frozen=True, slots=True) 29class MethodHelp: 30 """One method (``list`` / ``get`` / ``put`` / ``post`` / ``delete``).""" 31 32 name: str 33 description: str 34 example_request: str | None = None 35 example_response: str | None = None
One method (list / get / put / post / delete).
38@dataclass(frozen=True, slots=True) 39class TopicHelp: 40 """Everything the accordion needs to render one topic's help panel.""" 41 42 topic: str 43 summary: str 44 methods: list[MethodHelp] = field(default_factory=list) 45 # Short "read the code" pointer — module.ClassName, no leading dots. 46 source: str = "" 47 # Any topic that isn't in JMRI Java's original API gets ``True`` so 48 # the UI can badge it as rewrite-specific. Keeps client-side JMRI 49 # tooling from expecting it against a legacy server. 50 rewrite_extension: bool = False
Everything the accordion needs to render one topic's help panel.
736def get(topic: str) -> TopicHelp | None: 737 """Return the help entry for *topic*, or None if none is registered. 738 739 The web layer falls back to a "no help authored yet" panel when 740 this returns None — the topic still renders as a badge, just 741 without content. That way a new service can land without the 742 help wire-up blocking it. 743 """ 744 return TOPIC_HELP.get(topic)
Return the help entry for topic, or None if none is registered.
The web layer falls back to a "no help authored yet" panel when this returns None — the topic still renders as a badge, just without content. That way a new service can land without the help wire-up blocking it.