Finally got Lovense Connect under control via python on a PC

This took so much trial and error - and packet sniffing - and help from AI - to figure it out:

You need lovense connect 1.9.0. Run it on your local PC - connect your toys.

Below is a simple program in python to send commands (via a simple UI) - I am sharing it here because I couldn’t find a simple solution like this - I’m no coder, so if this doesn’t work for you then sorry, but I suggest using Grok 4 or something like that to try and get it working for you - but this works for me!

import tkinter as tk
from tkinter import ttk
import requests
import json
import threading

API_BASE = “http://127.0.0.1:20010/command
HEADERS = {
“Content-Type”: “application/json”,
“X-platform”: “MyTestApp”
}

def get_toys():
try:
res = requests.post(API_BASE, json={“command”: “GetToys”}, headers=HEADERS)
print(“:magnifying_glass_tilted_left: Raw response text:”, res.text)
res_json = res.json()
toys_json_str = res_json.get(“data”, {}).get(“toys”, “{}”)
toys_data = json.loads(toys_json_str)
return {info.get(“nickName”, toy_id): toy_id for toy_id, info in toys_data.items()}
except Exception as e:
print(f":cross_mark: Error in get_toys(): {e}")
return {}

def vibrate(toy_id, level=10, seconds=5):
body = {
“command”: “Function”,
“action”: f"{control_mode.get()}:{level}“,
“timeSec”: seconds,
“apiVer”: 1,
“toy”: toy_id
}
response = requests.post(API_BASE, json=body, headers=HEADERS)
print(f":wrench: Payload sent: {body}”)
print(f":inbox_tray: Response: {response.status_code} {response.text}")
return response.json()

def get_battery(toy_id):
body = {“command”: “GetBattery”, “toy”: toy_id, “apiVer”: 1}
response = requests.post(API_BASE, json=body, headers=HEADERS)
print(f":battery: Battery response: {response.text}")
return response.json().get(“data”, “Unknown”)

def send_command():
selected = toy_selector.get()
toy_id = toy_map.get(selected)
level = int(vibration_level.get())
seconds = int(duration.get())
if toy_id:
result = vibrate(toy_id, level, seconds)
status.set(f":check_mark: Sent: {result.get(‘code’, ‘OK’)} - {result.get(‘message’, ‘Success’)}")
else:
status.set(“:cross_mark: No toy selected”)

def stop_vibrate():
selected = toy_selector.get()
toy_id = toy_map.get(selected)
if toy_id:
result = vibrate(toy_id, 0, 0)
status.set(f":raised_hand: Stopped: {result.get(‘code’, ‘OK’)} - {result.get(‘message’, ‘Success’)}")
else:
status.set(“:cross_mark: No toy selected”)

def refresh():
global toy_map
toy_map = get_toys()
toy_selector[‘values’] = list(toy_map.keys())
if toy_map:
toy_selector.current(0)
status.set(“:check_mark: Toys loaded”)
else:
status.set(“:cross_mark: No toys found”)

def poll_battery():
while True:
selected = toy_selector.get()
toy_id = toy_map.get(selected)
if toy_id:
batt = get_battery(toy_id)
current_status = status.get().split(" | Battery:“)[0] if " | Battery:” in status.get() else status.get()
status.set(f"{current_status} | Battery: {batt}")
time.sleep(10) # Poll every 10 seconds

root = tk.Tk()
root.title(“Lovense Toy Controller”)

ttk.Label(root, text=“Select Toy:”).grid(row=0, column=0, sticky=“e”)
toy_selector = ttk.Combobox(root, width=30)
toy_selector.grid(row=0, column=1, padx=5, pady=5)

ttk.Label(root, text=“Control Mode:”).grid(row=1, column=0, sticky=“e”)
control_mode = tk.StringVar(value=“Vibrate”)
ttk.Combobox(root, textvariable=control_mode, values=[“Vibrate”, “Rotate”]).grid(row=1, column=1, padx=5, pady=5)

ttk.Label(root, text=“Level (1-20):”).grid(row=2, column=0, sticky=“e”)
vibration_level = tk.Spinbox(root, from_=1, to=20, width=5)
vibration_level.grid(row=2, column=1, sticky=“w”, padx=5)

ttk.Label(root, text=“Duration (s):”).grid(row=3, column=0, sticky=“e”)
duration = tk.Spinbox(root, from_=1, to=60, width=5)
duration.grid(row=3, column=1, sticky=“w”, padx=5)

ttk.Button(root, text=“Send Command”, command=send_command).grid(row=4, column=0, pady=10)
ttk.Button(root, text=“Stop”, command=stop_vibrate).grid(row=4, column=1, pady=10)

ttk.Button(root, text=“Refresh Toys”, command=refresh).grid(row=5, column=0, columnspan=2, pady=5)

status = tk.StringVar()
ttk.Label(root, textvariable=status).grid(row=6, column=0, columnspan=2)

toy_map = {}
refresh()

threading.Thread(target=poll_battery, daemon=True).start()

root.mainloop()