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)

Contributors

  • Luc โ™ฅ