Showing posts with label python. Show all posts
Showing posts with label python. Show all posts

2011-01-25

NGE101 – Norgo wireless energy meter (part 6)

In the previous posts I showed the bits in the captured frames in the order they were captured and decoded, ie. the first received bit were shown to the left.
When I managed to decode the main payload in the frames, I learned that the frames are send with the least significant bit (lsb) first, so I was actually showing the captured frames in "reverse". This ended up being a bit confusing since binary number are normally shows with the msb at the left, and lsb at the right, so in this post I will show the frames reverse compared to the previous posts, so what looked like:

preamble/header      payload                              checksum
11111010100011001001 110010111110011111010000000000000000 110100111110010
11111010100011001001 000101111110011111010000000000000000 000111100001010
11111010000011001001 00100111111100000000                 111000010110010
11111010000011001001 01100111111100000000                 111001010010000

will now look like this:

checksum        payload                              header/preamble
010011111001011 000000000000000010111110011111010011 10010011000101011111
010100001111000 000000000000000010111110011111101000 10010011000101011111
010011010000111                 00000000111111100100 10010011000001011111
000010010100111                 00000000111111100110 10010011000001011111

The first thing I wanted to figure out, was how the checksum should be calculated, so I started by capturing and looking at thousands of frames to see if I could find a pattern.

Here are a few frame pairs where just the lowest bit in the payload are different:

111010001010001 00000000110010001010 10010011000001011111
110010101000001 00000000110010001011 10010011000001011111

110000011001000 00000000110100010010 10010011000001011111
111000111011000 00000000110100010011 10010011000001011111

010010010001000 00000000110100010110 10010011000001011111
011010110011000 00000000110100010111 10010011000001011111

If you look closely at the checksum, you might notice that the xor of the two checksums in each pair is the same value: 001000100010000.

More frames with just one flipped bit in the payload:

  111111101010010 00000000111110111001 10010011000001011111
^ 101110101110010 00000000111110111011 10010011000001011111
= 010001000100000

  110100001000101 00000000111111001000 10010011000001011111
^ 100101001100101 00000000111111001010 10010011000001011111
= 010001000100000

  100010000111000 00000001000000000000 10010011000001011111
^ 000000001111000 00000001000000000100 10010011000001011111
= 100010001000000

  000110110111001 00000001000000011000 10010011000001011111
^ 100100111111001 00000001000000011100 10010011000001011111
= 100010001000000

After some time I managed to find these patterns:

111110000000001 - payload bit 12
011011010000000 - payload bit 11
001101101000000 - payload bit 10
110110100100000 - payload bit 9
001011000010000 - payload bit 8
100101100001000 - payload bit 7
100010100000100 - payload bit 6
000001000000010 - payload bit 5
100000100000001 - payload bit 4
000100010000000 - payload bit 3
100010001000000 - payload bit 2
010001000100000 - payload bit 1
001000100010000 - payload bit 0
110100000001000 - header bit n
111010000000100 - header bit n-1
011101000000010 - header bit n-2

To me, it looked very much like these xor patterns were generated by a "many-to-many" linear feedback shift register (lfsr), but no matter now much I tried, I could not figure it out.

So to crack the lsfr, I wrote a c++ program that used a genetic algorithm to find the lsfr tabs:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <algorithm>
 
typedef unsigned int uint;
 
uint patterns[] = {
  0b111110000000001, 0b011011010000000, 0b001101101000000, 0b110110100100000,
  0b001011000010000, 0b100101100001000, 0b100010100000100, 0b000001000000010,
  0b100000100000001, 0b000100010000000, 0b100010001000000, 0b010001000100000,
  0b001000100010000, 0b110100000001000, 0b111010000000100, 0b011101000000010,
};
static const uint pattern_cnt = (sizeof(patterns)/sizeof(patterns[0]));
 
uint calcErrors(uint mask[15])
{
  unsigned errors = 0;
  for(unsigned i=0; i<pattern_cnt-1; i++)
  {
    unsigned ipattern = patterns[i];
    unsigned opattern = patterns[i+1];
    unsigned pattern = ipattern;
    pattern = (pattern>>1);
    for(int biti=0; biti<15; biti++)
    {
      if(ipattern&(1<<biti))
        pattern ^= mask[biti];
    }
    errors += __builtin_popcount(pattern^opattern);
  }
 
  return errors;
}
 
struct genome
{
  uint mask[15];
  uint errors;
  bool operator<(const genome &other) const { return errors<other.errors; }
};
 
static const uint POP_SIZE = 10000;
 
int main()
{
  srand(time(NULL));
 
  static genome population[POP_SIZE];
  for(uint i=0; i<POP_SIZE; i++)
    for(uint j=0; j<15; j++)
      population[i].mask[j] = rand()&0x7fff;
 
  for(uint generation=0; generation<1000000; generation++)
  {
    for(uint i=0; i<POP_SIZE; i++)
      population[i].errors = calcErrors(population[i].mask);
 
    // sort genomes, so that the most fit will be in the beginning of the array
    std::sort(population, population+POP_SIZE);
 
    if(population[0].errors == 0)
    {
      for(uint j=0; j<15; j++) printf("%04x ", population[0].mask[j]);
      printf("\n");
      break;
    }
 
    static genome newpopulation[POP_SIZE];
    for(uint i=0; i<POP_SIZE; i++)
    {
      uint parent1 = pow(double(rand())/RAND_MAX, 3.0)*POP_SIZE;
      uint parent2 = pow(double(rand())/RAND_MAX, 3.0)*POP_SIZE;
 
      // produce offspring:
      memcpy(&newpopulation[i], &population[parent1], sizeof(newpopulation[i]));
      for(uint j=0; j<15; j++)
        if(rand()<RAND_MAX/2)
          newpopulation[i].mask[j] = population[parent2].mask[j];
 
      // mutate offspring
      if(rand()<RAND_MAX/10)
      {
        uint bit = rand()%15;
        uint mask = rand();
        for(int j=0; j<15; j++)
          if((mask>>j)&1)
            newpopulation[i].mask[j] ^= 1<<(bit%15);
      }
    }
    memcpy(population, newpopulation, sizeof(population));
  }
   return 0;
}

It only took the program around 15 seconds to find the correct "taps": 0x4880 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x2080 0x4000 0x4000 0x4000 0x4000 0x4000 0x4000.

To verify that I now had enough information to verify the checksum of captured frames, I wrote another small python script that calculates the checksum for a frame, and checks itagains the checksum in the captured frame:

checksum_taps = (0x4880, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
  0x2080, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000)
 
def next_mask(mask):
  next_mask = mask>>1
  for i in range(15):
    if mask&(1<<i):
      next_mask ^= checksum_taps[i]
  return next_mask
 
def verify(checksum, data, datalen):
  mask = 0x0001
  cchecksum = 0
  for i in range(datalen-1, 7, -1):
    mask = next_mask(mask)
    if (data>>i)&1:
      cchecksum ^= mask
  assert checksum == cchecksum
 
#        |checksum-----|    |payload---------------------------||header----||preamb|
verify(0b010011111001011, 0b00000000000000001011111001111101001110010011000101011111, 36+20)
verify(0b010100001111000, 0b00000000000000001011111001111110100010010011000101011111, 36+20)
verify(0b010011010000111,                 0b0000000011111110010010010011000001011111, 20+20)verify(0b000010010100111,                 0b0000000011111110011010010011000001011111, 20+20

By trial and error, I also managed to find out that the preamble is 8 bits long, and not included in the checksum, so the packages now look like this:

checksum        payload                              header       preamble
010011111001011 000000000000000010111110011111010011 100100110001 01011111
010100001111000 000000000000000010111110011111101000 100100110001 01011111
010011010000111                 00000000111111100100 100100110000 01011111
000010010100111                 00000000111111100110 100100110000 01011111

Now that I can generate frames with correct checksums, I can start sending my own frames to the NG101 receiver and see how it reacts when the different bits are set. This will make it easier to discover what all the bits the the header and payload are used for.
But that will have to wait for another day :)

2011-01-06

NGE101 – Norgo wireless energy meter (part 3)

Before decoding the data from the NGE101, it was necessary to find out the duration of the pulses it sends. To do this I needed a small sketch that could output the exact duration of the received  pulses.

This is the code I came up with, it will print a stream of bits and there durations in microseconds.

uint8_t lastinput = LOW;
uint32_t lastinputat;

void setup()
{
  pinMode(2, INPUT);
  Serial.begin(115200*8);
  lastinputat = micros();
}

void loop()
{
  uint8_t input = digitalRead(2);
  if(input != lastinput)
  {
    uint32_t now = micros();
    uint32_t dt = now - lastinputat;
    lastinput = input;
    lastinputat = now;
    
    Serial.print(input, 10);
    Serial.print(' ');
    Serial.println(dt, 10);
    }
  }

The output looks something like this:
1 1068
0 472
1 488
0 416
1 568
...

The durations were not as consistent as I had hoped they would be, so to get a better idea of what I was dealing with, I wrote a python gui application to graph the durations.

import wx 
import re 
import serial 
import sys 
import time 
import math 

WIN_HEIGHT = 128 
WIN_WIDTH = 590 # pixels 
DURATION_PER_PIXEL = 0.003 / (WIN_WIDTH-1) 

class TestFrame(wx.Frame): 
  def __init__(self): 
    wx.Frame.__init__(self, None, title='test', size=(WIN_WIDTH, WIN_HEIGHT)) 

    self.line_re = re.compile('([01]) *([0-9]+)') 

    self.ser = serial.Serial('/dev/ttyACM0', 115200*8, timeout=0.000) 
    self.last = None 
    self.recvqueue = '' 
    self.durations = [[0]*WIN_WIDTH, [0]*WIN_WIDTH] 

    self.panel = wx.Panel(self, size=(WIN_WIDTH, WIN_HEIGHT)) 
    self.panel.Bind(wx.EVT_PAINT, self.on_paint) 
    self.Fit() 
    self.Show(True) 

    self.timer = wx.Timer(self.panel, 123) 
    wx.EVT_TIMER(self.panel, 123, self.on_timer) 
    self.timer.Start(10) 

  def on_paint(self, event): 
    dc = wx.PaintDC(self.panel) 

    dc.SetPen(wx.Pen('red', 1)) 
    dc.DrawLine(0, WIN_HEIGHT/2, WIN_WIDTH, WIN_HEIGHT/2) 
    for i in range(0, WIN_WIDTH, 1): 
      t = i*DURATION_PER_PIXEL 
      if math.floor(t/0.001) != math.floor((t-DURATION_PER_PIXEL)/0.001): 
        dc.DrawLine(i, 0, i, WIN_HEIGHT) 
      elif math.floor(t/0.0001) != math.floor((t-DURATION_PER_PIXEL)/0.0001): 
        dc.DrawLine(i, WIN_HEIGHT/2-8, i, WIN_HEIGHT/2+8) 

    dc.SetPen(wx.Pen('black', 1)) 
    for i in range(WIN_WIDTH): 
      dc.DrawLine(i, WIN_HEIGHT/2+1, i, WIN_HEIGHT/2+1+self.durations[0][i]) 
      dc.DrawLine(i, WIN_HEIGHT/2-1, i, WIN_HEIGHT/2-1-self.durations[1][i]) 

  def on_timer(self, event): 
    redraw = False 

    while True: 
      line = self.ser.readline() 
      if not line: break 
      m = self.line_re.match(line) 
      if not m: continue 

      bit = int(m.group(1)) 
      duration = float(m.group(2))/1000000.0 
      print bit, duration 

      self.durations[bit][min(int(duration/DURATION_PER_PIXEL), WIN_WIDTH-1)] += 1 
      redraw = True 

    if redraw: 
      self.panel.Refresh() 

app = wx.App(False) 
frame = TestFrame() 

app.MainLoop()

The result looked like this. The window covers a durations of 3 milliseconds, so there is a tall vertical line per millisecond.

It's now possible to see that the durations fall into 4 groups:

  • short high pulses (logic level 1) in the range 400-600 milliseconds.

  • short low pulses (logic level 0) in the range 300-500 milliseconds.

  • long high pulses in the range 900-1100 milliseconds.

  • long low pulses in the range 800-1000 milliseconds.

I suspect the difference in timing from the high and low pulses is because the sender has an extremely slow cpu, and it spends a few more cycles when sending a 1 than sending a 0. A bit of goggling suggest that 32.768 kHz is very common speed for low powered embedded micro controllers. At that speed, the time difference (0.15 ms) between high and low pulses are just around 5 cycles.

Visualizing the data as a pulse train, using __, _ for low pulses, and --, - for high pulses, a pattern start to revel itself. Here are 4 bursts of data:
__--__--__-_--_-_-_-_-_-__--_-_-__-_-_--__--_-__-_--__--_-__-_-_--_-_-_-_-_-_-_-_-__--_-_-__-_--_-__--__--_-_-

__--__--__-_--_-_-_-_-_-__--_-_-__-_-_--_-_-__-_-_-_-_-_--__-_-_--_-_-_-_-_-_-_-_-_-_-__-_-_--_-__--_-__--__--

__--__--__-_--_-_-_-_-_-__--_-_-__-_-_--__-_-_--_-_-_-_-__--_-_-__-_-_-_-_-_-_-_-_-_-_--__-_-_--_-__--_-_-__-_

__--__--__-_--_-_-_-_-_-__--_-_-__-_-_--__-_--_-_-__-_-_--__-_-_--_-_-_-_-_-_-_-_-__-_--__-_--_-_-_-_-__-_--__

The script used to print these pulse trains:

import serial
import sys
import time
 
serdev = '/dev/ttyACM0'
ser = serial.Serial(serdev, 115200*8)
 
def decode(bit, duration):
  if bit==1 and duration>=400 and duration<=600: return '-'
  if bit==1 and duration>=900 and duration<=1100: return '--'
  if bit==0 and duration>=300 and duration<=500: return '_'
  if bit==0 and duration>=800 and duration<=1000: return '__'
  return None
 
while True:
  line = ser.readline().strip()
  pulse = decode(int(line[0]), int(line[2:]))
  if pulse: sys.stdout.write(pulse)
  else: sys.stdout.write('\n')  sys.stdout.flush()
A examination of the pulse trains reveals that short pulses always come in pairs! This suggest that the data is likely to be Manchester encoded, or more likely Differential Manchester encoded. After applying a differential Manchester decoder to the data, we see some very promising patterns:
1111101000001100100111011011000010000000101000010000110
(100 ms delay)
1111101000001100100111011011000010000000101000010000110
(43 s delay)

11111010100011001001101000100100100111010000000000000000101011110111110
1111101000001100100111100011000010000000001000101011110

1111101000001100100110011101000010000000110001011010101
1111101000001100100110011101000010000000110001011010101

11111010100011001001010110100100100111010000000000000000001000001101111
1111101000001100100110000011000010000000001001001101101

1111101000001100100111000011000010000000001000001001111
1111101000001100100111000011000010000000001000001001111

11111010100011001001011101100100100111010000000000000000111000101001001
1111101000001100100111011101000010000000110000011110111
The data is being send it bursts with around 43 seconds between them. In each burst there are two frames, separated by around 100 ms. As can be seen, every second burst have a longer frame than usual, the other bursts just appear to be repeating the same frame twice. The differential Manchester decoder:
lastshort = False
while True:
  line = ser.readline().strip()
  pulse = decode(int(line[0]), int(line[2:]))
 
  if pulse in ('_', '-'):
    if lastshort:
      sys.stdout.write('0')
    lastshort = not lastshort
  elif pulse in ('__', '--'):
    if lastshort:
      print 'ERROR'
      lastshort = False
    else:
      sys.stdout.write('1')
  else:
    lastshort = False
    sys.stdout.write('\n')  sys.stdout.flush()
That's it for now, next time I will try to make some sense of the newly discovered bits...