{"id":49270168,"url":"https://github.com/jamesccupps/p2scanner","last_synced_at":"2026-05-02T01:01:14.895Z","repository":{"id":353654488,"uuid":"1219214821","full_name":"jamesccupps/P2Scanner","owner":"jamesccupps","description":"P2 Protocol HVAC Scanner for Building Automation - CLI and GUI","archived":false,"fork":false,"pushed_at":"2026-04-25T11:48:35.000Z","size":880,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-25T13:26:43.291Z","etag":null,"topics":["apogee","bas","building-automation","desigo","hvac","insight","open-source","p2","pxc","python","scanner","siemens"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamesccupps.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-04-23T16:41:34.000Z","updated_at":"2026-04-25T11:46:05.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jamesccupps/P2Scanner","commit_stats":null,"previous_names":["jamesccupps/p2scanner"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/jamesccupps/P2Scanner","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesccupps%2FP2Scanner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesccupps%2FP2Scanner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesccupps%2FP2Scanner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesccupps%2FP2Scanner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamesccupps","download_url":"https://codeload.github.com/jamesccupps/P2Scanner/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesccupps%2FP2Scanner/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32518744,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"online","status_checked_at":"2026-05-01T02:00:05.856Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["apogee","bas","building-automation","desigo","hvac","insight","open-source","p2","pxc","python","scanner","siemens"],"created_at":"2026-04-25T13:15:19.218Z","updated_at":"2026-05-02T01:01:14.883Z","avatar_url":"https://github.com/jamesccupps.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# P2 Scanner\n\nScanner and point-read library for field controllers over the P2 protocol. Runs as a CLI, a Tk GUI, or an importable Python library.\n\nPure Python, zero external dependencies, read-only by design.\n\n\u003e **Looking to expose APOGEE panels as BACnet/IP?** See **[P2_BACnet_Bridge](https://github.com/jamesccupps/P2_BACnet_Bridge)** — a read-only bridge built on this scanner that surfaces every P2 point as a BACnet object, so any modern supervisor (Desigo CC, Niagara, EBI, Tridium-anything) can read APOGEE PXCs as native BACnet devices.\n\n---\n\n## Features\n\n**Discovery**\n- Cold-site onboarding via BACnet recon + tiered dictionary probe\n- Passive multicast listener (no probes sent)\n- Passive TCP 5034 listener for live COV / virtual-point / routing events\n- Port-scan discovery on TCP 5033\n- Automatic firmware-dialect detection (legacy vs modern panels)\n\n**Point operations**\n- Read by point name or slot number\n- Full-panel walk — every point on a controller, including panel-internal variables\n- FLN device enumeration\n- PPCL program source dump\n- 797 TEC application definitions bundled — state labels, units, and types\n- Comm-status detection — distinguishes live readings from stale cached data on FLN-faulted devices, matching Desigo's `#COM` indicator\n- Structured error decoding (not silent `None`s)\n\n**I/O**\n- Table, CSV, and JSON output\n- Sniff or decode packet captures for automatic BLN-name learning\n- Per-host dialect cache avoids re-probing on reconnect\n\n---\n\n## Files\n\n| File | Description |\n|------|-------------|\n| `p2_scanner.py` | CLI / library. Python 3.6+. |\n| `p2_gui.py`, `p2_gui_widgets.py`, `p2_gui_workers.py` | Tk GUI front-end |\n| `launch_gui_windows.bat` | Windows launcher — runs the GUI under `pythonw` so no console window flashes |\n| `analyze_pcap.py` | Standalone pcap inventory tool — opcode counts, error codes, frame-size distribution, sample bodies for unknowns. Useful for protocol exploration. Requires `tshark`. |\n| `tecpoints.json` | Point definitions for 797 TEC applications |\n| `site.json` | Site-configuration template |\n| `PROTOCOL.md` | Wire-level protocol reference |\n| `p2.lua` | Wireshark dissector — decodes P2 frames in Wireshark's UI |\n\n---\n\n## Requirements\n\n- Python 3.6 or later\n- Network access to PXC controllers on TCP/5033\n- `tshark` on PATH (optional — only needed for live-sniff discovery)\n- L2 access to the BAS subnet (only needed for cold discovery)\n\nNo pip packages required for the scanner itself.\n\n---\n\n## Quick start\n\n### Cold onboarding (no prior knowledge)\n\n```bash\npython p2_scanner.py --cold-discover --range 192.168.1.0/24 --save site.json\n```\n\n### Known BLN name\n\n```bash\npython p2_scanner.py --discover --range 192.168.1.0/24 --network MYBLN --save site.json\n```\n\n### Read a point\n\n```bash\npython p2_scanner.py --config site.json -n NODE1 -d DEVICE1 -p \"ROOM TEMP\"\n```\n\n### Launch the GUI\n\n```bash\n# POSIX (Linux / macOS)\npython p2_gui.py\n\n# Windows — double-click launch_gui_windows.bat, or:\nlaunch_gui_windows.bat\n```\n\nThe Windows .bat runs the GUI under `pythonw` so there's no console flash. If\nit reports \"p2_gui.py not found\" with the file actually present, the .bat\nhas been corrupted to LF-only line endings — open it in Notepad and re-save\nto restore CRLF.\n\n---\n\n## Command reference\n\nCommon commands shown below. Full flag list: `python p2_scanner.py --help`.\n\n### Discovery\n\n```bash\n# Default cold discovery (BACnet recon + dictionary probe)\npython p2_scanner.py --cold-discover --range 192.168.1.0/24 --save site.json\n\n# Conservative (2-second delay between probes — safe during production hours)\npython p2_scanner.py --cold-discover --range 192.168.1.0/24 --cold-delay 2\n\n# Passive multicast listen (no probes sent)\npython p2_scanner.py --listen 60 --save site.json\n\n# Learn BLN name from a packet capture\npython p2_scanner.py --pcap capture.pcapng --save site.json\n```\n\n### Reading\n\n```bash\n# All points on a device\npython p2_scanner.py --config site.json -n NODE1 -d DEVICE1\n\n# Specific point by name\npython p2_scanner.py --config site.json -n NODE1 -d DEVICE1 -p \"ROOM TEMP\"\n\n# Specific point by slot number (Desigo-style)\npython p2_scanner.py --config site.json -n NODE1 -d DEVICE1 -p 4\n\n# Full-panel walk — includes panel-internal / PPCL variables\npython p2_scanner.py --config site.json -n NODE1 --walk-points\n\n# Dump PPCL program source\npython p2_scanner.py --config site.json -n NODE1 --dump-programs\n\n# Panel firmware info\npython p2_scanner.py --config site.json -n NODE1 --info\n```\n\n### Output formats\n\n```bash\npython p2_scanner.py --config site.json -n NODE1 -d DEVICE1 -f csv \u003e out.csv\npython p2_scanner.py --config site.json -n NODE1 -d DEVICE1 -f json \u003e out.json\n```\n\n### IP range formats\n\n`10.0.0.50` · `10.0.0.0/24` · `10.0.0.80-200` · `10.0.0` (shorthand for `.1-254`) · comma-separated ranges.\n\n---\n\n## Output\n\nEach point read produces a result dictionary:\n\n| Field | Description |\n|-------|-------------|\n| `point_name` | Point name |\n| `point_slot` | Subpoint slot number (1–99) |\n| `value` | Numeric value (live, or stale-cached if `comm_status='comm_fault'`) |\n| `value_text` | Label for digital points (`\"NIGHT\"`, `\"COOL\"`, etc.) |\n| `units` | Engineering units |\n| `point_type` | `analog_ro` / `analog_rw` / `digital_ro` / `digital_rw` |\n| `comm_status` | `online` (live FLN read) or `comm_fault` (panel returned cached data because the device is FLN-faulted — Desigo's `#COM` indicator) |\n| `comm_error_code` | Panel-reported error byte; `0x06` is the typical comm-fault code |\n| `point_info` | Full metadata (state labels, units, scaling) |\n\nVerify-online operations on a list of devices additionally produce per-device fields:\n\n| Field | Description |\n|-------|-------------|\n| `status` | `online` / `offline` — authoritative classification |\n| `comm_status` | `online` / `comm_fault` — only when the panel reported one |\n| `room_temp` | Live ROOM TEMP value, present for online devices |\n| `stale_temp` | Cached ROOM TEMP value, present for `comm_fault` devices |\n| `application` | App number; populated for online AND `#COM` devices (panel-cached for the latter) |\n| `application_cached` | `True` only when APPLICATION was read from a comm-faulted device's cache |\n\nTable output shows Desigo-style `(slot) NAME` for each point. Digital points render as `LABEL (raw)`. Comm-faulted points get a `#COM` suffix.\n\n### Exit codes\n\n| Code | Meaning |\n|------|---------|\n| `0` | Success |\n| `1` | Scan ran but returned nothing (device offline, no readable points) |\n| `2` | Input rejected before any network I/O |\n\n---\n\n## Config file\n\n```json\n{\n  \"p2_network\": \"MYBLN\",\n  \"p2_site\": \"SITE\",\n  \"scanner_name\": \"P2SCAN|5034\",\n  \"known_nodes\": {\n    \"NODE1\": \"192.168.1.10\",\n    \"NODE2\": \"192.168.1.11\"\n  }\n}\n```\n\n- `p2_network` — BLN network name. Required. PXCs reject messages with the wrong name.\n- `scanner_name` — Scanner identity. Some sites require a specific format; if handshakes fail, try `\u003cSITE\u003eDCC-SVR|5034`.\n- `known_nodes` — Node name → IP map.\n\nAuto-updated by `--save`. Extra keys are preserved.\n\n---\n\n## Programmatic use\n\n```python\nimport p2_scanner as p2\n\np2.P2_NETWORK = \"MYBLN\"\np2.SCANNER_NAME = \"P2SCAN|5034\"\n\nconn = p2.P2Connection(\"192.168.1.50\")\nif conn.connect(\"NODE1\"):\n    result = conn.read_point(\"DEVICE1\", \"ROOM TEMP\", \"NODE1\")\n    print(result['value'], result.get('units'))\n    conn.close()\n```\n\nCore API:\n\n| Function | Purpose |\n|----------|---------|\n| `P2Connection(host)` | Open TCP session |\n| `conn.connect(node_name)` | Handshake |\n| `conn.read_point(device, point, node)` | Read a single point |\n| `conn.enumerate_fln(node)` | List FLN devices |\n| `conn.read_firmware(node)` | Panel model / firmware |\n| `scan_device(host, device, ...)` | High-level device scan with rendering |\n| `get_point_info(app, point_name)` | Metadata lookup |\n| `render_point_value(value, info)` | Value → display string |\n\nThe library is synchronous. For a GUI, run reads on a worker thread (see `p2_gui_workers.py` for reference).\n\n---\n\n## Troubleshooting\n\n| Issue | Fix |\n|-------|-----|\n| \"P2 network name required\" | Provide `--network` or `--config` |\n| \"Handshake failed\" | Verify BLN / scanner / node names |\n| Handshake takes 2+ seconds | First connect to a modern-firmware panel; the dialect auto-probe is doing its thing. Cached on subsequent connects. |\n| Listener hears nothing | Site has multicast disabled — use `--sniff` or `--cold-discover` |\n| \"Max peer sessions reached\" | Try when other supervisor connections are idle |\n| All device points show `#COM` | FLN bus disconnected, or the device-side controller is faulted. Cross-check against Desigo CC's System Manager — it'll show the same `#COM` flag if the fault is real. |\n| Digital point shows raw float instead of label | Read APPLICATION first — use `-n NODE -d DEVICE` so the scanner can auto-detect the app |\n| Windows `launch_gui_windows.bat` says \"p2_gui.py not found\" but the file IS there | The .bat got LF-only line endings somehow (web upload, Git autocrlf, copy through a Linux box). Open it in Notepad and re-save — Notepad always writes CRLF, which fixes it. |\n| GUI Verify Online shows 0 devices but doesn't tell me if the PXC is up | Click Verify Online anyway — on a no-device node the GUI auto-falls back to a firmware probe, which flips the node row green/red without needing any FLN devices. Or click Firmware directly. |\n\n---\n\n## Safety\n\n- **Read-only.** The scanner never writes points, changes setpoints, or modifies controller state. Write-capable opcodes are documented in `PROTOCOL.md` but intentionally not exposed.\n- **PXCs have a limited number of peer sessions** (typically 8–16). Don't run parallel scanners against the same panel.\n- **Cold discovery sends dictionary probes.** Observed as non-disruptive, but use `--cold-delay 2` during production hours as a precaution.\n\n## Wireshark dissector\n\nA Lua dissector (`p2.lua`) is included for decoding P2 frames live in Wireshark — useful for protocol debugging, learning the wire format, or analyzing traffic from production sites.\n\n**Install:**\n\n```\n# Linux / macOS\ncp p2.lua ~/.local/lib/wireshark/plugins/\n\n# Windows\ncopy p2.lua %APPDATA%\\Wireshark\\plugins\\\n\n# Or find your plugin path via Wireshark: Help → About Wireshark → Folders → Personal Lua Plugins\n```\n\nRestart Wireshark. The dissector auto-attaches to TCP ports 5033 and 5034. Decoded fields appear under the **P2** tree in the packet details pane: opcode names, error codes, sequence numbers, message types, and direction byte. The decoder also handles 0x0240 / 0x0274 push frames, the routing table 0x4634, alarm pair 0x0508/0x0509, schedule writes 0x5020/0x5022, PPCL editor 0x4100-family, and the property-write fault `0x0E15`. Multicast presence beacons on UDP/10001 are decoded under `p2_beacon`.\n\nThe dissector is conservative — it doesn't try to decode every TLV inside operational payloads, just the framing, opcodes, and the most common request/response shapes. For protocol exploration of an unfamiliar capture, run `analyze_pcap.py` against the saved file — it inventories every opcode, error code, frame-size distribution, and message-type seen, and flags anything not in `KNOWN_OPCODES` so you can spot new opcodes or unfamiliar variants quickly.\n\n---\n\n## See also\n\n- **[PROTOCOL.md](PROTOCOL.md)** — wire-level protocol reference. Every opcode, every format variant, every edge case. Read this if you're debugging unusual responses, implementing your own client, or just curious how a BAS protocol works on the wire.\n\n## Related projects\n\n- **[P2_BACnet_Bridge](https://github.com/jamesccupps/P2_BACnet_Bridge)** — Read-only bridge that uses this scanner as a library to expose APOGEE PXC controllers as BACnet/IP devices. Lets Desigo CC, Niagara, EBI, and other modern supervisors read APOGEE panels natively. Tk configurator GUI, persistent point manifest, FLN per-point reads + bulk panel-internal enumerate.\n- **[HVAC-Network-Scanner](https://github.com/jamesccupps/HVAC-Network-Scanner)** — Multi-protocol BACnet scanner for commercial building automation networks. Complementary to this scanner — covers BACnet/IP discovery while P2 Scanner covers Siemens APOGEE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesccupps%2Fp2scanner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamesccupps%2Fp2scanner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesccupps%2Fp2scanner/lists"}