29 июня 2011 г.

Релиз rmodbus 1.0.0

Наконец закончил мажорную версию реализации протокола ModBus на Ruby - rmodbus. Решил написать о нововведениях и подытожить свою работу.

 Что нового в 1.0.0? 
 
Новое API 
Я уже писал небольшое описание проекта и основные возможности, все это относится к версии 0.5.0. В новом релизе было переработано API клиентской части библиотеки. Очень важный недостаток предыдущей версии был в том, что для каждого ведомого необходимо было создавать свое соединение, что приводило к необходимости к открытию\закрытию  TCP сокета или COM порта для обращения к другому ведомому. В примере происходит копирования  значений 10 регистров из 1-го ведомого во 2-й.:
regs = []

ModBus::TCPClient.connect('127.0.0.1', 502, 1) do |cl|
  regs = cl.read_holding_registers(0,10)
end

ModBus::TCPClient.connect('127.0.0.1', 502, 2) do |cl|
  cl.write_multiple_registers(0,regs)
end
Теперь можно обращаться к ведомым внутри одного соединения:
# Подключаемся к TCP ModBus серверу IP 127.0.0.1, порт 502
ModBus::TCPClient.connect('127.0.0.1', 502) do |cl|
  # Обращение к ведому по адресу 1
  cl.with_slave(1) do |slave|
    # Чтение значений 10 регистров начиная с адреса 0
    regs = slave.read_holding_registers(0,10)
  end 
# Обращение к ведому по адресу 2
  cl.with_slave(2) do |slave|
    # Запись 10 значений начиная с адреса 0
    slave.write_multiple_registers(0,regs)
  end
end 
Чтение\запись через функции немного громоздкое и требует помнить их названия, по этому можно использовать другой способ:
ModBus::TCPClient.new('127.0.0.1', 8502) do |cl|
  cl.with_slave(1) do |slave|
    # Чтение одного регистра по адресу 16
    slave.holding_registers[16]

    # Запись одного регистра по адресу 16
    slave.holding_registers[16] = 123

    # Чтение регистров с адреса 16 по 20
    slave.holding_registers[16..20]

    # Запись регистров с адреса 16 по 20
    slave.holding_registers[16..20] = [1, 2, 3, 4, 5]
  end
end 
Таким образом можно обратиться к 4-м областям памяти ведомого (согласно спецификации ModBus): coils, discrete_inputs, holding_registers и input_registers. 

32-х разрядные данные 

Теперь библиотека поддерживает 32-х разрядные регистры и регистры с плавающей точкой. Сама поддержка осуществятся через вспомогательные методы:
# Чтение 32-х разрядного регистра
res = slave.holding_registers[0..1]
res.inspect => [20342, 17344]
res.to_32i => [1136676726]
res.to_32f => [384.620788574219]

# Запись 32-х разрядного регистра
cl.holding_registers[0..1] = [1136676726].from_32i
cl.holding_registers[0..1] => [20342, 17344]
cl.holding_registers[2..3] = [384.620788574219].from_32f
cl.holding_registers[2..3] => [20342, 17344]

Поддержка JRuby
Теперь можно библиотеку использовать и на JRuby, но без ModBus RTU  реализации, так как для работы с последовательным портом нужен гем serialpoort. Я собрал гем для Java, удалив эту зависимость и классы RTU реализации.

 Зачем нужен ModBus на Ruby?

О сильных и слабых сторонах Ruby я думаю уже известно, иначе до этой строки Вы не дочитали бы :) Естественно такая реализация не претендует на решение для встроенных систем или для систем с критичным подходом к производительности. Навряд ли Вы захотите ее как то использовать для написании своего OPC сервера или интеграции ее в SCADA. Но эта библиотека может пригодиться для быстрого подключение к модбас устройству, ведь необходимо всего несколько строк. Можно написать скрипты для тестирования взаимодействия ПЛК с верхним уровнем и т.д. RModBus используется для интеграции ПЛК с веб приложениями (Rails, Senatra, Ramaze) к тому же можно писать веб сервисы используя протоколы RESTFull, SOAP, RPC-XML и т.д. В этом ключе.
Что бы повысить интерес к таким легким решениям как rmodbus, привожу пример чтения данных по ModBus на Java (jamod):
//Соединение
InetAddress addr = InetAddress.getByName('127.0.0.1');
TCPMasterConnection con = new TCPMasterConnection(addr);
con.setPort(502);
con.connect();

//Подготовка запроса
ReadInputDiscretesRequest req = new ReadInputDiscretesRequest(0, 10);

//Подготовка транзакции
ModbusTCPTransaction trans = new ModbusTCPTransaction(con);
trans.setRequest(req);
// Чтение
trans.execute();
ReadInputDiscretesResponse res = (ReadInputDiscretesResponse) trans.getResponse();
System.out.println("Digital Inputs Status=" + res.getDiscretes().toString())
Против:
ModBus::TCPClient.connect('127.0.0.1', 502) do |cl|
  cl.with_slave(1) do |slave|
    puts "Digital Inputs Status=" + slave.discrete_inputs[0..10].to_s
  end 
end 
Можно еще короче
puts "Digital Inputs Status=%s" % 
  ModBus::TCPClient.connect('127.0.0.1', 502).with_slave(1).holding_registers[0.10]

Заключение

Я веду этот проект с 2008 года. Версия 0.1.0 поддерживала только TCP клиента и работала на Ruby-1.8.6. Теперь это вполне стабильная библиотека с поддержкой как клиентской, так и серверной части. Поддерживает RTU, RTU по TCP и TCP реализации модбас. Работает на всех популярных реализациях Ruby. Я затеял проект больше для обучения Ruby, чем для практического применения, но в итоге есть и реальные внедрения. Было много писем и патчей от разработчиков по всему миру, я не только учился программировать, но и изъяснятся на английском, работать в команде) Ниже перечислены люди которые внесли весомый вклад в проект:
Kelley Reynolds - реализация RTUViaTCP, идея нового API для версии 1.0.0, реализация вспомогательных методов для работы с 32-х разрядными данными и методы [] чтения\записи данных ведомого;
Hector G. Parra -реаниматор проекта serialpoort, благодаря ему стала возможна реализация RTU ModBus;
 jsanders - первый патч:);
Tallak Tveide - идея обращения к устройству внутри блока;
Mark Flocco - тесты с реальными ПЛК.

Идей о будущем функционале у меня нет. По этому проект развивается по мере появления запросов на новые функции и сообщения об ошибках. Так что если есть какие то идеи и замечания, то сюда и на английском.

Комментариев нет:

Отправить комментарий