Skip to content

Instantly share code, notes, and snippets.

@gabonator
Created August 5, 2023 08:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gabonator/7f4f7247f6c47fd8c7ccdfed3b432ccb to your computer and use it in GitHub Desktop.
Save gabonator/7f4f7247f6c47fd8c7ccdfed3b432ccb to your computer and use it in GitHub Desktop.
hcs200 decoder test for la104/rftool
#pragma once
#include "Types.h"
class CPoint {
public:
int x, y; // TODO: !!!
CPoint()
{
}
CPoint(int _x, int _y) :
x(_x), y(_y)
{
}
CPoint operator +(const CPoint& cp)
{
return CPoint( x + cp.x, y + cp.y );
}
bool operator !=(const CPoint& cp)
{
return cp.x != x || cp.y != y;
}
};
class CRect {
public:
CRect() :
left(0), top(0), right(0), bottom(0)
{
}
CRect( int _left, int _top, int _right, int _bottom ) :
left(_left), top(_top), right(_right), bottom(_bottom)
{
}
int CenterX()
{
return (left+right)>>1;
}
CPoint Center()
{
return CPoint( (left+right) >> 1, (top+bottom) >> 1 );
}
void Deflate(int l, int t, int r, int b)
{
left += l;
top += t;
right -= r;
bottom -= b;
}
void Inflate(int l, int t, int r, int b)
{
left -= l;
top -= t;
right += r;
bottom += b;
}
int Width() const
{
return right - left;
}
int Height() const
{
return bottom - top;
}
const CPoint& TopLeft() const
{
return *((CPoint*)this);
}
void Offset(int x, int y)
{
left += x;
right += x;
top += y;
bottom += y;
}
void Invalidate()
{
left = 0;
top = 0;
right = 0;
bottom = 0;
}
bool IsValid() const
{
return right > left;
}
void operator |= (const CRect& rcUnion)
{
if ( !IsValid() )
{
*this = rcUnion;
return;
}
left = min(left, rcUnion.left);
top = min(top, rcUnion.top);
right = max(right, rcUnion.right);
bottom = max(bottom, rcUnion.bottom);
}
CRect operator +(const CPoint& cp)
{
CRect rcNew( *this );
rcNew.left += cp.x;
rcNew.right += cp.x;
rcNew.top += cp.y;
rcNew.bottom += cp.y;
return rcNew;
}
bool IsInside( int x, int y )
{
if ( x < left || x >= right || y < top || y >= bottom )
return false;
return true;
}
bool operator == (const CRect& rcTest)
{
return left == rcTest.left &&
right == rcTest.right &&
top == rcTest.top &&
bottom == rcTest.bottom;
}
int left, top, right, bottom;
};
template <class TYPE>
class CArray
{
TYPE *m_arrElements;
ui16 m_nCount;
ui16 m_nMaxCount;
typedef int (*TCompareFunction)(TYPE&, TYPE&);
public:
CArray()
{
}
CArray( TYPE *pSource, int nLength, int nCount = 0 )
{
m_arrElements = pSource;
m_nCount = nCount;
m_nMaxCount = nLength;
}
void Init( TYPE *pSource, int nLength, int nCount = 0 )
{
m_arrElements = pSource;
m_nCount = nCount;
m_nMaxCount = nLength;
}
BOOL IsEmpty()
{
_ASSERT( m_arrElements );
return m_nCount == 0;
}
void Add(TYPE t)
{
_ASSERT( m_nCount < m_nMaxCount );
m_arrElements[m_nCount++] = t;
}
TYPE &GetLast()
{
_ASSERT( m_nCount > 0 );
return m_arrElements[m_nCount-1];
}
const TYPE &GetLast() const
{
_ASSERT( m_nCount > 0 );
return m_arrElements[m_nCount-1];
}
// returns pointer to existing element
TYPE& RemoveLast()
{
_ASSERT( m_nCount > 0 );
return m_arrElements[--m_nCount];
}
void Resize( int nDif )
{
m_nCount += nDif;
_ASSERT( m_nCount >= 0 && m_nCount <= m_nMaxCount );
}
int GetSize() const
{
return m_nCount;
}
int GetMaxSize()
{
return m_nMaxCount;
}
void SetSize( int nSize )
{
m_nCount = nSize;
}
TYPE& operator []( int i)
{
if ( i < 0 )
i += m_nCount;
_ASSERT( i >= 0 && i < GetSize() );
return m_arrElements[i];
}
const TYPE& operator []( int i) const
{
if ( i < 0 )
i += m_nCount;
_ASSERT( i >= 0 && i < GetSize() );
return m_arrElements[i];
}
void RemoveAt( int i )
{
_ASSERT( i < GetSize() );
for ( ; i < GetSize()-1; i++ )
m_arrElements[i] = m_arrElements[i+1];
Resize(-1);
}
void InsertAt( int i, const TYPE& element )
{
int nSize = GetSize();
_ASSERT( i <= nSize );
Resize(+1);
for ( int j = nSize-1; j >= i; j-- )
m_arrElements[j+1] = m_arrElements[j];
m_arrElements[i] = element;
}
void RemoveAll()
{
m_nCount = 0;
}
void Sort(TCompareFunction fCompare)
{
for ( int i=0; i<m_nCount; i++)
for ( int j=i+1; j<m_nCount; j++)
if ( fCompare( m_arrElements[i], m_arrElements[j] ) < 0 )
{
TYPE tTemp = m_arrElements[i];
m_arrElements[i] = m_arrElements[j];
m_arrElements[j] = tTemp;
}
}
int Find(TYPE& value)
{
for (int i=0; i<m_nCount; i++)
if (m_arrElements[i] == value)
return i;
return -1;
}
TYPE* GetData()
{
return m_arrElements;
}
void Copy(const CArray<TYPE>& source)
{
SetSize(source.GetSize());
for (int i=0; i<source.GetSize(); i++)
m_arrElements[i] = source.m_arrElements[i];
}
bool operator ==(const CArray<TYPE>& other)
{
if (GetSize() != other.GetSize())
return false;
for (int i=0; i<GetSize(); i++)
if (m_arrElements[i] != other.m_arrElements[i])
return false;
return true;
}
};
// Date: 2023-08-03
// Created by: Grzegorz Rajtar, grzegorz@rajtar.info
/*
Microchip HCS200/HCS300 KeeLoq Code Hopping Encoder based remotes.
66 bits transmitted, LSB first.
| 0-31 | Encrypted Portion
| 32-59 | Serial Number
| 60-63 | Button Status (S3, S0, S1, S2)
| 64 | Battery Low
| 65 | Fixed 1
*/
class CHcs200 : public CProtocol
{
public:
virtual int Frequency() override
{
return 433920000;
}
virtual void Example(CAttributes& attributes) override
{
attributes["length"] = 66;
attributes["data_0"] = 0x40a444d8;
attributes["data_1"] = 0x27131efc;
attributes["data_2"] = 0x3;
}
virtual bool Demodulate(const CArray<uint16_t>& pulse, CAttributes& attributes) override
{
uint8_t nibblesData[20];
CArray<uint8_t> b(nibblesData, COUNT(nibblesData));
int length = 0;
if (!PulseToBytes(pulse, b, length))
return false;
BitstreamToAttributes(b, length, attributes);
Analyse(attributes, b, length);
return true;
}
void Analyse(CAttributes& attributes, CArray<uint8_t>& b, int length)
{
int btn;
if (length < 66)
return;
if (b.GetSize() < 9) /*66 bit-s, 8bytes and a bit more*/
{
_ASSERT(0);
return;
}
}
void BitstreamToAttributes(CArray<uint8_t>& b, int bitLength, CAttributes& attributes)
{
int btn;
if (bitLength < 66)
{
BIOS::DBG::Print("\nbitLength: %d", bitLength);
_ASSERT(0);
return;
}
if (b.GetSize() < 9)
{
BIOS::DBG::Print("\nbSize: %d", b.GetSize());
_ASSERT(0);
return;
}
attributes["length"] = bitLength; // count of bits
attributes["encrypted"] = ((unsigned)(reverse8(b[3]) << 24) | (reverse8(b[2]) << 16) | (reverse8(b[1]) << 8) | (reverse8(b[0])));
attributes["serial"] = (reverse8(b[7] & 0xf0) << 24) | (reverse8(b[6]) << 16) | (reverse8(b[5]) << 8) | (reverse8(b[4]));
btn = (b[7] & 0x0f);
attributes["btn"] = btn;
attributes["btn_num"] = (btn & 0x08) | ((btn & 0x01) << 2) | (btn & 0x02) | ((btn & 0x04) >> 2); // S3, S0, S1, S2
attributes["learn"] = (b[7] & 0x0f) == 0x0f;
attributes["battery_low"] = (b[8] & 0x80) == 0x80;
attributes["repeat"] = (b[8] & 0x40) == 0x40;
/*
attributes["data_0"] = attributes["encrypted"];
attributes["data_1"] = attributes["serial"];
attributes["data_2"] = b[8] & 0xC0;*/
}
void AttributesToBitstream(const CAttributes& attributes, CArray<uint8_t>& b, int& bitLength)
{
bitLength = attributes["length"];
// TE = 400us
//todo preamble 23-pulses 12-high,11-low 23 * TE
//todo header 10 * TE
uint32_t encrypted = attributes["encrypted"];
int serial = attributes["serial"];
int btn = attributes["btn"];
int btn_num = attributes["btn_num"];
int learn = attributes["learn"];
int battery_low = attributes["battery_low"];
int repeat = attributes["repeat"];
int status;
//encrypted
b.Add(reverse8((encrypted >> 24) && 0xff)); //0
b.Add(reverse8(encrypted >> 16) && 0xff);
b.Add(reverse8(encrypted >> 8) && 0xff);
b.Add(reverse8(encrypted & 0xff)); //3
//serial
b.Add(reverse8(serial & 0xff)); //4
b.Add(reverse8((serial >> 8) & 0xff));
b.Add(reverse8((serial >> 16) & 0xff)); //6
//serial-msb + btn
if (learn)
btn = 0x0f;
b.Add(reverse8( ((serial >> 24) & 0xf0)) | (btn & 0x0f)); //7
status = 0;
if (battery_low)
status |= 0x80;
if (repeat)
status |= 0x40;
b.Add(reverse8(status & 0xff)); //8
}
virtual bool Modulate(const CAttributes& attr, CArray<uint16_t>& pulse) override
{
uint8_t nibblesData[9];
CArray<uint8_t> b(nibblesData, COUNT(nibblesData));
int length = 0;
AttributesToBitstream(attr, b, length);
return BytesToPulse(b, length, pulse);
}
virtual int PulseDivisor() override { return 370; }
private:
unsigned char reverse8(unsigned char b) {
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
return b;
}
int PulseLen(int microseconds)
{
return (microseconds+100)/370;
}
int PulseDuration(int ticks)
{
return ticks*370;
}
bool PulseToBytes(const CArray<uint16_t>& pulse, CArray<uint8_t>& bytes, int& length)
{
int i;
int preambule = 0;
for (i=0; i<pulse.GetSize()-33; i++)
{
int t = PulseLen(pulse[i]);
if (t >= 1 && t <=2)
{
preambule++;
if (preambule == 23)
{
i++;
break;
}
}
else {
preambule = 0;
}
}
if (preambule < 23) // should be 23*Te
return false;
//should be 10*Te
if (PulseLen(pulse[i]) >=9 && PulseLen(pulse[i]) <=11)
i++;
else
return false;
length = 0;
int bits = 0;
bool buffer_fix = false;
for (; i<pulse.GetSize()-1; i+=2)
{
int p0 = PulseLen(pulse[i]);
int p1 = PulseLen(pulse[i+1]);
if (p0==2 && p1==1)
{
length++;
bits <<= 1;
bits |= 0;
}
else if (p0==1 && p1==2)
{
length++;
bits <<= 1;
bits |= 1;
}
else
{
return false;
}
if ((length & 7) == 0)
{
// swap nibbles
bits = reverse8(bits);
bytes.Add(bits);
bits = 0;
}
//next item is last, final zero not in buffer ?
if (pulse.GetSize() - i == 3)
buffer_fix = true;
}
if (buffer_fix)
{
int p0 = PulseLen(pulse[i]);
if (p0==2 /*&& p1==1*/)
{
length++;
bits <<= 1;
bits |= 0;
}
else if (p0==1 /*&& p1==2*/)
{
length++;
bits <<= 1;
bits |= 1;
}
else
{
return false;
}
if ((length & 7) == 0)
{
// swap nibbles
bits = reverse8(bits);
bytes.Add(bits);
bits = 0;
}
}
if ((length & 7) != 0)
{
bits = reverse8(bits);
bytes.Add(bits);
}
return true;
}
bool BytesToPulse(const CArray<uint8_t>& bytes, int length, CArray<uint16_t>& pulse)
{
const int preambule = 23; // 23 * TE = 400us 101010.....?
for (int i=0; i <preambule; i++)
pulse.Add(PulseDuration(1));
pulse.Add(PulseDuration(10));
for (int i=0; i<length; i++)
{
int bit = (bytes[i/8] >> (i&7)) & 1;
if (bit)
{
pulse.Add(PulseDuration(1));
pulse.Add(PulseDuration(2));
} else
{
pulse.Add(PulseDuration(2));
pulse.Add(PulseDuration(1));
}
}
return true;
}
virtual void GetName(char* name) override
{
strcpy(name, "Keeloq HCS200/300");
}
virtual void GetDescription(CAttributes& attributes, char* desc) override
{
/*
attributes["length"] = bitLength; // count of bits
attributes["encrypted"] = ((unsigned)(reverse8(b[3]) << 24) | (reverse8(b[2]) << 16) | (reverse8(b[1]) << 8) | (reverse8(b[0])));
attributes["serial"] = (reverse8(b[7] & 0xf0) << 24) | (reverse8(b[6]) << 16) | (reverse8(b[5]) << 8) | (reverse8(b[4]));
btn = (b[7] & 0x0f);
attributes["btn"] = btn;
attributes["btn_num"] = (btn & 0x08) | ((btn & 0x01) << 2) | (btn & 0x02) | ((btn & 0x04) >> 2); // S3, S0, S1, S2
attributes["learn"] = (b[7] & 0x0f) == 0x0f;
attributes["battery_low"] = (b[8] & 0x80) == 0x80;
attributes["repeat"] = (b[8] & 0x40) == 0x40;
*//*
if (attributes.indexOf("encrypted") != -1)
sprintf(desc, "encrypted: <%08x>", (int)attributes["encrypted"]);
if (attributes.indexOf("serial") != -1)
sprintf(desc, "serial: <%07x>", (int)attributes["serial"]);
else*/
sprintf(desc, "%d bits: <%08x %07x %04x %01x %01x>",
(int)attributes["length"], (int)attributes["encrypted"], (int)attributes["serial"],
((uint16_t)attributes["btn"]), ((uint8_t)attributes["battery_low"]), ((uint8_t)attributes["repeat"]));
}
virtual const char* GetString(int i) override
{
return nullptr;
}
};
namespace BIOS
{
namespace DBG
{
void Print(const char * format, ...)
{
char buf[1024];
va_list args;
va_start( args, format );
vsprintf( buf, format, args );
printf("%s", buf);
va_end(args);
}
}
}
extern "C" {
void _HandleAssertion(const char* file, int line, const char* cond)
{
BIOS::DBG::Print("Assertion failed in ");
BIOS::DBG::Print(file);
BIOS::DBG::Print(" [%d]: %s\n", line, cond);
#ifdef __APPLE__
//kill(getpid(), SIGSTOP);
#endif
while (1);
}
}
struct TKeyValue
{
char* key;
uintptr_t value;
};
class CAttributes : public CArray<TKeyValue>
{
public:
typedef TKeyValue TAttribute;
public:
CAttributes(TAttribute* attr, int count) : CArray<TKeyValue>(attr, count)
{
}
uintptr_t& operator[](const char* key)
{
for (int i=0; i<GetSize(); i++)
if (strcmp(CArray<TKeyValue>::operator[](i).key, key) == 0)
return CArray<TKeyValue>::operator[](i).value;
Add(TKeyValue{(char*)key, 0});
return GetLast().value;
}
int operator[](const char* key) const
{
for (int i=0; i<GetSize(); i++)
if (strcmp(CArray<TKeyValue>::operator[](i).key, key) == 0)
return CArray<TKeyValue>::operator[](i).value;
_ASSERT(0);
return 0;
}
const TKeyValue& operator[](int i) const
{
return CArray<TKeyValue>::operator[](i);
}
TKeyValue& operator[](int i)
{
return CArray<TKeyValue>::operator[](i);
}
int indexOf(const char* key)
{
for (int i=0; i<GetSize(); i++)
if (strcmp(CArray<TKeyValue>::operator[](i).key, key) == 0)
return i;
return -1;
}
};
class CProtocol
{
public:
virtual int Frequency() = 0;
/*
// TODO: remove
virtual int MinIndentifyCount() { return 0; }
virtual int MinDemodulateCount() { return 0; }
virtual int AttackPoint(CArray<int>& pulse) { return 0; }
virtual bool Identify(CArray<int>& pulse) { return false; }
*/
virtual void Example(CAttributes& attributes) = 0;
virtual bool Demodulate(const CArray<uint16_t>& pulse, CAttributes& attributes) = 0;
virtual bool Modulate(const CAttributes& attr, CArray<uint16_t>& pulse) = 0;
virtual void Synthesize(CAttributes& attr) {}
virtual void GetName(char*) = 0;
virtual void GetDescription(CAttributes& attributes, char* desc) = 0;
virtual const char* GetString(int i) = 0;
virtual int PulseDivisor() = 0;
void PulseToBitstream(const CArray<uint16_t>& pulse, CArray<uint8_t>& bitstream, int interval)
{
int n = 0;
for (int i=0; i<pulse.GetSize(); i++)
{
for (int j=0; j<pulse[i]; j+=interval, n++)
{
int bit = 1-(i&1);
int byteIndex = n >> 3;
int bitIndex = n & 7;
if (bitIndex == 0)
bitstream.Add(0);
bitstream[byteIndex] |= bit << (7-bitIndex);
}
}
}
void BitstreamToAttributes(CArray<uint8_t>& b, int bitLength, CAttributes& attributes)
{
attributes["length"] = bitLength; // count of bits
uint32_t data=0;
int bytes = b.GetSize();
for (int i=0; i<bytes; i++) // per each byte
{
bool last = i==bytes-1;
data |= b[i] << (8*(3-(i&3)));
if ((i&3)==3 || last)
{
switch (i/4) // store as dword
{
case 0: attributes["data_0"] = data; break;
case 1: attributes["data_1"] = data; break;
case 2: attributes["data_2"] = data; break;
default: _ASSERT(0);
}
data = 0;
}
}
}
void AttributesToBitstream(const CAttributes& attributes, CArray<uint8_t>& b, int& bitLength)
{
bitLength = attributes["length"];
uint32_t data;
int bytes = (bitLength+7)/8;
for (int i=0; i<bytes; i++)
{
if ((i&3) == 0)
{
switch (i/4)
{
case 0: data = attributes["data_0"]; break;
case 1: data = attributes["data_1"]; break;
case 2: data = attributes["data_2"]; break;
default: _ASSERT(0);
}
}
b.Add(data >> 24);
data <<= 8;
}
}
};
g++ -std=c++11 ./test.cpp -o test
./test
#include <stdio.h>
#include <stdarg.h>
#include "library.h"
#include "Classes.h"
#include "protocol.h"
#include "hcs.h"
int main(void)
{
uint16_t pulses[] = { 360,380,340,380,360,360,360,360,360,360,360,360,360,360,360,360,380,360,360,360,360,360,360,3820,360,780,360,760,740,380,740,380,740,380,360,760,380,760,740,380,360,760,740,380,360,760,740,400,360,760,740,380,360,760,740,400,720,400,340,780,360,760,360,760,360,760,380,760,360,760,360,760,360,780,720,400,740,380,360,760,360,760,740,380,760,380,360,760,740,380,360,780,340,780,360,760,740,380,740,400,720,400,740,380,740,380,740,400,720,400,340,780,740,380,740,380,740,380,360,760,760,380,360,760,740,380,360,760,360,780,740,380,740,380,740,380,740,380,760,380,740,380,740,380,740,380,360,780,740,380,740,380,740 };
CArray<uint16_t> pulse(pulses, COUNT(pulses));
CHcs200 hcs;
CAttributes::TAttribute attributesData[20];
CAttributes attributes(attributesData, COUNT(attributesData));
bool status = hcs.Demodulate(pulse, attributes);
printf("decode = %d\n", status);
for (int i=0; i<attributes.GetSize(); i++)
printf("%s = %x\n", attributes[i].key, attributes[i].value);
return 0;
};
#pragma once
#include <assert.h>
#ifdef __cplusplus
#include <algorithm>
#endif
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C"
#endif
void _HandleAssertion(const char*, int, const char*);
#define _STR(x) #x
#ifndef _ASSERT
#define _ASSERT(e) {if(!(e)) _HandleAssertion(__FILE__, __LINE__, _STR(e)); }
#endif
#define ToWord(a, b) (ui16)(((a)<<8)|(b))
#define ToDword(a, b, c, d) (ui32)((ToWord(d, c)<<16)|ToWord(b,a))
#ifdef __APPLE__
#define min(a,b) std::min(a,b)
#define max(a,b) std::max(a,b)
#else
#define min(a,b) (((a)<(b))?(a):(b))
#define max(a,b) (((a)>(b))?(a):(b))
#endif
#define COUNT(arr) (int)(sizeof(arr)/sizeof(arr[0]))
#ifdef __APPLE__
#define NATIVEENUM uint32_t
#elif WIN32
#define NATIVEENUM uint32_t
#else
#define NATIVEENUM uint8_t
#endif
#define NATIVEPTR uintptr_t
// TODO: remove these types
typedef unsigned char ui8;
typedef unsigned short ui16;
typedef void* PVOID;
#ifdef WIN32
typedef int BOOL;
#else
typedef bool BOOL;
#endif
typedef const char * PCSTR;
typedef char * PSTR;
#ifndef WIN32
typedef uint32_t UINT;
#endif
#define EVERY(ms) static long dwTick##__LINE__ = 0; bool bDo##__LINE__ = BIOS::SYS::GetTick() - dwTick##__LINE__ > ms; if (bDo##__LINE__) dwTick##__LINE__ = BIOS::SYS::GetTick(); if (bDo##__LINE__)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment