mirror of
https://github.com/aljazceru/BTCPayServerPlugins.git
synced 2025-12-17 15:44:26 +01:00
enhance the shit out of it
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Product>Bitcoin Switch</Product>
|
<Product>Bitcoin Switch</Product>
|
||||||
<Description>Control harwdare using the POS as a switch</Description>
|
<Description>Control harwdare using the POS as a switch</Description>
|
||||||
<Version>1.0.0</Version>
|
<Version>1.0.1</Version>
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<!-- Plugin development properties -->
|
<!-- Plugin development properties -->
|
||||||
@@ -39,9 +39,4 @@
|
|||||||
<Folder Include="Resources\js\" />
|
<Folder Include="Resources\js\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<AdditionalFiles Include="Views\Shared\BitcoinSwitch\FileSellerTemplateEditorItemDetail.cshtml" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -17,25 +17,23 @@ using Newtonsoft.Json.Linq;
|
|||||||
|
|
||||||
namespace BTCPayServer.Plugins.FileSeller
|
namespace BTCPayServer.Plugins.FileSeller
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public class BitcoinSwitchEvent
|
public class BitcoinSwitchEvent
|
||||||
{
|
{
|
||||||
public string AppId { get; set; }
|
public string AppId { get; set; }
|
||||||
public string Message { get; set; }
|
public string SwitchSettings { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class BitcoinSwitchService : EventHostedServiceBase
|
public class BitcoinSwitchService : EventHostedServiceBase
|
||||||
{
|
{
|
||||||
private readonly AppService _appService;
|
private readonly AppService _appService;
|
||||||
private readonly InvoiceRepository _invoiceRepository;
|
private readonly InvoiceRepository _invoiceRepository;
|
||||||
public BitcoinSwitchService(EventAggregator eventAggregator,
|
|
||||||
|
public BitcoinSwitchService(
|
||||||
|
EventAggregator eventAggregator,
|
||||||
ILogger<BitcoinSwitchService> logger,
|
ILogger<BitcoinSwitchService> logger,
|
||||||
AppService appService,
|
AppService appService,
|
||||||
InvoiceRepository invoiceRepository) : base(eventAggregator, logger)
|
InvoiceRepository invoiceRepository)
|
||||||
|
: base(eventAggregator, logger)
|
||||||
{
|
{
|
||||||
_appService = appService;
|
_appService = appService;
|
||||||
_invoiceRepository = invoiceRepository;
|
_invoiceRepository = invoiceRepository;
|
||||||
@@ -43,7 +41,6 @@ namespace BTCPayServer.Plugins.FileSeller
|
|||||||
|
|
||||||
public ConcurrentMultiDictionary<string, WebSocket> AppToSockets { get; } = new();
|
public ConcurrentMultiDictionary<string, WebSocket> AppToSockets { get; } = new();
|
||||||
|
|
||||||
|
|
||||||
protected override void SubscribeToEvents()
|
protected override void SubscribeToEvents()
|
||||||
{
|
{
|
||||||
Subscribe<InvoiceEvent>();
|
Subscribe<InvoiceEvent>();
|
||||||
@@ -55,22 +52,8 @@ namespace BTCPayServer.Plugins.FileSeller
|
|||||||
{
|
{
|
||||||
if (evt is BitcoinSwitchEvent bitcoinSwitchEvent)
|
if (evt is BitcoinSwitchEvent bitcoinSwitchEvent)
|
||||||
{
|
{
|
||||||
if (AppToSockets.TryGetValues(bitcoinSwitchEvent.AppId, out var sockets))
|
_ = HandleGPIOMessages(cancellationToken, bitcoinSwitchEvent);
|
||||||
{
|
return;
|
||||||
foreach (var socket in sockets)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await socket.SendAsync(
|
|
||||||
new ArraySegment<byte>(System.Text.Encoding.UTF8.GetBytes(bitcoinSwitchEvent.Message)),
|
|
||||||
WebSocketMessageType.Text, true, cancellationToken);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evt is not InvoiceEvent invoiceEvent) return;
|
if (evt is not InvoiceEvent invoiceEvent) return;
|
||||||
@@ -124,27 +107,24 @@ namespace BTCPayServer.Plugins.FileSeller
|
|||||||
return (null, null, null);
|
return (null, null, null);
|
||||||
}
|
}
|
||||||
}).Where(tuple => tuple.Data != null && tuple.Items.Any(item =>
|
}).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)));
|
items.Exists(cartItem => cartItem.Id == item.Id)));
|
||||||
|
|
||||||
|
|
||||||
foreach (var valueTuple in apps)
|
foreach (var valueTuple in apps)
|
||||||
{
|
{
|
||||||
foreach (var item1 in valueTuple.Items.Where(item =>
|
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)))
|
items.Exists(cartItem => cartItem.Id == item.Id)))
|
||||||
{
|
{
|
||||||
var appId = valueTuple.Data.Id;
|
var appId = valueTuple.Data.Id;
|
||||||
var gpio = item1.AdditionalData["bitcoinswitch_gpio"].Value<string>();
|
var gpio = item1.AdditionalData["bitcoinswitch"].Value<string>();
|
||||||
var duration = item1.AdditionalData.TryGetValue("bitcoinswitch_duration", out var durationObj) &&
|
|
||||||
durationObj.Type == JTokenType.Integer
|
|
||||||
? durationObj.Value<string>()
|
|
||||||
: "5000";
|
|
||||||
|
|
||||||
PushEvent(new BitcoinSwitchEvent()
|
PushEvent(new BitcoinSwitchEvent()
|
||||||
{
|
{
|
||||||
AppId = appId,
|
AppId = appId,
|
||||||
Message = $"{gpio}-{duration}.0"
|
SwitchSettings = gpio
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -160,5 +140,105 @@ namespace BTCPayServer.Plugins.FileSeller
|
|||||||
|
|
||||||
await base.ProcessEvent(evt, cancellationToken);
|
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<byte>(buffer),
|
||||||
|
WebSocketMessageType.Text,
|
||||||
|
true,
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logs.PayServer.LogError(ex, "Error sending BitcoinSwitchEvent to socket");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses a settings string like "25-5000.0,delay 1000,23-200.0" into a sequence of actions.
|
||||||
|
/// </summary>
|
||||||
|
private static List<SwitchAction> ParseActions(string settings)
|
||||||
|
{
|
||||||
|
var actions = new List<SwitchAction>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents either a delay or a pin-duration command.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,23 @@
|
|||||||
|
|
||||||
<template v-if="editingItem">
|
<template v-if="editingItem">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Bitcoin Switch GPIO</label>
|
<label class="form-label">Bitcoin Switch</label>
|
||||||
<input type="number" inputmode="numeric" min="0" step="1" class="form-control mb-2" :value="editingItem['bitcoinswitch_gpio'] || ''" v-on:change="if(event.target.value) Vue.set(editingItem, 'bitcoinswitch_gpio', event.target.value); else Vue.delete(editingItem, 'bitcoinswitch_gpio');"/>
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control mb-2"
|
||||||
|
:value="editingItem['bitcoinswitch'] || ''"
|
||||||
|
pattern="^[0-9]+-[0-9]+(?:\.[0-9]+)?(?:\s*,\s*(?:delay\s*[0-9]+|[0-9]+-[0-9]+(?:\.[0-9]+)?))*$"
|
||||||
|
title="e.g. 25-5000,delay 5000,26-5000.0,delay 2000,23-200"
|
||||||
|
v-on:change="
|
||||||
|
event.target.reportValidity();
|
||||||
|
const val = event.target.value;
|
||||||
|
if (val) Vue.set(editingItem, 'bitcoinswitch', val);
|
||||||
|
else Vue.delete(editingItem, 'bitcoinswitch');
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group" v-if="editingItem['bitcoinswitch_gpio']">
|
<p>
|
||||||
<label class="form-label">Bitcoin Switch Duration</label>
|
Each segment specifies a <strong>GPIO pin</strong> and its <strong>activation duration</strong> (in milliseconds) in the format <code>pin-duration</code> (e.g., <code>25-5000.0</code>). You can also insert <code>,delay milliseconds</code> (e.g., <code>,delay 5000</code>) between segments to pause before the next activation. Separate all parts with commas.
|
||||||
<input type="number" inputmode="numeric" min="1" step="1000" class="form-control mb-2" :value="editingItem['bitcoinswitch_duration'] || ''" asp-items="files" class="form-select w-auto" v-on:change="if(event.target.value) Vue.set(editingItem, 'bitcoinswitch_duration', event.target.value); else Vue.delete(editingItem, 'bitcoinswitch_duration');"/>
|
</p>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
Reference in New Issue
Block a user