Skip to content

Repository files navigation

PPUC config-tool

The PPUC config-tool is a web application to configure your controllers. it is still WIP.

Installation

The PPUC config-tool is a web appication based on Drupal, written in PHP. So it needs a webserver to run.

To run a local instance, a docker image is available:

docker pull ghcr.io/ppuc/config-tool:latest
docker run -p 8080:80 -v config-tool-data:/var/www/web/config-tool-data ghcr.io/ppuc/config-tool:latest

Then open localhost:8080in a web browser and login as user ppuc using the password ppuc.

Creating a game from its manual

Setting up a machine by hand means creating every switch, coil and lamp one node at a time - about 150 of them for a 1990s game, each needing a number, a board and a pin. All of that is already written down in three tables in the operator manual: the switch matrix, the lamp matrix and the solenoid/flashlamp table.

Create Game from Manual (/game/wizard) takes those tables as a JSON document and builds the game, its I/O boards, switches, PWM devices and LED stripes. It shows the board allocation before creating anything.

The JSON is the input format on purpose, rather than the manual pages themselves. It is a contract: the pages can be transcribed by hand, extracted by an AI, or produced by another tool entirely, and what the wizard does with the result is the same either way.

Getting the JSON from the manual

The wizard has a collapsed "I have the manual, not the JSON" section holding a prompt to paste into ChatGPT, Gemini, Claude or whatever chat you use, along with scans of the manual pages. It names the pages to attach, gives the format and the rules, and states the numbers this platform's ROM expects, so the answer comes back in the form the wizard accepts.

Attach these three, which carry the numbers:

  • Switch Matrix
  • Lamp Matrix
  • Solenoid/Flashlamp Table

and these three if the manual has them, which add positions:

  • Switch Locations
  • Lamp Locations
  • Solenoid/Flashlamp Locations

Nothing is sent anywhere by config-tool itself. It reads JSON, and it does not care where the JSON came from.

Check the result. Reading a scanned table is exactly the sort of thing these tools get subtly wrong, and a misread number is a device wired to the wrong hardware. The wizard refuses anything it cannot make sense of - unknown keys, duplicate numbers, a direct switch on a number the ROM does not read - but it cannot tell a plausible wrong number from a right one.

Before the first run

The wizard needs the field definitions and the Opto_16 board type to be in the database. On an existing site:

drush deploy   # database updates and configuration, including the new fields
drush dcdi --folder=sites/default/files/default_content --preserve-ids --yes

Under DDEV, prefix both with ddev. The Docker image runs them on start, so a container restart is enough there.

If the wizard reports that no Opto_16 board type exists, drush deploy is what adds it - the default content import cannot, on a site whose taxonomy already uses the term id in the file.

Numbers come from the manual

Switches, coils and lamps use the numbers printed in the manual - a matrix switch is column x 10 + row, a coil is its solenoid number, a lamp is its lamp matrix number. The wizard does not renumber anything.

Switches outside the matrix are the exception, because the ROM only reads them at numbers the platform defines. For WPC:

Switch Number
Coin chutes 1-4 1, 2, 3, 4
Service credit / escape 5
Volume down, volume up 6, 7
Begin test 8
Flipper buttons (Fliptronic) 112 lower right, 114 lower left, 116 upper right, 118 upper left
Flipper EOS 200 upwards - PinMAME does not read these

A platform the wizard has no table for is refused rather than given WPC's numbers: a switch on a number nothing polls behaves exactly like a broken one.

Format

{
  "game":     { "title": "Dirty Harry", "platform": "WPC", "rom": "dh_lx2" },
  "switches": [
    { "number": 11, "description": "Gun Handle Trigger" },
    { "number": 31, "description": "Trough Jam", "opto": true },
    { "number": 24, "description": "Plumb Bob Tilt", "location": "cabinet" },
    { "number": 5,  "description": "Service Credit/Escape", "direct": true }
  ],
  "coils":    [
    { "number": 1, "description": "Ball Release", "class": "highPower" },
    { "number": 7, "description": "Knocker", "class": "highPower", "location": "backbox" },
    { "number": 9, "description": "Left Sling", "class": "lowPower", "fastFlipSwitch": 61 },
    { "number": 20, "description": "Gun Motor", "class": "lowPower", "type": "motor" }
  ],
  "flippers": [
    { "name": "Lower Right", "position": "lowerRight", "powerCoil": 29, "holdCoil": 30 }
  ],
  "flashers": [ { "number": 17, "description": "Headquarters" } ],
  "lamps":    [ { "number": 11, "description": "Left Rollover" } ],
  "gi":       [ { "number": 1,  "description": "Right String" } ]
}
Key Meaning
class The manual's SOLENOID TYPE column: highPower, lowPower or genPurpose. It sets the drive power, so it is required and never guessed.
type PWM device type: coil (default), lamp, motor, shaker. Not flasher - a flasher is an LED.
opto Puts the switch on an Opto_16 board.
direct A D-column switch. Its number must be one the platform defines.
location playfield (default), cabinet or backbox. Backbox devices share the cabinet board. Valid on switches, coils and LEDs.
fastFlipSwitch The switch this coil reacts to locally. The wizard puts both on one board.
holdWinding This output is the hold half of a pair driven as two outputs. Flippers are declared in flippers instead; use this for anything else, such as a trap door driven as "high" and "hold".
position Optional. Where the device is, as {"x": 0.5, "y": 0.2}. See below.
endSwitches The switches at an assembly's end positions, as [76, 77]. They become stop switches and go on the coil's board. See motors below.
position Which flipper this is, which selects the button number.

Rows the manual marks "Not Used" are simply left out. So are switches called "Always Closed" if you do leave them in: the original CPU used one to prove it was reading the matrix, nothing in PPUC reads it, and an input pin spent on one is a pin wasted. The wizard says which entries it left out.

Motors

A gun or cannon assembly is turned by a motor rather than thrown by a coil, and that changes two things.

Power. Such a motor is usually a low-voltage one - 12 V is typical - on a machine whose driver rail is 48 V. "type": "motor" therefore drives it at 64 of 255, about a quarter, whatever solenoid type the table gives its driver. The type describes the transistor; this is about what is on the end of it.

End positions. A motor normally has two switches marking the ends of its travel, and those should cut it the instant it arrives, or the assembly drives into its own stop. List them:

{ "number": 20, "description": "Gun Motor", "class": "lowPower",
  "type": "motor", "endSwitches": [76, 77] }

These become the output's stop switches: the board cuts the motor the moment one closes, without waiting for the host. That only works from the board that owns the output, so the wizard puts them there.

The maximum pulse time stays as the backstop for a switch that never closes, and its default is shorter than a traverse - time the travel and set it, or the assembly stops part way. The wizard says so in its summary.

Do not put an end switch in fastFlipSwitch. The polarity is the other way round: a fast-flip switch runs the coil while it is closed, so that would drive the motor whenever it had already arrived. The flipper column beside the switch matrix is a generic Fliptronic template - on Dirty Harry it lists an upper left flipper the game does not have - so the wizard ignores it and takes the flippers from the solenoid table instead.

What it decides for you

Coil power comes from the solenoid type: High Power 255, Low Power and Gen. Purpose 128. Every coil gets a maximum pulse time, so a wizard-built game loads without unprotected-coil warnings. The exception is a flipper hold winding, which is wound to sit energised and is marked holdWinding instead - bounding one would drop the flipper mid-game.

Boards are allocated automatically under three rules:

  1. A coil with a fast-flip switch goes on the same board as that switch. The board only reacts without waiting for the host when it owns both, which is why flipper buttons land on a playfield board even though they are cabinet hardware.
  2. Everything else stays where it is wired: cabinet devices (and backbox ones) on the cabinet board, the rest on boards under the playfield. Coils that fire in bursts are spread across boards rather than packed onto one: a ball rattling between three jet bumpers, or bouncing off both slingshots, drains a driver board's capacitor faster than it recharges.
  3. One LED stripe per board, on the LED connector. An Opto_16 has that connector too, so a string can sit on a board that is already there for its inputs.

LED strings

Under the playfield there is one string per role — lamps, flashers, GI — since those are separate runs. The cabinet gets one string carrying all of its roles together, because a role belongs to each LED rather than to the string, and splitting a handful of cabinet LEDs across three strings would cost three boards.

The cabinet string is always planned, even when the input mentions nothing for it. A machine's matrices describe the playfield; the cabinet has illumination they never mention:

  • Button lamps that are in the lamp matrix but sit on the cabinet — start and buy-in on Dirty Harry — marked with "location": "cabinet".
  • On WPC, a GI string often drives the backbox: "location": "backbox".
  • Lights that are simply on with the machine, such as the coin return buttons in the coin door. These are in no matrix, so number them from 100 as the LED number field describes, and mark them "location": "cabinet".

Space under a playfield is the real constraint, so the wizard adds as few boards as the device counts allow and spreads the load across them rather than filling each to the brim. Where a handful of coils would otherwise force one more board, it uses outputs already going spare on the cabinet board instead - a few wires from the cabinet to the playfield cost less than a board that has nowhere to go. It only does this for a coil with no fast-flip switch, never for a flipper winding, and it says which coils are affected so the wiring is not a surprise.

LED string positions are filled in matrix order, which is a starting point rather than a claim about how the string runs around the playfield. Reorder them to match the wiring.

Positions, if the manual has them

Some manuals include location pages - a playfield outline with each item number called out on it, alongside the switch, lamp and solenoid tables. Plenty do not, so every position is optional and the wizard works exactly as before without them. Mixing is fine too: position what the diagram shows and leave the rest.

{ "number": 63, "description": "Left Jet Bumper", "position": { "x": 0.4, "y": 0.78 } }

x runs left to right and y from the flipper end upwards, both as a fraction of the playfield from 0 to 1. Fractions rather than measurements, because a diagram is the source and its scale is arbitrary. A coordinate outside 0 to 1 is refused - that is a transcription error, not a device hanging off the playfield.

Positions buy two things:

  • Shorter wire runs. A device goes to the board nearest the ones already placed around it, so a board tends to serve its own region of the playfield.
  • A usable LED string order. Without positions, a string is ordered by lamp number, which says nothing about where the lamps are. With them, the wizard walks nearest-to-nearest from the bottom left, which is roughly the path somebody laying the string would take. Still a guess at the wiring, but one worth correcting rather than starting from. A string where only some LEDs have positions keeps the listed order - half a path and half a matrix is worse than either - and the wizard says which strings got which.

Proximity never overrides the capacitor rule. Three jet bumpers are inches apart, so nearest-board would put all three together, which is exactly the arrangement to avoid. Coils with a fast-flip switch are spread across boards whatever they are near.

Flipper timing on WPC Fliptronic

A Fliptronic flipper has two windings driven as two separate outputs, both switched on by the flipper button. On the original machine the power winding is ended by whichever comes first:

  • The EOS contact closing - the normal path. The finger reaches its stop after roughly 15 to 30 ms of travel, the CPU drops the power winding the moment the contact closes, and the hold winding keeps the finger up.
  • A fixed 30 to 40 ms timeout - the safety net, for an EOS contact that is broken, misadjusted or unplugged. Without it the power winding burns. The finger still stays up on the hold winding afterwards, but with no working EOS it loses strength: a heavy ball can push the finger down, and the hold winding alone cannot raise it again until the button is released and pressed.

PPUC now does both. The wizard creates the EOS as a switch, makes it a stop switch of the power winding, and puts it on the same board so the board acts on it itself. The maximum pulse time is set to 40 ms as the backstop for a contact that never closes - it clears the slowest stroke, so no flip is cut short, and stays inside the range WPC itself considered safe for the winding. Below 30 ms it would cut the flip off before the finger arrives.

Nothing stops the hold winding: that is what keeps the finger up once it is there.

One behaviour worth knowing, because it is the reason the EOS is read rather than just recorded. If a ball heavy enough pushes the finger back down, the EOS opens; the hold winding alone cannot lift it, so the board fires the power winding again while the button is still held. Without that the flipper stays down until the player releases and presses again.

Game YAML export

The generated game YAML includes optional switch and coil roles used by the runtime safety features:

  • Switch nodes and switch matrix switch nodes can be marked as Button. When checked, the exported switch entry contains button: true.
  • PWM device nodes can be marked as Ball search. When checked, the exported PWM output entry contains ballSearch: true.
  • PWM device nodes can be marked as Dual-wound coil, exported as dualWinding: true, for a coil whose EOS contact transfers to its own hold winding mechanically. An End-of-stroke switch can be referenced alongside it and is exported as eosSwitch: <number>.
  • PWM device nodes can be marked as Hold winding, exported as holdWinding: true, for the hold half of a flipper whose windings are driven as two separate outputs, as on WPC Fliptronic. Both flags tell libppuc the coil bounds itself, so it is not reported as having no thermal protection.

Lua rules

Games can store Lua rules and Blockly workspace data. The game export archive includes the generated YAML, rules.lua, and rules.blockly.json when rules data exists, so another config-tool instance can import and continue the project.

The rules editor always supports direct Lua editing. Blockly integration is initialized when Blockly assets are available on the page; otherwise the editor falls back to the Lua textarea.

Development

Linux and macOS

Install hombrew and DDEV.

Just follow the instructions for your operating system. But even if not documented well, even for Linux we recommended to install DDEV via brew! The PPUC ecosystem will require homebrew anyway. And it is always better to use a package manager.

For macOS these are the essential steps:

brew install docker
brew install orbstack
brew install ddev/ddev/ddev
mkcert -install

For Linux install docker according to https://ddev.readthedocs.io/en/stable/users/install/docker-installation/#linux Afterwards install DDEV:

brew install ddev/ddev/ddev
mkcert -install

Now clone this project somewhere in your home directory. It is recommended to create a PPUC directory first where you can also clone other PPUC components.

mkdir PPUC
cd PPUC
git clone https://github.com/PPUC/config-tool.git
cd config-tool
ddev start
ddev drush site:install ppuc --site-name="Pinball Power-Up Controller" --account-name=admin --account-pass=admin --existing-config -y
ddev drush dcdi --folder=sites/default/files/default_content --preserve-ids -y

Now you can open https://ppuc-config-tool.ddev.site/ in your browser and login using ppuc as username and ppuc as password.

When you restart your computer you need to start ddev again:

cd PPUC/config-tool
ddev start

Update the PPUC config-tool

Once ddev has been started you can also update to the latest version of the config-tool. It is recommended to export your games before performing the update.

Within PPUC/config-tool run

ddev snapshot
git pull
ddev drush deploy
ddev drush dcdi --folder=sites/default/files/default_content --preserve-ids --force-override -y

TODO: import/update ppuc profile default content after drush deploy

About

Web-based configuration and management tool for Pinball Power-Up Controller

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages