Skip to main content

Python Lovense API client

Project description

LovensePy

License: Apache 2.0

Python client for the Lovense API. Supports Standard API (LAN & Server), Standard Socket API, and Toy Events API.

Table of Contents


Features

  • Standard API LAN (Game Mode): GetToys, GetToyName, Function, Stop, Pattern, Preset, Position, PatternV2
  • Standard API Server: Function, Pattern, Preset via Lovense cloud; get_qr_code for QR pairing
  • Standard Socket API: getToken, getSocketUrl, WebSocket client for QR flow and remote control
  • Toy Events API: Real-time events (toy-list, button-down, function-strength-changed, etc.)

Prerequisites

Before using LovensePy, ensure you have:

  • Lovense Remote or Lovense Connect app installed on your phone or PC
  • Lovense toy paired with the app
  • Same Wi-Fi network as the device (required for LAN/Game Mode)
  • Developer token (for Server/Socket API) — obtain from Lovense Developer Dashboard
  • Callback URL (for Server API QR pairing) — e.g. ngrok tunnel or similar to receive pairing callbacks

Installation

pip install lovensepy

Dependencies: httpx, pydantic, websockets


Quick Start

Get your first command running in 4 steps:

Step 1: Install the package (see Installation).

Step 2: Enable Game Mode in Lovense Remote:

  • Open Lovense Remote > Discover > Game Mode > Enable LAN
  • Note the IP address (e.g. 192.168.1.100) and port (20011 for Remote, 34567 for Connect)

Step 3: Create a Python script:

from lovensepy import LANClient, Actions

client = LANClient("MyApp", "192.168.1.100", port=20011)
client.function_request({Actions.VIBRATE: 10}, time=3)

Step 4: Run the script. Your toy should vibrate at level 10 for 3 seconds.

Note: The time parameter is in seconds. The device holds the level until the next command or until you call client.stop().


API Variants

API Client Auth Notes
Standard / local LANClient Game Mode (IP + port) Lovense Remote: 20011/30011. Connect: 34567
Standard / server ServerClient token + uid uid from QR callback. Use get_qr_code for pairing
Socket / server SocketAPIClient getToken, getSocketUrl QR scan, commands via WebSocket
Socket / local SocketAPIClient(use_local_commands=True) same + LAN Commands via HTTPS to device
Socket / local only LANClient IP + port only No token, no WebSocket
Events API ToyEventsClient access (appName) Port 20011. Lovense Remote only

Flow: Standard local → HTTP/HTTPS to device. Standard server → HTTPS to Lovense cloud. Socket → WebSocket to cloud (or HTTPS to device when use_local_commands=True). Events → WebSocket to device.

API Architecture

flowchart TB
    subgraph YourApp [Your App - lovensepy]
        LANClient
        ServerClient
        SocketAPIClient
        ToyEventsClient
    end

    subgraph Local [Local Network]
        RemoteApp[Lovense Remote App]
        Toy[Lovense Toy]
    end

    subgraph Cloud [Lovense Cloud]
        LovenseServer[Lovense Server]
    end

    LANClient -->|"HTTP/HTTPS"| RemoteApp
    RemoteApp --> Toy

    ServerClient -->|"HTTPS"| LovenseServer
    LovenseServer --> RemoteApp

    SocketAPIClient -->|"WebSocket"| LovenseServer
    SocketAPIClient -->|"HTTPS when use_local_commands"| RemoteApp
    LovenseServer --> RemoteApp

    ToyEventsClient -->|"WebSocket"| RemoteApp

Step-by-Step Tutorials

LAN Game Mode Tutorial

Step 1: Enable Game Mode in Lovense Remote

  • Open Lovense Remote > Discover > Game Mode > Enable LAN
  • Or Lovense Connect > Game Mode > Enable LAN

Step 2: Note the IP and port

  • Lovense Remote: typically port 20011 (HTTP) or 30011 (HTTPS)
  • Lovense Connect: typically port 34567

Step 3: Create the client

from lovensepy import LANClient

client = LANClient("MyApp", "192.168.1.100", port=20011)

Step 4: Get connected toys

response = client.get_toys()
toys = {toy.id: toy.model_dump() for toy in response.data.toys} if response.data else {}
# toys is {toy_id: toy_info}

Step 5: Send a Function command (auto-stop)

import time
from lovensepy import Actions

# Vibrate at level 10 for 5 seconds; toy is auto-stopped on context exit.
with client.play({Actions.VIBRATE: 10}, time=5):
    time.sleep(5)

Step 8: Optional — use Presets or Patterns

from lovensepy import Presets

client.preset_request(Presets.PULSE, time=5)
time.sleep(5)

# Custom pattern: list of strength levels (0-20)
client.pattern_request([5, 10, 15, 20], time=4)
time.sleep(4)

client.stop()

Full example:

import time
from lovensepy import LANClient, Actions, Presets

client = LANClient("MyApp", "192.168.1.100", port=20011)

# Get toys
toys_response = client.get_toys()
toys = {toy.id: toy.model_dump() for toy in toys_response.data.toys} if toys_response.data else {}
print("Toys:", toys)

# Preset
client.preset_request(Presets.PULSE, time=5)
time.sleep(5)

# Function
client.function_request({Actions.ALL: 5}, time=3)
time.sleep(3)

# Pattern
client.pattern_request([5, 10, 15, 20], time=4)
time.sleep(4)

# Stop
client.stop()

Server API + QR Pairing Tutorial

Step 1: Get your developer token from the Lovense Developer Dashboard.

Step 2: Set up a callback URL (e.g. ngrok) and configure it in the Dashboard. Lovense will POST to this URL when a user scans the QR code.

Step 3: Call get_qr_code to get the QR image URL and 6-character code

from lovensepy import get_qr_code

qr_data = get_qr_code(developer_token="YOUR_TOKEN", uid="user_123")
qr_url = qr_data["qr"]   # Image URL for user to scan
code = qr_data["code"]   # 6-char code for PC Remote
print(f"Scan QR: {qr_url}")

Step 4: User scans the QR code in Lovense Remote.

Step 5: Lovense POSTs to your callback URL with uid and toys. Your server receives this and stores the uid.

Step 6: Create the ServerClient with the uid from the callback

from lovensepy import ServerClient, Actions

client = ServerClient(developer_token="YOUR_TOKEN", uid="user_123")

Step 7: Send commands (same as LAN)

import time

client.function_request({Actions.VIBRATE: 10}, time=5)
time.sleep(5)
client.stop()

Socket API Tutorial

The Socket API is async only. Use asyncio.run() to run your async code.

Step 1: Get an auth token

from lovensepy import get_token

auth_token = get_token(
    developer_token="YOUR_TOKEN",
    uid="user_123",
    uname="User"
)

Step 2: Get socket URL info. The platform must match the Website Name from your Lovense Developer Dashboard exactly.

from lovensepy import get_socket_url

socket_info = get_socket_url(auth_token, platform="Your App")

Step 3: Build the WebSocket URL

from lovensepy import build_websocket_url

ws_url = build_websocket_url(socket_info, auth_token)

Step 4: Create the client and connect

import asyncio
from lovensepy import SocketAPIClient

async def main():
    client = SocketAPIClient(ws_url, on_event=lambda e, p: print(e, p))
    await client.connect()  # starts background loops and returns quickly

Step 5: Request QR code (when on_socket_io_connected fires)

    client_ref = []

    def on_connected():
        client_ref[0].send_event("basicapi_get_qrcode_ts", {"ackId": "1"})

    client = SocketAPIClient(ws_url, on_socket_io_connected=on_connected, on_event=...)
    client_ref.append(client)

Step 6: User scans QR. You receive basicapi_update_device_info_tc with the toy list in the payload.

Step 7: Send commands when client.is_socket_io_connected is True

    if client.is_socket_io_connected:
        client.send_command("Function", "Vibrate:10", time_sec=5, toy="toy_id")

Step 8: Use send_command_await for critical stops (awaits delivery)

    await client.send_command_await("Function", "Stop", time_sec=0, toy="toy_id")

Step 9: For 24/7 bots, run auto-reconnect loop in background

    # Keep connection alive even after transient disconnects.
    runner = client.start_background(auto_reconnect=True, retry_delay=5.0)

You can also use await client.connect_with_retry(retry_delay=5.0) directly.

By local (same LAN): Pass use_local_commands=True — after QR scan, commands go via HTTPS to the device instead of WebSocket.


Toy Events Tutorial

Toy Events is Lovense Remote only (port 20011). Lovense Connect does not support Toy Events.

Step 1: Ensure you use Lovense Remote with Game Mode enabled. Port is typically 20011.

Step 2: Create the client with an event callback

import asyncio
from lovensepy import ToyEventsClient

def on_event(event_type, payload):
    print(event_type, payload)

client = ToyEventsClient(
    "192.168.1.100",
    port=20011,
    app_name="My App",
    on_event=on_event
)

Step 3: Connect (async)

async def main():
    await client.connect()

asyncio.run(main())

Step 4: User grants access when Lovense Remote prompts "Allow [My App] to access?"

Step 5: Receive events: toy-list, button-down, function-strength-changed, shake, etc.


API Reference

LANClient

Standard API LAN (Game Mode) client. Sends commands via HTTP/HTTPS to the Lovense app on the same network.

For async applications (Discord, Telegram, FastAPI, workers), use AsyncLANClient.

Constructor

LANClient(
    app_name: str,
    local_ip: str | None = None,
    *,
    domain: str | None = None,
    port: int = 20011,
    ssl_port: int = 30011,
    use_https: bool = False,
    verify_ssl: bool = True,
    timeout: float = 10.0,
)
Parameter Type Default Description
app_name str Application name (e.g. "MyApp")
local_ip str None Device IP (e.g. "192.168.1.100"). Use with domain=None.
domain str None Pre-built domain (e.g. "192-168-1-100.lovense.club"). Use when you have domain from Socket API.
port int 20011 HTTP port (Lovense Remote: 20011, Connect: 34567)
ssl_port int 30011 HTTPS port
use_https bool False Use HTTPS instead of HTTP
verify_ssl bool True Verify SSL cert. If False, uses fingerprint pinning.
timeout float 10.0 Request timeout in seconds

Example:

client = LANClient("MyApp", "192.168.1.100", port=20011)

Class method: LANClient.from_device_info(app_name, domain, https_port=30011, **kwargs) — Create from Socket API device info (e.g. basicapi_update_device_info_tc payload).

Methods

Method Parameters Returns Description
get_toys() GetToysResponse Get connected toys. Uses a typed data.toys[] list.
get_toys_name() GetToyNameResponse Get connected toy names.
function_request(actions, time=0, loop_on_time=None, loop_off_time=None, toy_id=None, stop_previous=None) actions: dict like {Actions.VIBRATE: 10}; time: seconds CommandResponse Send Function command. time in seconds.
stop(toy_id=None) toy_id: str or list CommandResponse Stop all motors.
preset_request(name, time=0, toy_id=None) name: Presets enum or str CommandResponse Send Preset (pulse, wave, etc.).
pattern_request(pattern, actions=None, interval=100, time=0, toy_id=None) pattern: list of 0–20; interval: ms CommandResponse Custom pattern.
pattern_request_raw(strength, rule="V:1;F:;S:100#", time=0, toy_id=None) Raw rule/strength strings CommandResponse Advanced pattern.
position_request(value, toy_id=None) value: 0–100 CommandResponse Position for Solace Pro.
pattern_v2_setup(actions) actions: list of {ts, pos} CommandResponse PatternV2 Setup.
pattern_v2_play(toy_id=None, start_time=None, offset_time=None, time_ms=None) CommandResponse PatternV2 Play.
pattern_v2_init_play(actions, toy_id=None, ...) CommandResponse PatternV2 Setup + Play.
pattern_v2_stop(toy_id=None) CommandResponse PatternV2 Stop.
pattern_v2_sync_time() CommandResponse PatternV2 SyncTime.
send_command(command_data, timeout=None) Raw command dict dict Low-level; returns raw dict. Raises LovenseError on failures.
decode_response(response) Response dict str Human-readable response string.

Example:

import time

with client.play({Actions.VIBRATE: 10}, time=5, toy_id="T123"):
    time.sleep(5)

ServerClient

Standard API Server client. Sends commands via Lovense cloud. Requires developer token and uid from QR pairing.

Constructor

ServerClient(
    developer_token: str,
    uid: str,
    timeout: float = 10.0,
)
Parameter Type Description
developer_token str From Lovense Developer Dashboard
uid str User ID from QR pairing callback
timeout float Request timeout

Methods

Same command methods as LANClient: function_request, stop, pattern_request, preset_request, send_command, decode_response. Note: Server pattern_request uses (rule, strength, time, toy_id) — different signature from LAN.


AsyncServerClient

Async version of the Standard API Server client for server-side bots.

Lifecycle and resource management

AsyncServerClient is an async client and should be closed when you stop the process:

from lovensepy import AsyncServerClient, Actions

async def run_once():
    async with AsyncServerClient("YOUR_DEV_TOKEN", "USER_UID") as client:
        await client.function_request({Actions.VIBRATE: 10}, time=2)

If you don't use async with, call await client.aclose() explicitly.

Per-request timeout overrides

High-level async methods accept timeout to override the client default for that single call.


AsyncLANClient

Async version of LAN client for local applications (runs on the same network as the Lovense device). If you're building a production bot that runs on your server, prefer AsyncServerClient or SocketAPIClient instead.

Lifecycle and resource management

AsyncLANClient reuses HTTP sessions for better throughput. Close it when done:

from lovensepy import AsyncLANClient, Actions

async def run_once():
    async with AsyncLANClient("MyBot", "192.168.1.100", port=20011) as client:
        await client.function_request({Actions.VIBRATE: 10}, time=2)

If you do not use async with, call await client.aclose() explicitly.

Per-request timeout overrides

All high-level async methods accept timeout to override client default timeout for that call:

toys = await client.get_toys(timeout=2.0)  # quick call
await client.pattern_request([5, 10, 15, 20], time=20, timeout=15.0)  # longer call

Concurrency safety

For HTTPS with verify_ssl=False, certificate fingerprint verification is guarded internally to avoid duplicate concurrent checks when many commands hit the same endpoint at once.


Server-side multi-session bot pattern (Discord/Telegram)

When your bot runs on a server, you typically use:

  • AsyncServerClient (Standard API Server): cloud HTTP requests (token + uid)
  • SocketAPIClient (Socket API): cloud WebSocket + event loop

Key idea: your backend must authenticate the incoming request, then resolve the correct Lovense session from your own stored mapping (e.g. in a database). Never accept a Lovense uid (or socket auth token) directly from the user request.

This avoids:

  • data conflicts (shared mutable objects across users)
  • accidental session mix-ups (sending commands to someone else’s uid)
  • security issues (exposing or trusting client-provided session identifiers)
import asyncio
from lovensepy import AsyncServerClient, Actions


class ServerSessionPool:
    """
    Keeps per-user clients in memory.

    user_id: your app user ID (Discord/Telegram).
    lovense_uid: stored in your DB after QR pairing / OAuth-like flow.
    """

    def __init__(self):
        self._clients: dict[str, AsyncServerClient] = {}
        self._lock = asyncio.Lock()

    async def get_or_create(self, user_id: str, *, lovense_uid: str) -> AsyncServerClient:
        async with self._lock:
            client = self._clients.get(user_id)
            if client is None:
                client = AsyncServerClient(
                    developer_token="YOUR_DEV_TOKEN",
                    uid=lovense_uid,
                    timeout=5.0,
                )
                self._clients[user_id] = client
            return client

    async def close_session(self, user_id: str) -> None:
        async with self._lock:
            client = self._clients.pop(user_id, None)
        if client is not None:
            await client.aclose()

    async def shutdown(self) -> None:
        async with self._lock:
            clients = list(self._clients.values())
            self._clients.clear()
        await asyncio.gather(*(c.aclose() for c in clients), return_exceptions=True)


sessions = ServerSessionPool()


async def handle_vibrate(user_id: str, level: int) -> None:
    # 1) Authenticate request on your server (Discord/Telegram auth).
    # 2) Look up lovense_uid for this authenticated user from your DB.
    lovense_uid = "LOOKED_UP_FROM_YOUR_DB"

    # 3) Resolve the correct per-user client.
    client = await sessions.get_or_create(user_id, lovense_uid=lovense_uid)

    # 4) Use per-request timeout if needed.
    await client.function_request({Actions.VIBRATE: level}, time=2, timeout=3.0)

Scaling notes (server-side):

  • Use one shared event loop and non-blocking handlers (await everywhere).
  • Reuse clients per user/session; avoid creating them per command.
  • Put an upper bound on concurrency (e.g. asyncio.Semaphore) if a single user or a spike of users can spam commands.
  • Add idle cleanup (TTL) so inactive sessions are closed automatically.
  • For very large loads, shard bot workers/processes and keep per-process session maps.
  • If you use SocketAPIClient, create and keep one WebSocket client per Lovense user session (per ws_url/auth token), and route commands through the correct per-session instance just like above.

SocketAPIClient

Async WebSocket client for Socket API. Commands via WebSocket (or LAN HTTPS when use_local_commands=True).

Constructor

SocketAPIClient(
    ws_url: str,
    *,
    use_local_commands: bool = False,
    app_name: str = "lovensepy",
    raise_on_disconnect: bool = False,
    on_socket_open: Callable | None = None,
    on_socket_close: Callable | None = None,
    on_socket_error: Callable[[Exception], ...] | None = None,
    on_socket_io_connected: Callable | None = None,
    on_event: Callable[[str, Any], ...] | None = None,
)
Parameter Type Description
ws_url str WebSocket URL from build_websocket_url
use_local_commands bool Send commands via LAN HTTPS when device on same network
app_name str App name for local commands
raise_on_disconnect bool Raise ConnectionError when sending while disconnected
on_socket_open, on_socket_close, on_socket_error Callable Connection lifecycle callbacks
on_socket_io_connected Callable Fired when Socket.IO handshake complete
on_event Callable Fired for each Socket.IO event (event_name, payload)

Methods

Method Description
connect() Async. Connect and start background ping/recv tasks (non-blocking).
run_forever() Async. Connect and block until disconnected.
start_background(auto_reconnect=False, retry_delay=5.0) Start run_forever (or reconnect loop) as a task.
connect_with_retry(retry_delay=5.0, max_retries=None) Reconnect loop for 24/7 bots.
wait_closed() Wait until current connection fully closes.
disconnect() Close connection.
send_command(command, action, time_sec=0, toy=None, ...) Send command (non-blocking).
send_command_await(command, action, ...) Send command and await delivery. Use for stops.
send_event(event, payload=None) Send raw Socket.IO event.
on(event_name) Decorator to register per-event handlers.
add_event_handler(event_name, handler) Register per-event handler programmatically.

Event routing example:

@client.on("basicapi_update_device_info_tc")
async def on_device_info(payload):
    print("Device info:", payload)

Properties

Property Type Description
is_socket_io_connected bool True when Socket.IO handshake done and ready for commands
is_using_local_commands bool True when commands go via LAN HTTPS

ToyEventsClient

Async WebSocket client for Toy Events API. Receives real-time events from toys. Lovense Remote only, port 20011.

Constructor

ToyEventsClient(
    ip: str,
    port: int = 20011,
    use_https: bool = False,
    https_port: int = 30011,
    app_name: str = "lovensepy",
    *,
    on_open: Callable | None = None,
    on_close: Callable | None = None,
    on_error: Callable[[Exception], ...] | None = None,
    on_event: Callable[[str, Any], ...] | None = None,
)

Methods and Properties

Method/Property Description
connect() Async. Connect, request access, receive events until disconnected.
disconnect() Close connection.
is_connected True if WebSocket connected.
is_access_granted True when user granted access in Lovense Remote.

Pattern Players

High-level API for sine waves and combo patterns.

SyncPatternPlayer

For use with LANClient. Synchronous.

SyncPatternPlayer(client: LANClient, toys: dict[str, dict] | GetToysResponse)
Method Parameters Description
play_sine_wave(toy_id, feature, duration_sec=5, num_steps=100, stop_prev_first=True) feature: e.g. "Vibrate1" Play sine wave on one feature.
play_combo(targets, duration_sec=4, num_steps=100) targets: [(toy_id, feature), ...] Play combo with random phases.
stop(toy_id) Stop toy.
features(toy_id) Get features for toy.

Example:

player = SyncPatternPlayer(client, toys)
player.play_sine_wave("T123", "Vibrate1", duration_sec=5)
player.play_combo([("T1", "Vibrate1"), ("T2", "Vibrate")], duration_sec=4)
player.stop("T123")

AsyncPatternPlayer

For use with SocketAPIClient. Same methods, async (use await).

player = AsyncPatternPlayer(client, toys)
await player.play_sine_wave("T123", "Vibrate1", duration_sec=5)
await player.stop("T123")

Utilities

Function Parameters Returns Description
get_token(developer_token, uid, uname=None, utoken=None, timeout=10) str Get auth token for Socket API. Raises on error.
get_socket_url(auth_token, platform, timeout=10) platform: Website Name from Dashboard dict Get socket info dict.
build_websocket_url(socket_info, auth_token) str Build full wss:// URL.
get_qr_code(developer_token, uid, uname=None, utoken=None, timeout=10) dict Get QR for Server API. Returns {qr, code}. See security note in docstring.
features_for_toy(toy) toy: dict from GetToys list[str] Get features (e.g. ["Vibrate1", "Rotate"]).
stop_actions(toy) toy: dict dict Build {Vibrate1: 0, ...} to stop.

Appendix

Actions and Presets

Actions (function types)

Action Range Toys
Actions.VIBRATE 0–20 Most
Actions.VIBRATE1, VIBRATE2, VIBRATE3 0–20 Edge, Diamo, multi-motor
Actions.ROTATE 0–20 Nora, Max, etc.
Actions.PUMP 0–3 Max 2
Actions.THRUSTING 0–20 Solace, Mission
Actions.FINGERING 0–20 Solace
Actions.SUCTION 0–20 Max 2
Actions.DEPTH 0–3 Solace Pro
Actions.STROKE 0–100 Solace Pro
Actions.OSCILLATE 0–20 Some toys
Actions.ALL 0–20 All motors at once
Actions.STOP Stop

Usage:

client.function_request({Actions.VIBRATE: 10}, time=5)
client.function_request({Actions.VIBRATE1: 5, Actions.VIBRATE2: 10}, time=3)

Presets (built-in patterns)

Preset Description
Presets.PULSE Pulse pattern
Presets.WAVE Wave pattern
Presets.FIREWORKS Fireworks pattern
Presets.EARTHQUAKE Earthquake pattern

Usage:

client.preset_request(Presets.PULSE, time=5)

Toy Events Event Types

Event When
toy-list Toys added/removed/enabled
toy-status Toy connected/disconnected
button-down, button-up, button-pressed User pressed toy button
function-strength-changed User changed level in app
shake, shake-frequency-changed Shake sensor
battery-changed, depth-changed, motion-changed Sensor updates
event-closed Game mode disabled
access-granted User granted access (internal)
pong Ping response (internal)

Lovense Flow Diagrams

The following sequence diagrams illustrate the flows described in the Lovense developer documentation.

Server API — QR pairing flow:

sequenceDiagram
    participant User as Your User
    participant App as Your App
    participant Server as Your Server
    participant Lovense as Lovense Server
    participant Remote as Lovense Remote App
    participant Toy as Lovense Toy

    User->>Remote: Open Lovense Remote
    User->>Toy: Turn on the toy
    User->>App: User logs in to your app
    App->>Server: Request to bind with Lovense Toy
    Server->>Lovense: Request QR code from Lovense
    Lovense-->>Server: Return a QR code URL
    Server->>App: Display the QR code
    User->>Remote: User scans the QR code with Lovense Remote App
    Remote->>Server: Lovense Remote app POSTs to your server
    App->>Remote: Control the toy by instructing the App
    Remote->>Toy: Trigger vibration

Socket API — authorization and connection flow:

sequenceDiagram
    participant User as User (Lovense App)
    participant Interface as Developer Interface
    participant DevServer as Developer Server
    participant Lovense as Lovense Server

    Interface->>Lovense: 1) Application for authorization token
    Lovense-->>Interface: Response authorization token
    Interface->>DevServer: Forward authorization token
    DevServer->>Lovense: 2) Validate authorization token
    Lovense-->>DevServer: Verification success, response socket connection info
    DevServer->>Lovense: Establishing socket connection
    DevServer->>Lovense: Get QR code information by socket
    User->>Interface: Start Lovense App, connect toys and scan the QR code
    Lovense->>User: 3) Report device information periodically
    Note over Lovense,User: Device info contains toy list, domain and port of local HTTP service
    Lovense->>DevServer: Synchronizing device information
    Interface->>User: Show toys and send command

Architecture

  • Clients: LANClient, ServerClient, SocketAPIClient, ToyEventsClient — command building, protocols
  • Transport: HttpTransport (POST JSON), WsTransport (WebSocket)
  • Security: Certificate fingerprint verification for HTTPS (port 30011/30011) when verify_ssl=False

HTTPS Certificate

For local HTTPS (ports 30011/30011), lovensepy verifies the Lovense certificate fingerprint instead of disabling SSL. Fingerprint in lovensepy.security.LOVENSE_HTTPS_FINGERPRINT.


Examples

File Description
examples/lan_game_mode.py LAN Game Mode — get toys, presets, functions, patterns
examples/patterns_demo.py Sine waves and combos with SyncPatternPlayer
examples/server_api.py Server API with token and uid
examples/socket_api_full.py Socket API with QR flow and command sending
examples/toy_events_full.py Toy Events — receive real-time events

Run with env vars, e.g. LOVENSE_LAN_IP=192.168.1.100 python examples/lan_game_mode.py


Tests

Install

pip install -e ".[dev]"

Unit tests (no devices)

pytest tests/test_unit.py -v

Integration tests

Integration tests require Lovense hardware and/or a developer token. Set environment variables for the test mode you use, then run the corresponding test file.

Test modes and required env vars:

Test file Mode Required env vars
test_standard_local.py Standard / local LOVENSE_LAN_IP, LOVENSE_LAN_PORT (20011 Remote, 34567 Connect)
test_standard_server.py Standard / server LOVENSE_DEV_TOKEN, LOVENSE_UID — or LOVENSE_QR_PAIRING=1 + ngrok
test_socket_server.py Socket / server LOVENSE_DEV_TOKEN, LOVENSE_UID, LOVENSE_PLATFORM
test_socket_server.py::test_by_local Socket / local Same as server + device on same LAN
test_socket_local.py Socket / local only LOVENSE_LAN_IP, LOVENSE_LAN_PORT
test_toy_events.py Toy Events LOVENSE_LAN_IP, LOVENSE_TOY_EVENTS_PORT (20011)

Example env setup:

export LOVENSE_LAN_IP=192.168.1.100
export LOVENSE_LAN_PORT=34567          # Lovense Connect
export LOVENSE_DEV_TOKEN=your_token
export LOVENSE_UID=your_uid
export LOVENSE_PLATFORM="Your App"
export LOVENSE_TOY_EVENTS_PORT=20011   # Toy Events (Lovense Remote only)
export LOVENSE_QR_PAIRING=1
export LOVENSE_CALLBACK_PORT=8765      # ngrok or cloudflared

Run integration tests:

pytest tests/test_standard_local.py -v -s
pytest tests/test_standard_server.py -v -s
pytest tests/test_socket_server.py -v -s
pytest tests/test_socket_local.py -v -s
pytest tests/test_toy_events.py -v -s

Links


License

Apache License 2.0 — see LICENSE for full text.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lovensepy-1.0.5.tar.gz (70.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lovensepy-1.0.5-py3-none-any.whl (53.8 kB view details)

Uploaded Python 3

File details

Details for the file lovensepy-1.0.5.tar.gz.

File metadata

  • Download URL: lovensepy-1.0.5.tar.gz
  • Upload date:
  • Size: 70.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for lovensepy-1.0.5.tar.gz
Algorithm Hash digest
SHA256 022e174682d3eed486997d3b3bb218581de1cf88360d3e1a146266f5241b4d37
MD5 ca85607e434094c7a7b0569fc89bc3b1
BLAKE2b-256 3f6f07d4deeb0341e066f979566bb276cfb988ca112d798e246cebdccddf2856

See more details on using hashes here.

Provenance

The following attestation bundles were made for lovensepy-1.0.5.tar.gz:

Publisher: publish-pypi.yml on koval01/lovensepy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lovensepy-1.0.5-py3-none-any.whl.

File metadata

  • Download URL: lovensepy-1.0.5-py3-none-any.whl
  • Upload date:
  • Size: 53.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for lovensepy-1.0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 3449cc7e3ad4bd55b3e5312b3b3fbf141490a5cda36cfa2cf83dd325907b1033
MD5 3c26de141ef06fccbdba5a8b79fbcfb5
BLAKE2b-256 fbdc82ba935eb1d978bba11aa295833629307ae08379b474470676484ef92846

See more details on using hashes here.

Provenance

The following attestation bundles were made for lovensepy-1.0.5-py3-none-any.whl:

Publisher: publish-pypi.yml on koval01/lovensepy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page