tikal.low_level.brands.lovense package

Submodules

tikal.low_level.brands.lovense.data module

Lovense-specific toy data: model capabilities and per-model recommendations.

This module is the single place to edit when adding or adjusting a Lovense model. It defines: - LOVENSE_TOY_NAMES: model name -> ToyCommands (capability/command mapping) - ROTATION_TOY_NAMES: models that support changing the rotation direction - MIN_SEGMENT_LENGTH: suggested minimum interval between intensity changes (ms)

The brand-agnostic data structures these build on (ToyData, ToyCommands and the validation exceptions) live in tikal.low_level.toy_data.

tikal.low_level.brands.lovense.data.LOVENSE_TOY_NAMES = {'Ambi': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name=None, intensity2_command=None), 'Calor': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name=None, intensity2_command=None), 'Diamo': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name=None, intensity2_command=None), 'Dolce': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name=None, intensity2_command=None), 'Domi': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name=None, intensity2_command=None), 'Edge': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name=None, intensity2_command=None), 'Exomoon': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name=None, intensity2_command=None), 'Ferri': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name=None, intensity2_command=None), 'Flexer': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name='Fingering', intensity2_command='Finger'), 'Gemini': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name=None, intensity2_command=None), 'Gravity': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name='Thrust', intensity2_command='Thrusting'), 'Gush': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name=None, intensity2_command=None), 'Hush': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name=None, intensity2_command=None), 'Hyphy': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name=None, intensity2_command=None), 'Lapis': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name=None, intensity2_command=None), 'Lush': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name=None, intensity2_command=None), 'Lush Anal': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name=None, intensity2_command=None), 'Max': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name='Air', intensity2_command='Air:Level'), 'Mission': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name=None, intensity2_command=None), 'Nora': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name='Rotation', intensity2_command='Rotate'), 'Osci': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name='Oscillation', intensity2_command='Oscillate'), 'Ridge': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name='Rotation', intensity2_command='Rotate'), 'Sex Machine': ToyCommands(intensity1_name='Thrust', intensity1_command='Thrusting', intensity2_name='Depth', intensity2_command='Depth'), 'Solace': ToyCommands(intensity1_name='Thrust', intensity1_command='Thrusting', intensity2_name='Depth', intensity2_command='Depth'), 'Spinel': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name='Thrust', intensity2_command='Thrusting'), 'Tenera': ToyCommands(intensity1_name='Sucking', intensity1_command='Suck', intensity2_name=None, intensity2_command=None), 'Vulse': ToyCommands(intensity1_name='Vibration', intensity1_command='Vibrate', intensity2_name=None, intensity2_command=None)}

Mapping of Lovense toy model names to their command configurations.

This dictionary defines all supported Lovense toy models and their capabilities. Keys are model names, values are ToyCommands objects specifying what commands each toy supports.

Models of different versions are treated the same (e.g., Lush 1, Lush 2, and Lush 3 all use the “Lush” key). Some commands are uncertain and assumed based on similar toys. Please notify me if some commands don’t work.

Type:

dict[str, ToyCommands]

Example

# Check capabilities
commands = LOVENSE_TOY_NAMES["Nora"]
print(f"{commands.intensity1_name}: {commands.intensity1_command}")
if commands.intensity2_name:
    print(f"{commands.intensity2_name}: {commands.intensity2_command}")
tikal.low_level.brands.lovense.data.MIN_SEGMENT_LENGTH = {'Ambi': 200, 'Calor': 200, 'Diamo': 200, 'Dolce': 200, 'Domi': 200, 'Edge': 200, 'Exomoon': 200, 'Ferri': 200, 'Flexer': 200, 'Gemini': 200, 'Gravity': 200, 'Gush': 200, 'Hush': 200, 'Hyphy': 200, 'Lapis': 200, 'Lush': 200, 'Lush Anal': 200, 'Max': 200, 'Mission': 200, 'Nora': 200, 'Osci': 200, 'Ridge': 200, 'Sex Machine': 800, 'Solace': 800, 'Spinel': 200, 'Tenera': 400, 'Vulse': 200}

Maps toys to my suggested minimum segment length, meaning the minimum interval between intensity changes (In milliseconds)

Especially useful if you want to implement any pattern playback-related functionality. This is just a suggestion. You’re free to use whatever you want.

Type:

dict[str, int]

Example

print(MIN_SEGMENT_LENGTH["Nora"])  # 200
tikal.low_level.brands.lovense.data.ROTATION_TOY_NAMES = ['Nora', 'Ridge']

List of toys (by model_name) that support rotation direction changes.

Toys in this list can use the rotate_change_direction() method to toggle their rotation direction.

Type:

list[str]

Example

if toy.model_name in ROTATION_TOY_NAMES:
    await toy.rotate_change_direction()

tikal.low_level.brands.lovense.handler module

Part of the Low-Level API: the Lovense BLEBrandHandler implementation.

Encapsulates all Lovense-specific BLE logic: identification (LVS- name prefix), UUID resolution, connection, and notification setup. Instantiated by BLEConnectionBuilder via the brand registry; you are not meant to use it directly.

class tikal.low_level.brands.lovense.handler.LovenseHandler(on_disconnect: ~typing.Callable[[str], ~typing.Any], on_power_off: ~typing.Callable[[str], ~typing.Any], logger_name: str = 'tikal', client_class: ~typing.Type[~bleak.BleakClient] = <class 'bleak.BleakClient'>)[source]

Bases: BLEBrandHandler

Brand handler for Lovense BLE toys.

Encapsulates all Lovense-specific logic: identification, UUID resolution, connection, and notification setup. The common construction signature (callbacks, logger, client class) is inherited from BLEBrandHandler.

async create_toy(toy_data: ToyData, device: BLEDevice) Toy[source]

Connect to a single Lovense toy.

Parameters:
  • toy_dataLovenseData object representing the toy to connect to.

  • deviceBLEDevice object representing the toy to connect to.

Returns:

A connected, ready to use LovenseToy instance.

Raises:
  • ValidationError – The toy_data contains an invalid model name.

  • ConnectionError – The BLE connection, UUID resolution, or notification setup failed.

static create_toy_data(device: BLEDevice) ToyData[source]

Create a LovenseData instance (inherits from ToyData) from a BLE device representing a Lovense toy.

static handles_device(device: BLEDevice) bool[source]

Return True if the BLE device is a lovense toy. Lovense toys advertise with a name prefixed ‘LVS-’

static handles_toy(toy_data: ToyData) bool[source]

Return True if the given ToyData represents a Lovense toy.

tikal.low_level.brands.lovense.toy module

Part of the Low-Level API: the Lovense concrete Toy implementation.

Implements the Lovense-specific BLE protocol: command formatting, response parsing, and Lovense-specific notifications (like power-off events). You are not meant to instantiate LovenseToy directly. :class: BLEConnectionBuilder establishes connections to toys and returns ready-to-use instances.

class tikal.low_level.brands.lovense.toy.LovenseToy(transport: BleTransport, model_name: str, on_power_off: Callable[[str], Any], logger_name: str)[source]

Bases: Toy

Low-level representation of a Lovense BLE toy.

Implements the Lovense-specific protocol for communication with Lovense toys over Bluetooth Low Energy. Handles command formatting, response parsing, and Lovense-specific notifications (like power-off events). You are not meant to instantiate these classes directly. BLEConnectionBuilder establishes connections to toys and returns instances of LovenseToy

Parameters:
  • transport – Transport layer handling the BLE communication.

  • model_name – Model name (e.g., “Nora”, “Lush”). Must be a key in LOVENSE_TOY_NAMES.

  • on_power_off – Callback invoked when the user powers off the toy via the physical power button. Receives the toy’s Bluetooth address as a string argument.

  • logger_name – Name of the logger to use. Use empty string for root logger.

Example:

# Set intensity of the primary capability to maximum
await toy.intensity1(20)

# Send a custom command
response = await toy.direct_command("DeviceType")
print(f"Device info: {response}")
property brand: str

Returns the brand of the toy. Always “Lovense” for this class

async change_rotation_direction() bool[source]

Change rotation direction for toys with rotation capability.

This method only affects toys that support rotation (Nora and Ridge). For other toys, it returns True immediately without sending any command.

Returns:

True if the toy acknowledged the command or if rotation is not supported, False if the command failed.

Example:

await toy.rotate_change_direction()  # Toggle rotation direction
property change_rotation_direction_available: bool

Check if the toy supports changing the rotation direction.

Returns:

True if the rotation direction can be changed, False otherwise.

Return type:

bool

Example:

if toy.change_rotation_direction_available:
    await toy.change_rotation_direction()
async direct_command(command: str, timeout: float = 3.0) str | None[source]

Send any command directly to the toy.

This method allows sending commands that are not implemented by the library.

Parameters:
  • command – Command string in UTF-8 format. A semicolon terminator will be added if not present.

  • timeout – Response timeout in seconds. Defaults to 3.0.

Returns:

Response string from the toy (with semicolon stripped), or None if timeout or error occurred.

Example:

# Query firmware version
response = await toy.direct_command("DeviceType")
# Response format: "C:11:0082059AD3BD"
# (C = device type, 11 = firmware version, address)
async disconnect() None[source]

Disconnect from the device.

Stops all toy actions, disables notifications, and closes the BLE connection. This is regarded as intentional disconnect and does not trigger an on_disconnect callback even if an on_disconnect callback was set (either during initialization or via set_on_disconnect). The method does not raise any exceptions.

Note

After calling this method, the toy object is unusable. To connect again, you will need to re-scan and connect using the ConnectionBuilder. Use the newly provided Lovense object by the ConnectionBuilder.

async get_batch_number() str | None[source]

Retrieve the production batch number.

The batch number appears to be in YYMMDD format, indicating the manufacturing date.

Returns:

Batch number string (e.g., “241015” for October 15, 2024), or None if an error occurred.

Example:

batch = await toy.get_batch_number()
if batch:
    print(f"Manufactured: 20{batch[:2]}/{batch[2:4]}/{batch[4:6]}")
async get_battery_level() int | None[source]

Retrieve the battery level as a percentage.

Returns:

Battery level (0-100%), or None if the command failed or timed out.

Example:

battery = await toy.get_battery_level()
if battery is not None:
    if battery < 20:
        print(f"Low battery: {battery}%")
    else:
        print(f"Battery: {battery}%")

Note

Lovense toys have a quirk where they prefix ‘s’ before the value if the toy was recently reconnected. This method strips the ‘s’ when present

async get_device_type() str | None[source]

Retrieve device type and firmware information.

Returns:

FirmwareVersion:Address” (e.g., “C:11:0082059AD3BD”), or None if an error occurred.

Return type:

String in format “DeviceType

Example:

info = await toy.get_device_type()
if info:
    parts = info.split(":")
    device_type = parts[0]
    firmware = parts[1]
    print(f"Device type: {device_type}, Firmware: {firmware}")
async get_status() int | None[source]

Retrieve the status code of the toy.

Returns:

Status code (2 = Normal operation), or None if an error occurred.

Example:

status = await toy.get_status()
if status == 2:
    print("Toy is operating normally")
else
    print(f"Unusual status code: {status}")
async intensity1(level: int) bool[source]

Set the primary capability to the specified level.

The primary capability depends on the toy model (e.g., vibration for Gush, thrusting for Solace)

Parameters:

level – Intensity level (0-20). Values outside this range are clamped.

Returns:

True if the toy acknowledged the command, False otherwise.

Example:

await toy.intensity1(20)  # Set primary capability to maximum
async intensity2(level: int) bool[source]

Set the secondary capability to the specified level.

The secondary capability depends on the toy model (e.g., Rotation for Nora, depth control for Solace). Not all toys have a secondary capability. Returns true immediately and does not send a command if the toy has no secondary capability.

Parameters:

level – Intensity level (0-20). Values outside the valid range are clamped.

Returns:

True if the toy acknowledged the command or if no secondary capability exists, False otherwise

Example:

await toy.intensity2(10)  # Set secondary capability to medium intensity

Note

For Max’s air pump, the level is automatically divided by 4 to convert from 0-20 scale to 0-5 scale.

property intensity_names: tuple[str, str | None]

Get display names for Lovense toy capabilities.

Returns:

(primary_name, secondary_name). Secondary name is None if the toy has only one capability.

Return type:

tuple[str, str | None]

Example:

names = toy.intensity_names
print(f"{names[0]}: intensity1")  # "Vibration: intensity1"
if names[1]:
    print(f"{names[1]}: intensity2")  # "Rotation: intensity2"
property max_intensity: int

Get maximum intensity value for Lovense toys.

Returns:

Always 20 for Lovense toys.

Return type:

int

Note

Some capabilities (like Max’s air pump) use different ranges. Those are automatically scaled for you.

async power_off() bool[source]

Turn off power to the toy.

This sends the PowerOff command which turns off the toy. The toy can then only be turned back on via the physical power button.

Returns:

True if successful, False otherwise.

Example:

await toy.power_off()
property recommended_min_interval: int

Get the recommended minimum interval between intensity changes (milliseconds).

Returns:

The Lovense per-model suggested minimum segment length.

Return type:

int

async reconnect() bool[source]

Attempts to reconnect to the toy to after an unintentional disconnect.

Does nothing if already connected. Use only after an unintended disconnect (ConnectionError). If you disconnected via disconnect, use the ConnectionBuilder instead.

Returns:

True if the reconnection was successful, False otherwise.

async set_model_name(model_name: str) None[source]

Set the model name of the toy.

Parameters:

model_name – New model name. Must be in LOVENSE_TOY_NAMES.keys() of module ToyData. Case Insensitive. Case Insensitive.

Raises:
  • InvalidModelError – If model_name is not valid for this toy brand.

  • BadModelError – If the model_name is valid, but commands still fail. See BadModelError for details.

Example:

# Update model name in case it was set incorrectly while building the connection via the ConnectionBuilder
toy.set_model_name("Nora")
async start_notifications() None[source]

Start listening for messages from the Lovense toy.

This method is called by the ConnectionBuilder during connection setup and should not be called manually.

Raises:

ConnectionError – Failed to start notifications.

async stop() bool[source]

Stop all toy actions by setting all intensities to zero.

This method is a shortcut for intensity1(0) and intensity2(0)

Returns:

True if both commands succeeded, False if either failed.

Example:

await toy.stop()
async strict_change_rotation_direction() bool[source]

Similar to :meth: rotate_change_direction, but this method raises an exception if the command fails.

Raises:
  • UnexpectedToyResponse – The toys’ response was unexpected, e.g. “ERROR” instead of “OK”.

  • ConnectionError – Command could not be sent, or the toy did not respond within an appropriate timeout.

Returns:

True if the toy supports rotation, False otherwise. Does not raise if the toy does not support rotation.

async strict_direct_command(command: str, timeout: float = 3.0) str[source]

Similar to :meth: direct_command, but this method raises an exception if the command fails.

Raises:

ConnectionError – Command could not be sent, or the toy did not respond within the provided timeout.

Returns:

response string from the toy

async strict_disconnect() None[source]

Similar to :meth: disconnect, but this methode does raise an exception if the disconnect fails. The exception is only for logging. The toy is still disconnected in the error case.

Raises:

ConnectionError – Command could not be sent, or the toy did not respond within an appropriate timeout.

async strict_get_batch_number() str[source]

Similar to :meth: get_batch_number, but this method raises an exception if the command fails.

Raises:
  • ConnectionError – Command could not be sent, or the toy did not respond within an appropriate timeout.

  • UnexpectedToyResponse – The toys’ response was unexpected.

Returns:

Batch number string (e.g., “241015” for October 15, 2024)

async strict_get_battery_level() int[source]

Similar to :meth: get_battery_level, but this method raises an exception if the command fails.

Raises:
  • UnexpectedToyResponse – The toys’ response was unexpected.

  • ConnectionError – Command could not be sent, or the toy did not respond within an appropriate timeout.

Returns:

Battery level as a percentage (0-100).

async strict_get_device_type() str[source]

Similar to :meth: get_device_type, but this method raises an exception if the command fails.

Raises:

ConnectionError – Command could not be sent, or the toy did not respond within the provided timeout.

Returns:

FirmwareVersion:Address” (e.g., “C:11:0082059AD3BD”)

Return type:

String in format “DeviceType

Note

does not raise UnexpectedToyResponse as the response is not verified.

async strict_get_status() int[source]

Similar to :meth: get_status, but this method raises an exception if the command fails.

Raises:
  • ConnectionError – Command could not be sent, or the toy did not respond within the provided timeout.

  • UnexpectedToyResponse – The toys’ response was unexpected, in this case any string containing non-digit characters.

Returns:

Status code (2 = Normal operation)

async strict_intensity1(level: int) bool[source]

Similar to :meth: intensity1, but this method raises an exception if the intensity command fails.

Raises:
  • ConnectionError – Command could not be sent, or the toy did not respond within an appropriate timeout.

  • UnexpectedToyResponse – The toys’ response was unexpected, e.g. “ERROR” instead of “OK”.

Returns:

Always true

async strict_intensity2(level: int) bool[source]

Similar to :meth: intensity2, but this method raises an exception if the intensity command fails.

Raises:
  • ConnectionError – Command could not be sent, or the toy did not respond within an appropriate timeout.

  • UnexpectedToyResponse – The toys’ response was unexpected, e.g. “ERROR” instead of “OK”.

Returns:

True if the toy supports a secondary capability, False otherwise.

async strict_power_off() bool[source]

Similar to :meth: power_off, but this method raises an exception if the command fails.

Raises:
  • UnexpectedToyResponse – The toys’ response was unexpected.

  • ConnectionError – Command could not be sent, or the toy did not respond within an appropriate timeout.

Returns:

Always True.

async strict_reconnect() bool[source]

Similar to :meth: reconnect, but this methode does raise an exception if the reconnection fails.

Raises:
  • ConnectionError – The reconnection failed.

  • RuntimeError – The reconnection was attempted after intentionally disconnecting the toy.

Returns:

Always True.

async strict_stop() bool[source]

Similar to :meth: stop, but this method raises an exception if the stop command fails.

Raises:
  • UnexpectedToyResponse – The toys’ response was unexpected, e.g. “ERROR” instead of “OK”.

  • ConnectionError – Command could not be sent, or the toy did not respond within an appropriate timeout.

Returns:

Always True.

Module contents

Lovense brand subpackage: the Lovense toy class, BLE handler, and model data.

class tikal.low_level.brands.lovense.LovenseHandler(on_disconnect: ~typing.Callable[[str], ~typing.Any], on_power_off: ~typing.Callable[[str], ~typing.Any], logger_name: str = 'tikal', client_class: ~typing.Type[~bleak.BleakClient] = <class 'bleak.BleakClient'>)[source]

Bases: BLEBrandHandler

Brand handler for Lovense BLE toys.

Encapsulates all Lovense-specific logic: identification, UUID resolution, connection, and notification setup. The common construction signature (callbacks, logger, client class) is inherited from BLEBrandHandler.

async create_toy(toy_data: ToyData, device: BLEDevice) Toy[source]

Connect to a single Lovense toy.

Parameters:
  • toy_dataLovenseData object representing the toy to connect to.

  • deviceBLEDevice object representing the toy to connect to.

Returns:

A connected, ready to use LovenseToy instance.

Raises:
  • ValidationError – The toy_data contains an invalid model name.

  • ConnectionError – The BLE connection, UUID resolution, or notification setup failed.

static create_toy_data(device: BLEDevice) ToyData[source]

Create a LovenseData instance (inherits from ToyData) from a BLE device representing a Lovense toy.

static handles_device(device: BLEDevice) bool[source]

Return True if the BLE device is a lovense toy. Lovense toys advertise with a name prefixed ‘LVS-’

static handles_toy(toy_data: ToyData) bool[source]

Return True if the given ToyData represents a Lovense toy.

class tikal.low_level.brands.lovense.LovenseToy(transport: BleTransport, model_name: str, on_power_off: Callable[[str], Any], logger_name: str)[source]

Bases: Toy

Low-level representation of a Lovense BLE toy.

Implements the Lovense-specific protocol for communication with Lovense toys over Bluetooth Low Energy. Handles command formatting, response parsing, and Lovense-specific notifications (like power-off events). You are not meant to instantiate these classes directly. BLEConnectionBuilder establishes connections to toys and returns instances of LovenseToy

Parameters:
  • transport – Transport layer handling the BLE communication.

  • model_name – Model name (e.g., “Nora”, “Lush”). Must be a key in LOVENSE_TOY_NAMES.

  • on_power_off – Callback invoked when the user powers off the toy via the physical power button. Receives the toy’s Bluetooth address as a string argument.

  • logger_name – Name of the logger to use. Use empty string for root logger.

Example:

# Set intensity of the primary capability to maximum
await toy.intensity1(20)

# Send a custom command
response = await toy.direct_command("DeviceType")
print(f"Device info: {response}")
property brand: str

Returns the brand of the toy. Always “Lovense” for this class

async change_rotation_direction() bool[source]

Change rotation direction for toys with rotation capability.

This method only affects toys that support rotation (Nora and Ridge). For other toys, it returns True immediately without sending any command.

Returns:

True if the toy acknowledged the command or if rotation is not supported, False if the command failed.

Example:

await toy.rotate_change_direction()  # Toggle rotation direction
property change_rotation_direction_available: bool

Check if the toy supports changing the rotation direction.

Returns:

True if the rotation direction can be changed, False otherwise.

Return type:

bool

Example:

if toy.change_rotation_direction_available:
    await toy.change_rotation_direction()
async direct_command(command: str, timeout: float = 3.0) str | None[source]

Send any command directly to the toy.

This method allows sending commands that are not implemented by the library.

Parameters:
  • command – Command string in UTF-8 format. A semicolon terminator will be added if not present.

  • timeout – Response timeout in seconds. Defaults to 3.0.

Returns:

Response string from the toy (with semicolon stripped), or None if timeout or error occurred.

Example:

# Query firmware version
response = await toy.direct_command("DeviceType")
# Response format: "C:11:0082059AD3BD"
# (C = device type, 11 = firmware version, address)
async disconnect() None[source]

Disconnect from the device.

Stops all toy actions, disables notifications, and closes the BLE connection. This is regarded as intentional disconnect and does not trigger an on_disconnect callback even if an on_disconnect callback was set (either during initialization or via set_on_disconnect). The method does not raise any exceptions.

Note

After calling this method, the toy object is unusable. To connect again, you will need to re-scan and connect using the ConnectionBuilder. Use the newly provided Lovense object by the ConnectionBuilder.

async get_batch_number() str | None[source]

Retrieve the production batch number.

The batch number appears to be in YYMMDD format, indicating the manufacturing date.

Returns:

Batch number string (e.g., “241015” for October 15, 2024), or None if an error occurred.

Example:

batch = await toy.get_batch_number()
if batch:
    print(f"Manufactured: 20{batch[:2]}/{batch[2:4]}/{batch[4:6]}")
async get_battery_level() int | None[source]

Retrieve the battery level as a percentage.

Returns:

Battery level (0-100%), or None if the command failed or timed out.

Example:

battery = await toy.get_battery_level()
if battery is not None:
    if battery < 20:
        print(f"Low battery: {battery}%")
    else:
        print(f"Battery: {battery}%")

Note

Lovense toys have a quirk where they prefix ‘s’ before the value if the toy was recently reconnected. This method strips the ‘s’ when present

async get_device_type() str | None[source]

Retrieve device type and firmware information.

Returns:

FirmwareVersion:Address” (e.g., “C:11:0082059AD3BD”), or None if an error occurred.

Return type:

String in format “DeviceType

Example:

info = await toy.get_device_type()
if info:
    parts = info.split(":")
    device_type = parts[0]
    firmware = parts[1]
    print(f"Device type: {device_type}, Firmware: {firmware}")
async get_status() int | None[source]

Retrieve the status code of the toy.

Returns:

Status code (2 = Normal operation), or None if an error occurred.

Example:

status = await toy.get_status()
if status == 2:
    print("Toy is operating normally")
else
    print(f"Unusual status code: {status}")
async intensity1(level: int) bool[source]

Set the primary capability to the specified level.

The primary capability depends on the toy model (e.g., vibration for Gush, thrusting for Solace)

Parameters:

level – Intensity level (0-20). Values outside this range are clamped.

Returns:

True if the toy acknowledged the command, False otherwise.

Example:

await toy.intensity1(20)  # Set primary capability to maximum
async intensity2(level: int) bool[source]

Set the secondary capability to the specified level.

The secondary capability depends on the toy model (e.g., Rotation for Nora, depth control for Solace). Not all toys have a secondary capability. Returns true immediately and does not send a command if the toy has no secondary capability.

Parameters:

level – Intensity level (0-20). Values outside the valid range are clamped.

Returns:

True if the toy acknowledged the command or if no secondary capability exists, False otherwise

Example:

await toy.intensity2(10)  # Set secondary capability to medium intensity

Note

For Max’s air pump, the level is automatically divided by 4 to convert from 0-20 scale to 0-5 scale.

property intensity_names: tuple[str, str | None]

Get display names for Lovense toy capabilities.

Returns:

(primary_name, secondary_name). Secondary name is None if the toy has only one capability.

Return type:

tuple[str, str | None]

Example:

names = toy.intensity_names
print(f"{names[0]}: intensity1")  # "Vibration: intensity1"
if names[1]:
    print(f"{names[1]}: intensity2")  # "Rotation: intensity2"
property max_intensity: int

Get maximum intensity value for Lovense toys.

Returns:

Always 20 for Lovense toys.

Return type:

int

Note

Some capabilities (like Max’s air pump) use different ranges. Those are automatically scaled for you.

async power_off() bool[source]

Turn off power to the toy.

This sends the PowerOff command which turns off the toy. The toy can then only be turned back on via the physical power button.

Returns:

True if successful, False otherwise.

Example:

await toy.power_off()
property recommended_min_interval: int

Get the recommended minimum interval between intensity changes (milliseconds).

Returns:

The Lovense per-model suggested minimum segment length.

Return type:

int

async reconnect() bool[source]

Attempts to reconnect to the toy to after an unintentional disconnect.

Does nothing if already connected. Use only after an unintended disconnect (ConnectionError). If you disconnected via disconnect, use the ConnectionBuilder instead.

Returns:

True if the reconnection was successful, False otherwise.

async set_model_name(model_name: str) None[source]

Set the model name of the toy.

Parameters:

model_name – New model name. Must be in LOVENSE_TOY_NAMES.keys() of module ToyData. Case Insensitive. Case Insensitive.

Raises:
  • InvalidModelError – If model_name is not valid for this toy brand.

  • BadModelError – If the model_name is valid, but commands still fail. See BadModelError for details.

Example:

# Update model name in case it was set incorrectly while building the connection via the ConnectionBuilder
toy.set_model_name("Nora")
async start_notifications() None[source]

Start listening for messages from the Lovense toy.

This method is called by the ConnectionBuilder during connection setup and should not be called manually.

Raises:

ConnectionError – Failed to start notifications.

async stop() bool[source]

Stop all toy actions by setting all intensities to zero.

This method is a shortcut for intensity1(0) and intensity2(0)

Returns:

True if both commands succeeded, False if either failed.

Example:

await toy.stop()
async strict_change_rotation_direction() bool[source]

Similar to :meth: rotate_change_direction, but this method raises an exception if the command fails.

Raises:
  • UnexpectedToyResponse – The toys’ response was unexpected, e.g. “ERROR” instead of “OK”.

  • ConnectionError – Command could not be sent, or the toy did not respond within an appropriate timeout.

Returns:

True if the toy supports rotation, False otherwise. Does not raise if the toy does not support rotation.

async strict_direct_command(command: str, timeout: float = 3.0) str[source]

Similar to :meth: direct_command, but this method raises an exception if the command fails.

Raises:

ConnectionError – Command could not be sent, or the toy did not respond within the provided timeout.

Returns:

response string from the toy

async strict_disconnect() None[source]

Similar to :meth: disconnect, but this methode does raise an exception if the disconnect fails. The exception is only for logging. The toy is still disconnected in the error case.

Raises:

ConnectionError – Command could not be sent, or the toy did not respond within an appropriate timeout.

async strict_get_batch_number() str[source]

Similar to :meth: get_batch_number, but this method raises an exception if the command fails.

Raises:
  • ConnectionError – Command could not be sent, or the toy did not respond within an appropriate timeout.

  • UnexpectedToyResponse – The toys’ response was unexpected.

Returns:

Batch number string (e.g., “241015” for October 15, 2024)

async strict_get_battery_level() int[source]

Similar to :meth: get_battery_level, but this method raises an exception if the command fails.

Raises:
  • UnexpectedToyResponse – The toys’ response was unexpected.

  • ConnectionError – Command could not be sent, or the toy did not respond within an appropriate timeout.

Returns:

Battery level as a percentage (0-100).

async strict_get_device_type() str[source]

Similar to :meth: get_device_type, but this method raises an exception if the command fails.

Raises:

ConnectionError – Command could not be sent, or the toy did not respond within the provided timeout.

Returns:

FirmwareVersion:Address” (e.g., “C:11:0082059AD3BD”)

Return type:

String in format “DeviceType

Note

does not raise UnexpectedToyResponse as the response is not verified.

async strict_get_status() int[source]

Similar to :meth: get_status, but this method raises an exception if the command fails.

Raises:
  • ConnectionError – Command could not be sent, or the toy did not respond within the provided timeout.

  • UnexpectedToyResponse – The toys’ response was unexpected, in this case any string containing non-digit characters.

Returns:

Status code (2 = Normal operation)

async strict_intensity1(level: int) bool[source]

Similar to :meth: intensity1, but this method raises an exception if the intensity command fails.

Raises:
  • ConnectionError – Command could not be sent, or the toy did not respond within an appropriate timeout.

  • UnexpectedToyResponse – The toys’ response was unexpected, e.g. “ERROR” instead of “OK”.

Returns:

Always true

async strict_intensity2(level: int) bool[source]

Similar to :meth: intensity2, but this method raises an exception if the intensity command fails.

Raises:
  • ConnectionError – Command could not be sent, or the toy did not respond within an appropriate timeout.

  • UnexpectedToyResponse – The toys’ response was unexpected, e.g. “ERROR” instead of “OK”.

Returns:

True if the toy supports a secondary capability, False otherwise.

async strict_power_off() bool[source]

Similar to :meth: power_off, but this method raises an exception if the command fails.

Raises:
  • UnexpectedToyResponse – The toys’ response was unexpected.

  • ConnectionError – Command could not be sent, or the toy did not respond within an appropriate timeout.

Returns:

Always True.

async strict_reconnect() bool[source]

Similar to :meth: reconnect, but this methode does raise an exception if the reconnection fails.

Raises:
  • ConnectionError – The reconnection failed.

  • RuntimeError – The reconnection was attempted after intentionally disconnecting the toy.

Returns:

Always True.

async strict_stop() bool[source]

Similar to :meth: stop, but this method raises an exception if the stop command fails.

Raises:
  • UnexpectedToyResponse – The toys’ response was unexpected, e.g. “ERROR” instead of “OK”.

  • ConnectionError – Command could not be sent, or the toy did not respond within an appropriate timeout.

Returns:

Always True.