Skip to content

CaiXianlin

Compatible

This product is compatible with OpenShock.

Cheap and easily acquirable.

Buying

Shockers

Best effort list of current AliExpress sellers. Feel free to add more sources to this list!

Cables

The charging port for this model is a standard DC 3.5 x 1.35mm. A USB to DC 3.5 x 1.35mm cable is used to charge the shocker. You might even have one laying around as they are common.

Media

CaiXianlin Shocker FrontCaiXianlin Shocker Back

CaiXianlin Shocker Back Inner Case

Thank you @dasbrin on Discord for the images.

Technical Specification

Official documents

US Patent Document 1

US Patent Document 2

Community Reversed Engineered documents

Shocker & Remote Documents on GitHub by @Nat-the-Kat

RF Specification

NameValue
Carrier Frequency433.95 MHz
Modulation TypeASK / OOK

Bit encoding

TypeHigh durationLow duration
Sync1400µs750µs
1750µs250µs
0250µs750µs

Packet fields

NameValueLengthRemarks
Transmitter ID0 - 6553516 bitsThe collar will be Paired to this
Channel Number0 - 24 bitsThe collar will be Paired to this
Action Command1 - 34 bits1 = Shock, 2 = Vibrate, 3 = Beep
Command Intensity0 - 998 bitsShould always be 0 for beep
Message checksum0 - 2558 bits8-bit sum of all other fields as a int32

Layout

text
[PREFIX        ] = SYNC
[TRANSMITTER ID] =     XXXXXXXXXXXXXXXX
[CHANNEL       ] =                     XXXX
[MODE          ] =                         XXXX
[STRENGTH      ] =                             XXXXXXXX
[CHECKSUM      ] =                                     XXXXXXXX
[END           ] =                                             00

Working C++ code

Firmware CaiXianlin Encoder

Example untested RFCat code

py
# Import the necessary libraries and functions
from rflib import *
import time

# Set up the RfCat device
d = RfCat()
d.setPktPQT(0)
d.setMdmNumPreamble(0)
d.setEnableMdmManchester(False)
d.setFreq(433950000)
d.setMdmModulation(MOD_ASK_OOK)
d.setMdmDRate(3950)
d.makePktFLEN(22)
d.setMdmSyncWord(0)
   
"""
Returns the string representation of the action (Shock, Vibrate, or Beep)
"""
def get_action_string(action):
   if action == 1:
      return 'Shock'
   elif action == 2:
      return 'Vibrate'
   elif action == 3:
      return 'Beep'
   else:
      return 'Unknown'

# Since the receivers only support 3 channels, we can change the transmitter ID to extend the number of channels
for transmitter_id in range(46231, 46233):
   # Loop through the channels
   for channel in range(3):
      # Loop through the actions
      for action in range(1, 4):
         # Loop through the intensities, but not if the action is 3 (beep)
         for intensity in range(0, 100, 10) if action != 3 else [0]:

            # Intensity has max of 99
            if intensity == 100:
               intensity = 99

            # Assemble the payload
            payload = (transmitter_id << 24) | (channel << 20) | (action << 16) | (intensity << 8)

            # Calculate the checksum (sum(bytes) % 256)
            checksum = 0
            for i in range(8):
               checksum += (payload >> (i * 8)) & 0xFF
            checksum %= 256

            # Add the checksum to the payload
            payload |= checksum

            # Assemble the message
            message = bytes.fromhex('fc{0:040b}88'.format(payload).replace('1', 'e').replace('0', '8'))

            print('Sending {0} on channel {1} with intensity {2} and checksum {3}'.format(get_action_string(action), channel, intensity, checksum))

            # Transmit the message 5 times
            for i in range(5):
               d.RFxmit(message)