Skip to main content

F-machine

F-machine is a UK sex toy company. Two or their products, the Gigolo fucking machine and the Tremblr male milking machine, can be controlled remotely via radio frequency (RF).

Overview

Both Gigolo and Tremblr share the same protocol. The only difference is that the Tremblr has two additional buttons for controlling suction which are missing on the Gigolo.

The following three parameters can be controlled remotely:

  • Power: Starts and stops the movement.
  • Speed: Increases or decreases the stroke speed (= number of strokes per minute).
  • Suction (Tremblr only): By increasing or decreasing suction, the receiver will stroke closer to the base or the tip of the shaft.

The stroke length can only be adjusted via a screw on the machine itself while power is turned off, and is not controllable remotely.

The manufacturer indicates multiple types of remotes (type 'A' to 'E'). This document refers to the 'A' type of remote. It is possible that the other versions use different messages, as well as an entire RF stack entirely.

Note: The Tremblr has a fan which is always running as long as the hardware power switch is turned on, even while the motor itself is not moving. This produces a continuous white noise level. When designing long-running applications (e.g. an alarm clock), you may need a Smart Plug to turn the power on or off remotely, in addition to the infrastructure for sending commands to the toy.

From radio to bits

The remote control uses a carrier frequency of 314.965 MHz and the signal is modulated with OOK (On/Off Keying), using a SC2262 encoder that sends tri-states bits (1/0/F) as fixed code commands.

Libraries are readily available to emulate the commands to be sent over 315Mhz:

Message structure

Each command is encoded as a 12 bits message, plus a synchronization pulse. Each bit is encoded as a 2 pulses cycle:

  • Bit '0': 2*(short high, long low)
  • Bit '1': 2*(long high, short low)
  • But 'F' (floating): short high, long low, long high, short low

A 'short' duration is 4 times the 2262 chip's clock, while a long one is 12 times the clock.

            __        __
Bit '0' : _| |______| |______
______ ______
Bit '1' : _| |__| |__
__ ______
Bit 'F' : _| |______| |__

A Sync bit needs to be transmitted to complete the command. It is a 4 bits long (128 clocks) pulse, with a high state for 4 clocks, and then kept to low for the rest of the 128 clocks duration.

While the datasheet of the reciever chip (2272) indicates that 2 valid and identical commands needs to be recieved before the operation is acted upon, it seems that most implementation will repeat the command betweetn 8 to 15 times.

Buttons

power

This button starts or stops the movement, depending on whether the machine is moving already.

Note that the same message is sent for starting and stopping, therefore, an application cannot reliably put the machine in the moving or stopped state. Additional sensors might be required to detect movement. Alternatively, when using a Smart Plug you could cut power to the machine and then restore power, which will put the machine in the "not moving, at speed 1" state.

speed_inc & speed_dec

These two buttons increase and decrease the motor speed, respectively. There are 32 discrete speeds.

Sending a button press for 8 messages changes the speed by 1 step. Sending the button press longer than that may change the speed by more than that, see below for details. When in the lowest speed, the speed decrease button has no effect, while in the highest speed, the speed increase button has no effect.

Note that when the machine is stopped via the power messages, the speed is persisted in memory and once another power command is sent, the machine will resume moving at the previous speed. You can also send commands to change the speed while the machine is not moving, and they will take effect as soon as the power command is sent. Turning off the power to the machine and restoring it will set the speed to 1 (the lowest speed).

List of speeds

The machine has 32 discrete speeds. We measured the following speeds of the Tremblr, given in revolutions per minute (RPM), which is identical to the number of strokes per minute:

#Speed
116.5 rpm
222.7-24 rpm
330.5 rpm
437.5 rpm
544-45 rpm
651 rpm
757-58 rpm
864-65 rpm
970-71 rpm
1078-80 rpm
1185-86 rpm
1292-93 rpm
1397-99 rpm
14104 rp
15110 rpm
16119 rp
17125 rpm
18132 rpm
19139 rpm
20145 rp
21152 rpm
22159 rpm
23166 rpm
24174 rp
25181 rpm
26187 rpm
27193 rpm
28199-200 rpm
29204-212 rpm
30217 rpm
31224 rpm
32235 rpm

Note that the speeds are mostly linear, with some discontinuities on the upper end which may be due to measurement errors. One approximation for the speed, given the step index, is v(n) = 10 + (225 - 10) * n / 32.

Also, keep in mind that while the absolute speed differences stay the same, the relative difference between e.g. speed 1 and 2 is much higher than between speed 20 and 21. Therefore, a user will definitely notice a change by 1 step in the lower speeds, while a change by 1 step in the higher speeds might not be perceived at all.

Amount of step changes based on number of messages

Because of the required "repetition" of commands to ensure the message is being received by the device, there is a risk that sending N times the message "Speed UP" results in the motor speed being increase more than once.

We measured the effect of sending more than 9 messages to the Tremblr to figure out if it is worth sending a longer button press instead of sending multiple 8-message long button presses. Based on our measurements, there is non-determinism when sending 16- to 22-message long button presses. These results showed up across multiple measurements, which rules out connectivity errors. Therefore, we recommend sticking with at most 15 messages for accurate speed control.

The following table contains our results, namely by how many steps the speed did change based on for how many messages the button press was sent. We used the methodology of always putting the machine in the slowest speed first, and then sending speed increase messages.

# messagesChange in # steps
1-7 messagesno effect
8-9 messages+1
10-12 messages+2
13-15 messages+3
16 messages+4 or +5 or +6
17-18 messages+5 or +6
19 messages+6 or +7
20 messages+8 or +9
21 messages+11 or +12
22-24 messages+11
25 messages+12
26 messages+14

suction_inc & suction_dec

These two buttons, only applicable to the Tremblr, change the amount of air in circulation. By increasing suction, the receiver will stroke closer to the base of the shaft, while decreasing suction causes the receiver to stroke closer to the tip of the shaft.

More specifically, these buttons control two valves which will either let more air in, or remove air. The valves are opened when at least 8 messages are sent, and stay open as long as the button press keeps being sent.

List of messages

Tremblr (A remote)

  • Speed Up: 11FFFF001000
  • Start/Stop: 11FFFF000010
  • Speed Down: 11FFFF000100
  • Suction Down: 11FFFF000001
  • Suction Up: 11FFFF010000

Gigolo (A remote)

  • Speed Up: 11FFFF001000
  • Speed Down: FFFFF1110010
  • Start/Stop: FFFFF1110100

Hardware for sending RF signals

For sending RF signals from a computer, you need a RF device and an antenna. When shopping for a device, be aware that many RF devices (especially when they only cost \$50) can only be used for receiving signals; you'll need a device that can both receive and transmit signals.

Also, keep in mind that using a RF transmission device follows strict rules and regulations. The frequency that F-machine operates in, 314.965 MHz, is allowed for unlicensed, civilian use in some jurisdictions but do not send at a higher power level or longer than necessary to avoid interfering in other people's equipment.

It is possible to salvage a 2262 chip from another unused remote or buy a new one. You then just need to add a simple 315Mhz transmitter such as the H34A in 315Mhz version.

For a more generic (albeit more expensive option) the HackRF One is an entry-level device and often comes bundled with a suitable antenna. Just download the required software for your operating system, connect the device to your computer via USB and you are good to go.

Transmitting messages with H34A and any arduino compatible microcontroler

Some GitHub repositories will offer very simple pieces of code to reuse:

Transmitting a message with HackRF One

For example, to send data from the file data.bin at the correct frequency with a sample rate of 2 million Hz, you'd use the following command:

hackrf_transfer -f 314965000 -x 30 -s 2000000 -t ./data.bin

Generating a message

To generate the required data.bin file, you can use the following TypeScript file. Install Deno and run with deno --allow-write f-machine.ts.

const SAMPLES_PER_SECOND = 2_000_000;
const SIGNAL_FREQUENCY = SAMPLES_PER_SECOND / 561.4; // 3,562 Hz
const ANGULAR_FREQUENCY = 2 * Math.PI * SIGNAL_FREQUENCY;

function getSample(position: number, amplitude: number) {
const real = amplitude * Math.sin(ANGULAR_FREQUENCY * position);
const imag = amplitude * Math.cos(ANGULAR_FREQUENCY * position);
return [real, imag];
}

function floatToInt(input: number) {
return Math.round(
input >= 0 ? Math.min(127, input * 127) : Math.max(-128, input * 128)
);
}

function writeSamples(
array: Int8Array,
numSamples: number,
offset: number,
magnitude: number
) {
for (let i = 0; i < numSamples; i += 1) {
const [real, imag] = getSample(
(offset + i) / SAMPLES_PER_SECOND,
magnitude
);
array[i * BYTES_PER_SAMPLE] = floatToInt(real);
array[i * BYTES_PER_SAMPLE + 1] = floatToInt(imag);
}
}

const SAMPLES_PER_BIT = Math.round(1.72 * 0.001 * SAMPLES_PER_SECOND); // 1.72ms per bit
const BITS_PER_MESSAGE = 25;
const SILENCE_BITS = 7;
const BYTES_PER_SAMPLE = 2;

const WIDTH_SET = 0.79;
const WIDTH_UNSET = 0.334 * 2;

function getRawMessage(bits: ReadonlyArray<0 | 1>) {
const array = new Int8Array(
SAMPLES_PER_BIT * (BITS_PER_MESSAGE + SILENCE_BITS) * BYTES_PER_SAMPLE
);

for (let i = 0; i < BITS_PER_MESSAGE; i += 1) {
const part = new Int8Array(
array.buffer,
i * SAMPLES_PER_BIT * BYTES_PER_SAMPLE,
SAMPLES_PER_BIT * BYTES_PER_SAMPLE
);

if (bits[i] === 1) {
writeSamples(
part,
SAMPLES_PER_BIT * WIDTH_SET,
i * SAMPLES_PER_BIT * BYTES_PER_SAMPLE,
2.3
);
} else {
writeSamples(
part,
(SAMPLES_PER_BIT / 2) * WIDTH_UNSET,
i * SAMPLES_PER_BIT * BYTES_PER_SAMPLE,
2.3
);

// do nothing for other half since bytes are already zero
}
}

// do nothing for empty space at the end, it is already zero

return array;
}

interface Buttons {
power?: true;
speed?: "inc" | "dec";
suction?: "inc" | "dec";
}

function getMessage(buttons: Buttons) {
const bits = [
// header
1,
1,
1,
1,
0,
1,
0,
1,
0,
1,
0,
1,
// buttons
0,
0,
buttons.suction === "inc" ? 1 : 0,
buttons.suction === "inc" ? 1 : 0,
buttons.speed === "inc" ? 1 : 0,
buttons.speed === "inc" ? 1 : 0,
buttons.speed === "dec" ? 1 : 0,
buttons.speed === "dec" ? 1 : 0,
buttons.power === true ? 1 : 0,
buttons.power === true ? 1 : 0,
buttons.suction === "dec" ? 1 : 0,
buttons.suction === "dec" ? 1 : 0,
// end
0,
] as const;

return getRawMessage(bits);
}

const MESSAGE: Buttons = { speed: "inc" };
const NUM_MESSAGES = 13;

await Deno.writeFile("./data.bin", new Uint8Array(SAMPLES_PER_SECOND / 2)); // 250ms of silence at the start
for (let m = 0; m < NUM_MESSAGES; m += 1) {
const messageData = getMessage(MESSAGE);
await Deno.writeFile("./data.bin", new Uint8Array(messageData.buffer), {
append: true,
});
}
console.log("Done writing to data.bin!");