{"id":32519560,"url":"https://github.com/toddheasley/p-func","last_synced_at":"2026-04-15T07:31:40.861Z","repository":{"id":297882719,"uuid":"921697820","full_name":"toddheasley/p-func","owner":"toddheasley","description":"LEGO® Powered Up","archived":false,"fork":false,"pushed_at":"2025-09-16T22:48:01.000Z","size":4870,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-09-17T00:04:42.515Z","etag":null,"topics":["lego","lego-wireless-protocol","powered-up","poweredup","swift","swiftui"],"latest_commit_sha":null,"homepage":"","language":"Swift","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/toddheasley.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2025-01-24T12:52:18.000Z","updated_at":"2025-09-16T22:48:04.000Z","dependencies_parsed_at":null,"dependency_job_id":"158463b5-e68e-4589-9024-b6acabf7d275","html_url":"https://github.com/toddheasley/p-func","commit_stats":null,"previous_names":["toddheasley/p-func"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/toddheasley/p-func","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toddheasley%2Fp-func","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toddheasley%2Fp-func/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toddheasley%2Fp-func/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toddheasley%2Fp-func/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/toddheasley","download_url":"https://codeload.github.com/toddheasley/p-func/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toddheasley%2Fp-func/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281386564,"owners_count":26492014,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-28T02:00:06.022Z","response_time":60,"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":["lego","lego-wireless-protocol","powered-up","poweredup","swift","swiftui"],"created_at":"2025-10-28T04:37:02.789Z","updated_at":"2026-04-15T07:31:40.843Z","avatar_url":"https://github.com/toddheasley.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `PFunc`\n\n__Control Powered Up motors and lights from an `@Observable` Swift interface__\n\n`PFunc` talks to [LEGO® Powered Up](https://www.lego.com/themes/powered-up) hubs over Bluetooth Low Energy (BLE). [Core Bluetooth](https://developer.apple.com/documentation/corebluetooth) does the heavy lifting, managing connections and writing instructions to the hubs.\n\n`PFunc` implements _just_ enough of the [LEGO Wireless Protocol](https://lego.github.io/lego-ble-wireless-protocol-docs) to replace the [88010 Remote Control](https://www.lego.com/product/remote-control-88010) and drive the current generation of Powered Up attachments from the 2- and 4-port consumer hubs.\n\n### Supported Hubs\n\n| [88012\u0026nbsp;Hub](https://www.lego.com/product/technic-hub-88012) | [88009\u0026nbsp;Hub](https://www.lego.com/product/hub-88009) |\n| --- | --- |\n| ![](docs/technic-hub-88012.png) | ![](docs/hub-88009.png) |\n\n### Supported Attachments\n\n| [88011\u0026nbsp;Train\u0026nbsp;Motor](https://www.lego.com/product/train-motor-88011) | [88013\u0026nbsp;Large\u0026nbsp;Motor](https://www.lego.com/product/technic-large-motor-88013) |\n| --- | --- |\n| ![](docs/train-motor-88011.png) | ![](docs/technic-large-motor-88013.png) |\n\n| [45303\u0026nbsp;Motor](https://www.lego.com/product/simple-medium-linear-motor-45303) | [88005\u0026nbsp;Light](https://www.lego.com/product/light-88005) |\n| --- | --- |\n| ![](docs/simple-medium-linear-motor-45303.png) | ![](docs/light-88005.png) |\n\n### Supported Platforms\n\nWritten in [Swift](https://developer.apple.com/documentation/swift) 6.2 for Tahoe/26 versions of [macOS](https://developer.apple.com/macos), [iOS](https://developer.apple.com/ios)/[iPadOS](https://developer.apple.com/ipad) and [visionOS.](https://developer.apple.com/visionos) Build with [Xcode](https://developer.apple.com/xcode) 26 or newer.\n\n## Instructions\n\nApps using `PFunc` are using Core Bluetooth. Your app will crash if its `Info.plist` doesn't include `NSBluetoothAlwaysUsageDescription` [privacy description.](https://developer.apple.com/documentation/uikit/protecting_the_user_s_privacy/requesting_access_to_protected_resources)\n\nEnable Bluetooth in app entitlements:\n\n| macOS | iOS, visionOS |\n| --- | --- |\n| ![](docs/entitlements-app-sandbox.png) | ![](docs/entitlements-background-modes.png) |\n\n[Add `p-func` package](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app) to your Xcode project, then add `PFunc` library to the app target(s).\n\n### Connect Hubs\n\nAdd `@Observable PFunc` object to the SwiftUI app environment; connect nearby hubs when Bluetooth is enabled:\n\n```swift\nimport SwiftUI\nimport PFunc\n\n@main\nstruct App: SwiftUI.App {\n    @State private var pFunc: PFunc = PFunc()\n    \n    // MARK: App\n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n                .environment(pFunc)\n                .onChange(of: pFunc.state) {\n                    if pFunc.state == .poweredOn {\n                        pFunc.connect()\n                    }\n                }\n        }\n    }\n}\n```\n\nAll hub property updates are published:\n\n* Advertising name (14-character ASCII string)\n* Battery voltage (0-100%)\n* Bluetooth signal strength (poor/fair/good w/ relative dbm) and connection status (`CBPeripheralState`)\n* Built-in RGB light color (10 named presets or custom RGB 0-255)\n* Ports and attached devices (automatically detect/init known `Device` types)\n\n### Control Attached Devices\n\nDetect when a device is attached to a port and operate functions:\n\n```swift\nimport PFunc\nimport SwiftUI\n\nstruct RemoteControl: View {\n    init(hub id: UUID) {\n        self.id = id\n    }\n    \n    @Environment(PFunc.self) private var pFunc: PFunc\n    private let id: UUID\n    \n    private var device: Device? { pFunc.hub(id)?.device(at: .external(.a)) }\n    \n    // MARK: View\n    var body: some View {\n        Button(action: {\n            if let light: LEDLight = device as? LEDLight {\n                light.intensity = light.intensity == .off ? .percent(50) : .off\n            } else if let motor: Motor = device as? Motor {\n                motor.power = motor.power == .float ? .forward(50) : .float)\n            }\n        }) {\n            Text(\"Toggle Device Function\")\n        }\n        .disabled(device == nil)\n    }\n}\n```\n\n### Change Hub Name and RGB Light\n\nBoth advertising name and RGB light color are settable and resettable:\n\n```swift\npFunc.hub(id)?.resetName(\"New Hub Name\")\npFunc.hub(id)?.resetName() // Reset name back to firmware default\n```\n\n```swift\npFunc.hub(id)?.rgbLightColor = .red\n```\n\nName changes are persisted on the hub across connections, until changed or reset. RGB light color always starts at hub default on connection. (To remember which hubs were which color last time connected, your app can depend on the Core Bluetooth peripheral `CBUUID` being the same across connections.)\n\n## Thanks\n\nI had a little help from the Internet:\n\n* [Notes on LEGO wireless BLE protocol](https://virantha.github.io/bricknil/lego_api/lego.html)\n* [Powered UP - Community Docs (the missing device docs ...)](https://github.com/sharpbrick/docs)\n* [SmartBotKit LWP](https://github.com/smartbotkit/lwp)\n\n`PFunc` is not affiliated with the LEGO Group. LEGO® is a trademark of the LEGO Group, which does not sponsor, authorize or endorse this software.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoddheasley%2Fp-func","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftoddheasley%2Fp-func","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoddheasley%2Fp-func/lists"}