Считывание показаний и программирование электросчетчика "Энергомера СЕ102М" по rs-485

Написал скрипт на Питоне для сабжа.

Подключаем выводы A, B и GND WirenBoard к контактам 9, 10 и 11 счетчика.

Для чтения данных запускаем скрипт из консоли без ключей или с ключом -r.
Для программирования запускаем с ключом -p.

Систему команд смотрим в инструкции к счетчику.
Протокол обмена данными: ГОСТ Р МЭК 61107-2001.

#!/usr/bin/python
# coding: utf-8
import sys, io, serial

# Если нет ключей, то режим чтения по умолчанию
# -r : чтение ограниченного набора параметров
# -p : режим программирования (отменяет -r)
# -s : "silent mode", только для -p, выводятся только данные

read_flag = '0'
if '-r' in sys.argv:
   read_flag = '6'
if '-p' in sys.argv:
   read_flag = '1'
   silent = '-s' in sys.argv


# Параметры последовательного порта: 9600 бод, 7E1, таймаут 0.1 с
ser = serial.Serial('/dev/ttyNSC0', bytesize=serial.SEVENBITS, parity=serial.PARITY_EVEN, timeout = 0.1)
sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser), newline = '')


# Чтение посылки с проверкой LRC
def data_decode(sdata):
   msg = dict()
   msg['head'] = ''
   msg['body'] = ''
   msg['lrc'] = False

# Ничего не меняем, если пришли служебные символы (ACK, NAK)
   if len(sdata) <= 1:
      msg['body'] = sdata
      msg['lrc'] = True

   else:
      lrc = 0x00
      head_add = False
      body_add = False
      lrc_add = False

# Считаем LRC (Сложение всех байт после (SOH) или (STX), не включая,
# до (ETX) включительно по модулю 0x7f, одновременно читаем заголовок и данные
      for i in range(0,len(sdata)-1):

# Обнаружен (SOH)
         if sdata[i] == '\x01':
            head_add = True
            lrc_add = True

# Обнаружен (STX)
         elif sdata[i] == '\x02':
            head_add = False
            body_add = True
            if lrc_add:
               lrc = (lrc + ord(sdata[i])) & 0x7f
            else:
               lrc_add = True

# Обнаружен (ETX)
         elif sdata[i] == '\x03':
            head_add = False
            body_add = False
            lrc_add = False
            lrc = (lrc + ord(sdata[i])) & 0x7f

         else:
            if head_add:
               msg['head'] += sdata[i]
            elif body_add:
               msg['body'] += sdata[i]
            if lrc_add:
               lrc = (lrc + ord(sdata[i])) & 0x7f

# Проверяем последний байт посылки на соответствие вычисленому LRC
      msg['lrc'] = lrc == ord(sdata[len(sdata) - 1])

   return msg



# Запись посылки в строку с добавлением вычисленного LRC
def data_encode(msg):
   sdata = ''
   if msg['head']:
      sdata += '\x01' + msg['head']
   if msg['body']:
      sdata += '\x02' + msg['body']
   sdata += '\x03'

# Вычисление LRC см. data_decode
   lrc = 0x00
   lrc_add = False
   for i in range(0,len(sdata)):
      if sdata[i] == '\x01':
         lrc_add = True
      elif sdata[i] == '\x02':
         if lrc_add:
            lrc = (lrc + ord(sdata[i])) & 0x7f
         else:
            lrc_add = True
      elif sdata[i] == '\x03':
         lrc_add = False
         lrc = (lrc + ord(sdata[i])) & 0x7f
      else:
         if lrc_add:
            lrc = (lrc + ord(sdata[i])) & 0x7f

# Добавление вычисленного LRC в строку посылки
   sdata += chr(lrc)

   return sdata


# Отправка посылки и чтение данных из последовательного порта
def send_read(sdata):
   sio.write(unicode(sdata))
   sio.flush()
   return sio.read().encode('ascii')


# Пароль для режима программирования (заводской: 777777)
# Если не указан, будет запрошен
password = ''

# Завершаем предыдущий сеанс
send_read(data_encode({'head':'B0','body':''}))

# Получаем идентификационное сообщение в ответ на общий запрос
ident = send_read('/?!\r\n')

# Отправляем подтвеждение с выбором режима и получаем информационное сообщение
message = data_decode(send_read('\x060' + ident[4] + read_flag + '\r\n'))

# Продолжаем, пока не получено сообщение окончания сеанса (B0)
while message['head'] <> 'B0':

# Запрошен пароль для режима программирования
   if message['head'] == 'P0':
      if password == '':
         try:
            password = raw_input('' if silent else 'Enter password ('+ message['body'] + '): ')
         except (EOFError):
            send_read(data_encode({'head':'B0', 'body':''}))
            exit()
      message = data_decode(send_read(data_encode({'head':'P1', 'body': '(' + password + ')'})))

# Получен запрос повторения (NAK)
# Почему-то после этого счетчик не ждет повторения, а просто перестает отвечать
# Начинаем сначала
   elif message['body'] == '\x15':
      if not silent:
         print '(NAK) received, restarting...'
      send_read(data_encode({'head':'B0','body':''}))
      ident = send_read('/?!\r\n')
      message = data_decode(send_read('\x060' + ident[4] + read_flag + '\r\n'))

# Нет ответа - начинаем сначала
   elif message['body'] == '':
      if not silent:
         print 'Timeout, restarting...'
      send_read(data_encode({'head':'B0','body':''}))
      ident = send_read('/?!\r\n')
      message = data_decode(send_read('\x060' + ident[4] + read_flag + '\r\n'))

   else:

# Получено сообщение подтверждения
      if message['body'] == '\x06':
         if not silent:
            print '(ACK)'

      else:

# Получено информационное сообщение
         print message['body'] if message['lrc'] else 'Data is corrupt!'

# Если режим чтения, выходим
      if not (read_flag == '1'):
         exit() 

# Ввод типа команды (чтение, запись или выход)
# Поддерживаются только R1, W1 и B0
      try:
         head = raw_input('' if silent else '(R)ead, (W)rite or e(X)it (default)? ')
      except (EOFError):
         send_read(data_encode({'head':'B0','body':''}))
         exit()

# Ввод команды и отправка
      if (head.upper() == 'R') or (head.upper() == 'W'):
         try:
            body = raw_input('' if silent else 'Enter command: ')
         except (EOFError):
            send_read(data_encode({'head':'B0','body':''}))
            exit()
         message = data_decode(send_read(data_encode({'head':head.upper()+'1','body':body})))

# Завершение сеанса
      else:
         send_read(data_encode({'head':'B0','body':''}))
         exit()
4 лайка