You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C#中构建本地uPnP应用实现Alexa/Echo无AWS控制(Windows服务)

Alright, let's break down exactly how to build this local-only C# uPnP app that mimics WeMo for Alexa control—no AWS Skill required, and packaged as a Windows service. This approach leans into the same local discovery and control mechanisms Belkin uses, so Alexa will treat your app just like a real WeMo device.

Core Background: Why WeMo Works Without the Cloud

WeMo devices rely on two key local network protocols that Alexa understands natively:

  1. SSDP (Simple Service Discovery Protocol): A UDP-based system where devices announce themselves (and respond to search queries) on the local network.
  2. SOAP (Simple Object Access Protocol): An XML-based API that Alexa uses to send control commands (like "turn on") and retrieve device state.

Your app needs to implement both to trick Alexa into seeing it as a WeMo device.

Step 1: Implement SSDP for Alexa Discovery

Alexa periodically sends M-SEARCH requests over UDP to the multicast address 239.255.255.250 on port 1900, looking for WeMo devices. Your app needs to:

  • Listen for these requests and send a properly formatted SSDP response.
  • (Optional) Send periodic NOTIFY messages to speed up initial discovery.

C# SSDP Listener Snippet

using System.Net;
using System.Net.Sockets;
using System.Text;

public class SsdpDiscoveryService
{
    private UdpClient _udpListener;
    private const int SsdpPort = 1900;
    private const string MulticastAddr = "239.255.255.250";
    private readonly string _deviceUuid = $"uuid:{Guid.NewGuid()}"; // Generate a unique ID once and reuse it
    private readonly string _localIp;

    public SsdpDiscoveryService()
    {
        // Get your local IPv4 address (skip loopback addresses)
        _localIp = Dns.GetHostEntry(Dns.GetHostName())
            .AddressList
            .First(ip => ip.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(ip))
            .ToString();
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        _udpListener = new UdpClient(SsdpPort);
        _udpListener.JoinMulticastGroup(IPAddress.Parse(MulticastAddr));

        while (!cancellationToken.IsCancellationRequested)
        {
            var receiveResult = await _udpListener.ReceiveAsync(cancellationToken);
            var request = Encoding.UTF8.GetString(receiveResult.Buffer);

            // Check if this is an Alexa search for WeMo devices
            if (request.Contains("M-SEARCH") && request.Contains("urn:Belkin:device:**"))
            {
                var response = BuildSsdpResponse();
                await _udpListener.SendAsync(Encoding.UTF8.GetBytes(response), response.Length, receiveResult.RemoteEndPoint);
            }
        }
    }

    private string BuildSsdpResponse()
    {
        // The location points to your HTTP service's device description XML
        var locationUrl = $"http://{_localIp}:8080/setup.xml";

        return $"HTTP/1.1 200 OK\r\n" +
               "CACHE-CONTROL: max-age=86400\r\n" +
               $"LOCATION: {locationUrl}\r\n" +
               "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n" +
               "ST: urn:Belkin:device:**\r\n" +
               $"USN: {_deviceUuid}::urn:Belkin:device:**\r\n" +
               "EXT:\r\n" +
               "\r\n";
    }

    public void Stop() => _udpListener?.Close();
}
Step 2: Build the WeMo-Compatible HTTP/SOAP Service

Once Alexa discovers your device via SSDP, it will fetch a device description XML from the LOCATION URL you provided, then send SOAP commands to control it. You need:

  • A local HTTP server to serve the XML and handle SOAP requests.
  • Strict adherence to WeMo's XML/SOAP schema (Alexa is picky about formatting).

1. Serve the Device Description XML (setup.xml)

Create this XML file and serve it from /setup.xml:

<?xml version="1.0"?>
<root xmlns="urn:Belkin:device-1-0">
  <specVersion>
    <major>1</major>
    <minor>0</minor>
  </specVersion>
  <device>
    <deviceType>urn:Belkin:device:controllee:1</deviceType>
    <friendlyName>My C# Smart Plug</friendlyName>
    <manufacturer>Belkin International Inc.</manufacturer>
    <manufacturerURL>http://www.belkin.com</manufacturerURL>
    <modelDescription>Belkin Plugin Socket 1.0</modelDescription>
    <modelName>Socket</modelName>
    <modelNumber>1.0</modelNumber>
    <modelURL>http://www.belkin.com/plugin/</modelURL>
    <serialNumber>123456789</serialNumber>
    <UDN>uuid:your-unique-device-id-here</UDN> <!-- Match the UUID from SSDP -->
    <UPC>123456789012</UPC>
    <serviceList>
      <service>
        <serviceType>urn:Belkin:service:basicevent:1</serviceType>
        <serviceId>urn:Belkin:serviceId:basicevent1</serviceId>
        <controlURL>/upnp/control/basicevent1</controlURL>
        <eventSubURL>/upnp/event/basicevent1</eventSubURL>
        <SCPDURL>/eventservice.xml</SCPDURL>
      </service>
    </serviceList>
  </device>
</root>

2. Handle SOAP Control Requests

Use ASP.NET Core (great for Windows services) to build the SOAP endpoint. Here's a minimal example:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Serve the device description XML
app.MapGet("/setup.xml", () => Results.File("setup.xml", "text/xml"));

// Handle WeMo's basic event control (on/off commands)
app.MapPost("/upnp/control/basicevent1", async (HttpContext context) =>
{
    using var reader = new StreamReader(context.Request.Body);
    var soapRequest = await reader.ReadToEndAsync();

    // Handle "turn on/off" requests
    if (soapRequest.Contains("SetBinaryState"))
    {
        // Extract the desired state (0 = off, 1 = on)
        var stateMatch = System.Text.RegularExpressions.Regex.Match(soapRequest, "<BinaryState>(\\d)</BinaryState>");
        if (stateMatch.Success)
        {
            var targetState = stateMatch.Groups[1].Value;
            // Add your custom logic here (e.g., control a local app, toggle a virtual switch)
            Console.WriteLine($"Received command to set state to: {targetState}");
        }

        // Send a success response back to Alexa
        var soapResponse = @"<?xml version=""1.0"" encoding=""utf-8""?>
<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"" s:encodingStyle=""http://schemas.xmlsoap.org/soap/encoding/"">
  <s:Body>
    <u:SetBinaryStateResponse xmlns:u=""urn:Belkin:service:basicevent:1"">
      <BinaryState>1</BinaryState>
    </u:SetBinaryStateResponse>
  </s:Body>
</s:Envelope>";

        context.Response.ContentType = "text/xml";
        await context.Response.WriteAsync(soapResponse);
    }
    // Handle state check requests
    else if (soapRequest.Contains("GetBinaryState"))
    {
        var currentState = "1"; // Replace with your actual device state
        var soapResponse = @"<?xml version=""1.0"" encoding=""utf-8""?>
<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"" s:encodingStyle=""http://schemas.xmlsoap.org/soap/encoding/"">
  <s:Body>
    <u:GetBinaryStateResponse xmlns:u=""urn:Belkin:service:basicevent:1"">
      <BinaryState>" + currentState + @"</BinaryState>
    </u:GetBinaryStateResponse>
  </s:Body>
</s:Envelope>";

        context.Response.ContentType = "text/xml";
        await context.Response.WriteAsync(soapResponse);
    }
});

// Run the HTTP server on port 8080
await app.RunAsync("http://*:8080");
Step 3: Package as a Windows Service

Wrap your SSDP and HTTP logic into a Windows service using .NET's BackgroundService (works for .NET Core 3.0+):

Service Implementation

public class WemoEmulatorService : BackgroundService
{
    private readonly SsdpDiscoveryService _ssdpService;
    private IHost _httpHost;

    public WemoEmulatorService(SsdpDiscoveryService ssdpService)
    {
        _ssdpService = ssdpService;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // Start the SSDP listener
        var ssdpTask = _ssdpService.StartAsync(stoppingToken);

        // Start the HTTP service
        var httpBuilder = WebApplication.CreateBuilder();
        // Add your HTTP endpoints here (copy the ASP.NET Core logic from Step 2)
        _httpHost = httpBuilder.Build();
        var httpTask = _httpHost.RunAsync(stoppingToken);

        // Wait for either service to stop
        await Task.WhenAny(ssdpTask, httpTask);
    }

    public override async Task StopAsync(CancellationToken cancellationToken)
    {
        _ssdpService.Stop();
        await _httpHost.StopAsync(cancellationToken);
        await base.StopAsync(cancellationToken);
    }
}

Install the Service

  • Use the .NET CLI to publish your app as a self-contained executable:
    dotnet publish -c Release -r win-x64 --self-contained true
    
  • Install the service using the sc command (run Command Prompt as Administrator):
    sc create "Wemo Emulator" binPath="C:\path\to\your\published\WemoEmulator.exe" start=auto
    
  • Start the service:
    sc start "Wemo Emulator"
    
Step 4: Test Locally
  1. Ensure your Windows service is running and port 8080 is open in your local firewall.
  2. Open the Alexa app on your phone: Go to Devices > + > Add Device > Plug > WeMo > Continue. Alexa should discover your C# device.
  3. Test voice commands like "Alexa, turn on My C# Smart Plug"—your service should receive and process the request.
Troubleshooting Tips
  • Discovery Failures: Use Wireshark to capture UDP traffic on port 1900. Verify your SSDP responses are being sent and received by Alexa.
  • SOAP Errors: Enable logging for your HTTP service to see the exact requests Alexa sends. Double-check XML namespaces and response formatting—Alexa rejects malformed SOAP.
  • Permission Issues: If the service can't listen on ports, make sure it's running under an account with network permissions (LocalService usually works, but you may need to adjust firewall rules).

内容的提问来源于stack exchange,提问作者Paul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 04:28:21