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!
International: AliExpress
Netherlands: AliExpress
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.
International: AliExpress
International: AliBaba
Media
Thank you @dasbrin
on Discord for the images.
Technical Specification
Official documents
Community Reversed Engineered documents
Shocker & Remote Documents on GitHub by @Nat-the-Kat
RF Specification
Name | Value |
---|---|
Carrier Frequency | 433.95 MHz |
Modulation Type | ASK / OOK |
Bit encoding
Type | High duration | Low duration |
---|---|---|
Sync | 1400µs | 750µs |
1 | 750µs | 250µs |
0 | 250µs | 750µs |
Packet fields
Name | Value | Length | Remarks |
---|---|---|---|
Transmitter ID | 0 - 65535 | 16 bits | The collar will be Paired to this |
Channel Number | 0 - 2 | 4 bits | The collar will be Paired to this |
Action Command | 1 - 3 | 4 bits | 1 = Shock, 2 = Vibrate, 3 = Beep |
Command Intensity | 0 - 99 | 8 bits | Should always be 0 for beep |
Message checksum | 0 - 255 | 8 bits | 8-bit sum of all other fields as a int32 |
Layout
[PREFIX ] = SYNC
[TRANSMITTER ID] = XXXXXXXXXXXXXXXX
[CHANNEL ] = XXXX
[MODE ] = XXXX
[STRENGTH ] = XXXXXXXX
[CHECKSUM ] = XXXXXXXX
[END ] = 00
Working C++ code
Example untested RFCat code
# 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)