A complete demo showing how to:
- Control TP-Link/Kasa smart bulbs and plugs via an Azure Functions app (.NET 8 isolated)
- Share typed models between services and plugins
- Call the Functions from Dataverse plugins (net462) with managed identity, environment variables, and HTTP helpers
- Optionally run a local Node/TypeScript REST service that talks to devices on your LAN
This repo contains three main projects:
- Functions:
Functions/NextGenDemo.Functions.csproj - Plugins (Dataverse):
Plugins/NextGenDemo.Plugins.csproj - Shared models:
Shared/NextGenDemo.Shared.csproj - Optional local device API:
KasaLightAPI/(Node 18+)
Prerequisites
- Windows with PowerShell (recommended), or any OS with a terminal
- .NET 8 SDK
- Node.js 18+ (only if you plan to run the local Kasa API)
- Azure Functions Core Tools (optional if you prefer
func start)
Clone and restore
git clone https://github.com/mikefactorial/NextGenPluginDemo.git
cd NextGenPluginDemo
dotnet restoreThe Functions app calls a REST API that talks to your bulbs/plugs on the LAN. You can run the included Node service or point to an existing endpoint (e.g., ngrok).
- Configure KasaLightAPI
- Copy
KasaLightAPI/.env.exampletoKasaLightAPI/.env - Set
API_KEYto a secret value (match this in the Functions settings)
- Install and start
cd KasaLightAPI
npm i
npm run dev
# Service starts on http://localhost:3000Key endpoints (see KasaLightAPI/README.md for full list):
- GET
/api/health - GET
/api/devices - GET
/api/test/:ip/on|off|red|green|blue|yellow|purple|white - POST
/api/bulb/:ip/hexbody:{ hex: "#FF0000" } - POST
/api/bulb/:ip/colorbody:{ hue, saturation, brightness? } - POST
/api/bulb/:ip/temperaturebody:{ kelvin } - POST
/api/bulb/:ip/aliasbody:{ alias }
Configuration lives in Functions/local.settings.json and is read as flat keys in Functions/Program.cs:
BulbApiBaseUrl— e.g.,http://localhost:3000/api(for the local Node API) or your ngrok URLBulbApiKey— must match theAPI_KEYused by the device API if authentication is enabled
Example (Functions/local.settings.json):
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"BulbApiBaseUrl": "http://localhost:3000/api",
"BulbApiKey": "your-local-api-key"
}
}Start the Functions app
# Default launch profile runs on port 7210 (see Functions/Properties/launchSettings.json)
dotnet run --project Functions/NextGenDemo.Functions.csproj -- --port 7210
# Base URL: http://localhost:7210/apiMain files
- Functions entry:
Functions/Program.cs - HTTP functions:
Functions/BulbFunctions.cs - Service + settings:
Functions/Services/BulbControlService.cs,Functions/Services/BulbApiSettings.cs - Models:
Shared/Models/BulbControlRequest.cs,Shared/Models/DeviceInfo.cs
Implemented in Functions/BulbFunctions.cs.
- POST
/api/BulbQuickAction
- Body:
{ "bulbIP": "<ip>", "action": "on|off|red|green|blue|yellow|purple|white" }
- POST
/api/ControlBulb
- Body uses strongly-typed models with enum strings for pattern and transition; examples in
Functions/README_API_Examples.json. - Minimal example:
{
"bulbIP": "192.168.1.50",
"colors": [
{ "hex": "#FF0000", "durationMs": 1000 },
{ "hex": "#0000FF", "durationMs": 1000 }
],
"pattern": { "type": "Sequential", "repeatCount": 1, "transition": "Fade", "transitionDurationMs": 500 }
}- GET
/api/ListDevices
- Returns array of devices discovered by the backend API
- POST
/api/devices/{deviceIp}/alias
- Body:
{ "alias": "Living Room Light" }
More payloads and curl examples: Functions/README_API_Examples.json
Run from the Functions folder while the app is running:
cd Functions
./test-bulb-functions.ps1
./test-device-functions.ps1Troubleshooting tips and known fixes: Functions/TROUBLESHOOTING.md
Device management notes: Functions/DEVICE_MANAGEMENT.md
Core classes
- Base:
Plugins/PluginBase.cs - Example plugin that calls the Functions:
Plugins/BulbApi.cs - Environment variables helper:
Plugins/Types/EnvironmentVariableService.cs - HTTP and identity helpers:
Plugins/Integration/HttpClientWrapper.cs,Plugins/Integration/JwtTokenProvider.cs,Plugins/Integration/AzureVariables.cs
Environment variable schema names used by the plugins (Dataverse):
mf_devicelisturl— Device list Function URLmf_bulbquickactionurl— Quick action Function URLmf_bulbcontrolurl— Control Function URLmf_bulbip— Optional default device IP (if relevant)mf_azurefunctionauthscope— Azure AD scope for acquiring tokens- Storage (optional):
mf_storagecontainername,mf_storageendpoint
Build, pack, and sign
# Build
dotnet build Plugins/NextGenDemo.Plugins.csproj
# Pack a NuGet (uses Microsoft.PowerApps.MSBuild.Plugin)
dotnet pack Plugins/NextGenDemo.Plugins.csproj -c Release
# Note: The csproj defines a signing step that runs in Visual Studio after Pack
# (see PostBuild target). You can sign manually with: dotnet nuget sign ...See Plugins/NextGenDemo.Plugins.csproj for package metadata and the PostBuild signing target.
GitHub Actions builds and tests on push/PR to master: .github/workflows/dotnet.yml.
Functions/
Program.cs
BulbFunctions.cs
Services/
BulbControlService.cs
BulbApiSettings.cs
README_API_Examples.json
DEVICE_MANAGEMENT.md
TROUBLESHOOTING.md
Plugins/
PluginBase.cs
BulbApi.cs
Integration/
Types/
Shared/
Models/
BulbControlRequest.cs
DeviceInfo.cs
KasaLightAPI/
README.md
package.json
- Enum values in JSON must be strings (e.g.,
"Fade","Sequential") — seeFunctions/TROUBLESHOOTING.md - Default Functions dev port is 7210 via
Functions/Properties/launchSettings.json - Keep secrets out of source control; use
local.settings.jsonand.env - The local API expects the
X-API-Keyheader ifAPI_KEYis set; the Functions client sends this header whenBulbApiKeyis configured
This repository is provided for demo purposes. Add your preferred license if needed.