diff --git a/Plugins/BTCPayServer.Plugins.BitcoinSwitch/BTCPayServer.Plugins.BitcoinSwitch.csproj b/Plugins/BTCPayServer.Plugins.BitcoinSwitch/BTCPayServer.Plugins.BitcoinSwitch.csproj index f31d0ee..8e83521 100644 --- a/Plugins/BTCPayServer.Plugins.BitcoinSwitch/BTCPayServer.Plugins.BitcoinSwitch.csproj +++ b/Plugins/BTCPayServer.Plugins.BitcoinSwitch/BTCPayServer.Plugins.BitcoinSwitch.csproj @@ -9,7 +9,7 @@ Bitcoin Switch Control harwdare using the POS as a switch - 1.0.0 + 1.0.1 true @@ -39,9 +39,4 @@ - - - - - \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.BitcoinSwitch/BitcoinSwitchService.cs b/Plugins/BTCPayServer.Plugins.BitcoinSwitch/BitcoinSwitchService.cs index c4b5c67..6aec58b 100644 --- a/Plugins/BTCPayServer.Plugins.BitcoinSwitch/BitcoinSwitchService.cs +++ b/Plugins/BTCPayServer.Plugins.BitcoinSwitch/BitcoinSwitchService.cs @@ -17,32 +17,29 @@ using Newtonsoft.Json.Linq; namespace BTCPayServer.Plugins.FileSeller { - - - public class BitcoinSwitchEvent { public string AppId { get; set; } - public string Message { get; set; } - + public string SwitchSettings { get; set; } } - - + public class BitcoinSwitchService : EventHostedServiceBase { private readonly AppService _appService; private readonly InvoiceRepository _invoiceRepository; - public BitcoinSwitchService(EventAggregator eventAggregator, + + public BitcoinSwitchService( + EventAggregator eventAggregator, ILogger logger, AppService appService, - InvoiceRepository invoiceRepository) : base(eventAggregator, logger) + InvoiceRepository invoiceRepository) + : base(eventAggregator, logger) { _appService = appService; _invoiceRepository = invoiceRepository; } public ConcurrentMultiDictionary AppToSockets { get; } = new(); - protected override void SubscribeToEvents() { @@ -55,26 +52,12 @@ namespace BTCPayServer.Plugins.FileSeller { if (evt is BitcoinSwitchEvent bitcoinSwitchEvent) { - if (AppToSockets.TryGetValues(bitcoinSwitchEvent.AppId, out var sockets)) - { - foreach (var socket in sockets) - { - try - { - await socket.SendAsync( - new ArraySegment(System.Text.Encoding.UTF8.GetBytes(bitcoinSwitchEvent.Message)), - WebSocketMessageType.Text, true, cancellationToken); - } - catch (Exception e) - { - - } - } - } + _ = HandleGPIOMessages(cancellationToken, bitcoinSwitchEvent); + return; } if (evt is not InvoiceEvent invoiceEvent) return; - List cartItems = null; +List cartItems = null; if (invoiceEvent.Name is not (InvoiceEvent.Completed or InvoiceEvent.MarkedCompleted or InvoiceEvent.Confirmed)) { @@ -124,27 +107,24 @@ namespace BTCPayServer.Plugins.FileSeller return (null, null, null); } }).Where(tuple => tuple.Data != null && tuple.Items.Any(item => - item.AdditionalData?.ContainsKey("bitcoinswitch_gpio") is true && + item.AdditionalData?.ContainsKey("bitcoinswitch") is true && items.Exists(cartItem => cartItem.Id == item.Id))); foreach (var valueTuple in apps) { foreach (var item1 in valueTuple.Items.Where(item => - item.AdditionalData?.ContainsKey("bitcoinswitch_gpio") is true && + item.AdditionalData?.ContainsKey("bitcoinswitch") is true && items.Exists(cartItem => cartItem.Id == item.Id))) { var appId = valueTuple.Data.Id; - var gpio = item1.AdditionalData["bitcoinswitch_gpio"].Value(); - var duration = item1.AdditionalData.TryGetValue("bitcoinswitch_duration", out var durationObj) && - durationObj.Type == JTokenType.Integer - ? durationObj.Value() - : "5000"; + var gpio = item1.AdditionalData["bitcoinswitch"].Value(); + PushEvent(new BitcoinSwitchEvent() { AppId = appId, - Message = $"{gpio}-{duration}.0" + SwitchSettings = gpio }); } @@ -160,5 +140,105 @@ namespace BTCPayServer.Plugins.FileSeller await base.ProcessEvent(evt, cancellationToken); } + + private async Task HandleGPIOMessages(CancellationToken cancellationToken, BitcoinSwitchEvent bitcoinSwitchEvent) + { + // Parse switch settings into actions + var actions = ParseActions(bitcoinSwitchEvent.SwitchSettings); + + + try + { + // Execute each action sequentially + foreach (var action in actions) + { + if (action.IsDelay) + { + // Wait for specified delay + await Task.Delay(action.DelayMs, cancellationToken); + } + else + { + // Send pin-duration command + var message = $"{action.Pin}-{action.Duration}"; + var buffer = System.Text.Encoding.UTF8.GetBytes(message); + + if (!AppToSockets.TryGetValues(bitcoinSwitchEvent.AppId, out var sockets)) + return; + foreach (var socket in sockets) + { + await socket.SendAsync( + new ArraySegment(buffer), + WebSocketMessageType.Text, + true, + cancellationToken); + } + + } + } + } + catch (Exception ex) + { + Logs.PayServer.LogError(ex, "Error sending BitcoinSwitchEvent to socket"); + } + + + return; + } + + /// + /// Parses a settings string like "25-5000.0,delay 1000,23-200.0" into a sequence of actions. + /// + private static List ParseActions(string settings) + { + var actions = new List(); + var segments = settings.Split(',', StringSplitOptions.RemoveEmptyEntries); + foreach (var seg in segments.Select(s => s.Trim())) + { + if (seg.StartsWith("delay ", StringComparison.OrdinalIgnoreCase)) + { + // Delay segment + var parts = seg.Split(' ', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 2 && int.TryParse(parts[1], out var ms)) + { + actions.Add(SwitchAction.Delay(ms)); + } + } + else + { + // Pin-duration segment + var parts = seg.Split('-', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 2 + && int.TryParse(parts[0], out var pin) + && double.TryParse(parts[1], out var duration)) + { + actions.Add(SwitchAction.Command(pin, duration)); + } + } + } + return actions; + } } -} \ No newline at end of file + + /// + /// Represents either a delay or a pin-duration command. + /// + public class SwitchAction + { + public bool IsDelay { get; } + public int DelayMs { get; } + public int Pin { get; } + public double Duration { get; } + + private SwitchAction(bool isDelay, int delayMs, int pin, double duration) + { + IsDelay = isDelay; + DelayMs = delayMs; + Pin = pin; + Duration = duration; + } + + public static SwitchAction Delay(int ms) => new(true, ms, 0, 0); + public static SwitchAction Command(int pin, double duration) => new(false, 0, pin, duration); + } +} diff --git a/Plugins/BTCPayServer.Plugins.BitcoinSwitch/Views/Shared/BitcoinSwitch/BitcoinSwitchPluginTemplateEditorItemDetail.cshtml b/Plugins/BTCPayServer.Plugins.BitcoinSwitch/Views/Shared/BitcoinSwitch/BitcoinSwitchPluginTemplateEditorItemDetail.cshtml index c99e29f..ca2005c 100644 --- a/Plugins/BTCPayServer.Plugins.BitcoinSwitch/Views/Shared/BitcoinSwitch/BitcoinSwitchPluginTemplateEditorItemDetail.cshtml +++ b/Plugins/BTCPayServer.Plugins.BitcoinSwitch/Views/Shared/BitcoinSwitch/BitcoinSwitchPluginTemplateEditorItemDetail.cshtml @@ -1,12 +1,23 @@  \ No newline at end of file + +

+ Each segment specifies a GPIO pin and its activation duration (in milliseconds) in the format pin-duration (e.g., 25-5000.0). You can also insert ,delay milliseconds (e.g., ,delay 5000) between segments to pause before the next activation. Separate all parts with commas. +

+