Yoooi0 / MultiFunPlayer

flexible application to synchronize various devices with media playback
https://yoooi0.github.io/MultiFunPlayer/
MIT License
122 stars 22 forks source link

Nogasm plugin #106

Closed Michel-0 closed 1 year ago

Michel-0 commented 1 year ago

Hi there, are you guys aware of the nogasm community?

These guys use edge-o-matic devices (buying or DIY), which calculate an arousal level based on pressure measurement from a toy in your butt. A fast rising or high level may stop whatever toy connected, as the level falls the toy gets restarted.

Basically making it ideally an infinite loop... I think you get the idea...

What I'm requesting here is basically an API like TCP or Websocket or whatever for MFP to:

There is actually a product by that name I mentioned above (as far as I know not a trademark). I do not want to advertise this product and I am not associated in any way with the company manufacturing or distributing it. You may get in contact with this company, there may be a commercial use for this API in combination with that product on the market.

Actually I build such a thing DIY - way on my own. As well as I implemented a web-based software for visualization and calculation. But now the connection to MFP is missing to actually beeing able to control my device.

Looking forward for your thoughts on this use case.

Kind regards, me.

EDIT: I'm not yet sure if such an API would be the correct way to do that. Since MFP already has many limit and scale features by itself, I thought controlling them remotely would be perfect. But a alternative may be something like a external gateway server between MFP and e.g. buttplug.io. MFP already supports WebSocket, UDP, TCP output (somehow), I may connect MFP to my own implemented WebSocketServer, throttle the signal there and connect it to my device. Then the work would be to be done on my side...

Yoooi0 commented 1 year ago

Development builds, which you can find under actions tab, have support for plugins. Current plugin api is here: https://github.com/Yoooi0/MultiFunPlayer/blob/master/MultiFunPlayer/Plugin/PluginBase.cs

You should be able to do whatever you want inside that plugin, and to control MFP you use InvokeAction/PublishMessage.

I dont know if you are the one that contacted me on discord about this, but I actually made a sample plugin for EOM3000. It uses Axis::SpeedLimitSecondsPerStroke::Set action but there should be a bunch of other ways to limit the motion. It completely untested as I dont have one but should be a good starting point.

Save both to Plugins directory next to MultiFunPlayer.exe.

EoM3000.cs:

//#r "name:System.Net.HttpListener"
//#r "name:System.Private.Uri"

using MultiFunPlayer.Plugin;
using MultiFunPlayer.Common;
using System;
using System.IO;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Reflection;
using System.Linq;
using Newtonsoft.Json.Linq;

namespace Plugin;

public class EoM3000PluginSettings : PluginSettingsBase
{
    private Uri _uri = new Uri("ws://127.0.0.1:80/");

    public Uri Uri
    {
        get => _uri;
        set => SetAndNotify(ref _uri, value);
    }

    public override UIElement CreateView() => CreateViewFromFile("Plugins\\EoM3000.xaml");

    public override void HandleSettings(JObject settings, SettingsAction action)
    {
        if (action == SettingsAction.Saving)
        {
            settings[nameof(Uri)] = Uri?.ToString();
        }
        else if (action == SettingsAction.Loading)
        {
            if (settings.TryGetValue<Uri>(nameof(Uri), out var uri))
                Uri = uri;
        }
    }
}

public class EoM3000Plugin : AsyncPluginBase
{
    public EoM3000PluginSettings Settings { get; }

    public EoM3000Plugin(EoM3000PluginSettings settings) => Settings = settings;

    public override async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        using var client = new ClientWebSocket();
        await client.ConnectAsync(Settings.Uri, cancellationToken);

        var readBuffer = new byte[1024];
        while (!cancellationToken.IsCancellationRequested)
        {
            using var memory = new MemoryStream();

            var result = default(WebSocketReceiveResult);
            do
            {
                result = await client.ReceiveAsync(readBuffer, cancellationToken);
                memory.Write(readBuffer, 0, result.Count);
            } while (!cancellationToken.IsCancellationRequested && !result.EndOfMessage);

            var message = Encoding.UTF8.GetString(memory.GetBuffer());
            var o = JObject.Parse(message);
            if (!o.TryGetObject(out var readings, "readings"))
                continue;

            if (!readings.TryGetValue<double>("arousal", out var arousal))
                continue;

            arousal = MathUtils.Clamp(arousal, 0, 600);
            var factor = Math.Pow(arousal < 200 ? 0 : MathUtils.Clamp01((arousal - 200) / 400), 4);
            factor = Math.Pow(factor, 4);

            var secondsPerStroke = MathUtils.Lerp(0.001, 10, factor);
            InvokeAction("Axis::SpeedLimitSecondsPerStroke::Set", DeviceAxis.Parse("L0"), secondsPerStroke);
            Logger.Info("{0} {1}", arousal, factor);
        }
    }
}

EoM3000.xaml:

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:converters="clr-namespace:MultiFunPlayer.UI.Converters;assembly=MultiFunPlayer"
             xmlns:material="http://materialdesigninxaml.net/winfx/xaml/themes"
             mc:Ignorable="d"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <converters:UriToStringConverter x:Key="UriToStringConverter"/>
    </UserControl.Resources>
    <StackPanel>
        <TextBox Text="{Binding Uri, Converter={StaticResource UriToStringConverter}}"
                 material:HintAssist.Hint="websocket uri"
                 Style="{StaticResource MaterialDesignFloatingHintTextBox}"
                 Width="250"
                 VerticalAlignment="Center"/>
    </StackPanel>
</UserControl>
Michel-0 commented 1 year ago

Wow... uhm... yes ok. Thanks! Will have a look at it. I didn't contact you on discord... as I said i do not own a EOM (instead a DIY thing).[^1]

I'll reply once it works or I might get stuck somewhere with the plugin thing...

[^1]: I'm the guy called ibims from eroscripts.com you talked to recently.

Michel-0 commented 1 year ago

I did get 1.23.2+98.e3d9a8a working (got some problems with .NET 7, so i choose the one before). Set up the EoM3000Plugin & EoM3000PluginSettings, altough i had to use PluginBase instead of PluginSettingsBase, because it told me a compile error PluginSettingsBase not found. However i think it works, and i also think i understand the source code, so i got all informations to keep working...

Until further notice i think this "issue" can be marked as solved.

Yoooi0 commented 1 year ago

There was a bunch of changes to the plugin system after that commit so you should just install .net7, otherwise the plugin will not work after release.