For test of a decoder I made a python script that creates and sends SENT messages. I used an ESP32 with it's RMT module for the test. Code attached. it includes proper CRC calculation, which was the hard part, given that almost no test data was available.
Code: Select all
from machine import Pin
from esp32 import RMT
# Constants
data_pin = const(13) # GPIO13
LOW = const(5) # Low pulse width in ticks
SYNC = const(56 - LOW) # SYNC Pulse width
OFFSET = const(12 - LOW) # Nibble offset
MAXTICK = const(272) # 56 + 8 * 27 ticks
crc4_tab = (0, 13, 7, 10, 14, 3, 9, 4, 1, 12, 6, 11, 15, 2, 8, 5)
p = Pin(data_pin, Pin.OUT)
def frame(msg, status, idle, pause):
'''
Generate the frame pulse train. Arguments:
msg: value to be sent as 3 byte bytes string
status: Status value
idle level: 0 for low, 1 for high
pause: minimal pause length. 0 for no pause
'''
# create the pulse sequence
crc = 5 # Magic start value
# calibration pulse
data = [LOW, SYNC]
# Status nibble
data.append(LOW)
data.append(status + OFFSET) # Status nibble
# Data nibbles
for v in msg:
nibble = (v >> 4) & 0xf
crc = crc4_tab[crc] ^ nibble
data.append(LOW)
data.append(nibble + OFFSET)
nibble = v & 0xf
crc = crc4_tab[crc] ^ nibble
data.append(LOW)
data.append(nibble + OFFSET)
crc = crc4_tab[crc] # padding nibble
# CRC nibble
data.append(LOW)
data.append(crc + OFFSET) # crc nibble
# Add a pause pulse, if requested
if pause == 1:
data.append(LOW)
data.append(LOW)
elif pause > 0:
pause = max(pause, 12) # Ensure minimal length of 12 ticks
data.append(LOW)
data.append((MAXTICK - sum(data)) + (pause - LOW)) # pause pulse
return data
def send_data(data, idle, tick):
'''
Send the data frame(s). Arguments:
data: list with the tick values
idle: Idle level of the data line
tick: tick time in us units
'''
# set up the interface
idle = 1 if idle else 0 # map idle value to 0 and 1
p.value(idle)
if idle: # if idle is high, the idle level has to be 1. Not supported
# by the main branch of MicroPython
rmt = RMT(0, pin=p, clock_div=8, idle=1) # 100 ns tick granularity, idle=1
else:
rmt = RMT(0, pin=p, clock_div=8) # 100 ns tick granularity, idle=0 (default)
tick = int(tick * 10 + 0.49) # scale to 100 ns granularity
# Mutiply items by the internal tick units
for i in range(len(data)):
data[i] *= tick
# send the pulses
rmt.write_pulses(data, start=1 - idle)
rmt.wait_done(timeout=sum(data)) # Wait for the end
rmt.deinit()
# send a single fast data frame with two data values
def fast(value1, value2=None, status=0, idle=1, pause=12, tick=3):
'''
Send a single data frame in fast data format. Arguments:
value1, value2: values to be sent; integer
status: Status value
idle level: 0 for low, 1 for high
pause: minimal pause length. 0 for no pause
tick: tick time in us units
'''
msg = bytearray(3)
msg[0] = ((value1 >> 4) & 0xff)
# msg[1] = ((value1 << 4) & 0xf0) | ((value2 >> 8) & 0x0f)
# msg[2] = value2 & 0xff
if value2 is None:
value2 = value1
# value2 with the nibbles in opposite order:
msg[1] = ((value1 << 4) & 0xf0) | (value2 & 0x0f)
msg[2] = (value2 & 0xf0) | ((value2 >> 8) & 0xf)
# value 2 is sent in regular order.
# msg[1] = ((value1 << 4) & 0xf0) | ((value2 >> 8) & 0x0f)
# msg[2] = value2 & 0xff
data = frame(msg, status, idle, pause)
send_data(data, idle, tick)
# send a single secure message frame
def secure(value, counter=0, status=0, idle=1, pause=12, tick=3):
'''
Send a single secure message frame. Arguments:
value: value to be sent; integer
counter: counter; integer
status: Status value
idle level: 0 for low, 1 for high
pause: minimal pause length. 0 for no pause
tick: tick time in us units
'''
msg = bytearray(3)
msg[0] = ((value >> 4) & 0xff)
msg[1] = ((value & 0x0f) << 4) | ((counter >> 4) & 0x0f)
msg[2] = ((counter & 0x0f) << 4) | ((~value >> 8) & 0x0f)
data = frame(msg, status, idle, pause)
send_data(data, idle, tick)
# send a single fast channel high speed frame
def highspeed(value, status=0, idle=1, pause=12, tick=2.7):
'''
Send a fast channel high speed frame. Arguments:
value: value to be sent; integer
status: Status value
idle level: 0 for low, 1 for high
pause: minimal pause length. 0 for no pause
tick: tick time in us units
'''
msg = bytearray(2)
msg[0] = ((value >> 5) & 0x70) | ((value >> 6 ) & 0x07)
msg[1] = ((value << 1) & 0x70) | (value & 0x07)
data = frame(msg, status, idle, pause)
send_data(data, idle, tick)
# send a short serial message encoded in a burst of 16 frames
def serial(value, id=0, idle=0, pause=12, tick=3, dummy=True):
'''
Send a short serial message with a burst of 16 frames. Arguments:
value: value to be sent; integer
id: ID; integer
idle level: 0 for low, 1 for high
pause: minimal pause length. 0 for no pause
tick: tick time in us units
dummy: Flag telling whether a additional trailing frame will be sent
'''
data = []
crc = 5 # Magic start value
crc = crc4_tab[crc] ^ (id & 0x0f)
crc = crc4_tab[crc] ^ ((value >> 4) & 0x0f)
crc = crc4_tab[crc] ^ (value & 0x0f)
crc = crc4_tab[crc] # padding nibble
value = ((id & 0x0f) << 12) | ((value & 0xff) << 4) | crc
for i in range(16):
if i == 0:
status = ((value >> 13) & 0x04) | 0x08
else:
status = ((value >> 13) & 0x04)
data += frame(chr(i).encode() + b"\x00\x00", status, idle, pause)
value <<= 1
# adding a dummy frame to cope with the SD2000x+ expectation
if dummy:
data += frame(b"\xff\x00\x00", 0, idle, pause)
send_data(data, idle, tick)
# send an enhanced serial message with 12 or 16 bit in a 18 frame burst
def enhanced(value, id=0, long=False, idle=0, pause=12, tick=3, dummy=True):
'''
Send an enhanced serial message with a burst of 18 frames. Arguments:
value: value to be sent; integer
id: ID; integer
long: True for 16 bit, False for 12 bit,
idle level: 0 for low, 1 for high
pause: minimal pause length. 0 for no pause
tick: tick time in us units
dummy: Flag telling whether am additional trailing frame will be sent
'''
# Pack the data into two bit fields for bit2 and bit3
if long:
bit3 = ((id & 0x0f) << 6) | ((value >> 11) & 0x1e) | 0x400
else:
bit3 = ((id & 0xf0) << 2) | ((id & 0x0f) << 1)
bit2 = value & 0xfff
# calculate the CRC bitwise
polynome = 0b011001 # (x^6 +) x^4 + x^3 + 1 (1 is x^0 -> lowest bit set, x^6 is implicit)
uprmask = 0b100000 # Masking the upper bit of the crc
bitmask = 0x800
crc = 0x15 # Magic start value
for i in range(15): # 24 data bits + 6 implicit zero bits
crc = ((crc << 1) | ((bit2 & bitmask) != 0)) ^ (polynome if (crc & uprmask) else 0)
crc = ((crc << 1) | ((bit3 & bitmask) != 0)) ^ (polynome if (crc & uprmask) else 0)
bitmask >>= 1
crc &= 0x3f
# add the crc to bit2 and "enhanced serial" flag to bit3
bit2 |= (crc << 12) # crc sixbit
bit3 |= 0x3f000 # ones sixbit
# create the frame sequence
data = []
for i in range(18):
status = ((bit2 >> 15) & 0x04) | ((bit3 >> 14) & 0x08)
data += frame(chr(i).encode() + b"\x00\x00", status, idle, pause)
bit2 <<= 1
bit3 <<= 1
# adding a dummy frame to cope with the SD2000x+ expectation
if dummy:
data += frame(b"\xff\x00\x00", 0, idle, pause)
send_data(data, idle, tick)