PDA

Просмотр полной версии : Modbus-мастер в Visual Studio (.NET)



Yegor
20.12.2014, 09:42
Ща будем подрубать Овеновский ПЛК110 к своей проге в Visual Studio. Полтора года назад мы этим уже занимались (http://www.owen.ru/forum/showthread.php?t=14784), но молча. Будем исправляться.

Должно получиться окошко, отображающее число циклов контроллера (счёт на стороне ПЛК). Также в окошке будет кнопка, позволяющая сбросить счётчик. Начнём с консольной программы, которая будет только выводить число. Закончим WPF-приложением с переходом от опросной модели к событийной.

Затариваемся


Visual Studio. Например, версия 2013 Community Edition (http://www.visualstudio.com/en-us/news/vs2013-community-vs.aspx) — самая навороченная из бесплатных для некоммерческого использования. Также можно не выпендриваться и скачать самую лёгкую редакцию Express (http://www.visualstudio.com/en-us/products/visual-studio-express-vs.aspx) — ею можно пользоваться и в коммерческих целях. До первого запуска как-нибудь сами дойдёте, ок? Я верю в вас.
Библиотека NModbus (http://code.google.com/p/nmodbus/downloads/list). Пять лет уже не обновлялась, но работает нормально, да и опенсорс, есличо. Бесплатна для любых целей. Качаем ".NET 3.5 NModbus 1.11.0.0 Binaries". Можно не качать, если Visual Studio у вас Community Edition или ещё серьёзнее.


Готовимся
Пока устанавливается студия, можно купить контроллер Овен ПЛК110-60 и написать для него тестовую программу. Начнём с Modbus TCP, но потом я расскажу, как сделать RTU, например.

В общем, тут ничего хитрого: в конфигурацию добавляем Modbus (slave), в этот Modbus (slave) добавляем регистр 2 byte и называем его reg0, а в Modbus [FIX] добавляем TCP. Скриншот:

15806

А в PLC_PRG пишем reg0 := reg0 + 1; Всё, тестовая программа готова. См. файл test0.pro, но он именно для ПЛК110-60. Если у вас другой — делайте сами. Ну и понятно, что можно взять любое другой Modbus-устройство.

Фигачим
В наконец запустившейся студии делаем консольное приложение на C#.

15807

Теперь надо добавить в проект библиотеку NModbus. Тут есть два способа:

Если у вас студия Community Edition или круче, то воспользуемся NuGet. Tools > NuGet Package Manager > Manage NuGet Packages... В появившемся окне ищем NModbus:

15808

Жмём Install. Всё, в проекте появилась ссылка "NModbus4":

15809
Ручками. Из ранее скачанного архива NModbus достаём папку bin/net и из неё добавляем Modbus, log4net и Unme.Common в ссылки проекта:

15810


Теперь можно написать мастера сети (айпишник своего ПЛК в длинную строчку подставьте):
using Modbus.Device;
using System;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace NModbusTut
{
class Program
{
static void Main(string[] args)
{
var master = ModbusIpMaster.CreateIp(new TcpClient("10.1.6.10", 502));
while (true)
{
Console.Write("\r{0}", master.ReadHoldingRegisters(1, 0, 1)[0]);
}
}
}
}
Хыы, работает:

15811

См. NModbusTut01.zip.

Само собой, ModbusIpMaster можно заменить на ModbusSerialMaster, скормить ему вместо TcpClient последовательный порт (SerialPort), и таким образом получить Modbus RTU вместо Modbus TCP.

Графика
Теперь сотворим что-нибудь гуёвое.

15801

На событие окна Loaded ставим вот такой обработчик:
mbMaster = ModbusIpMaster.CreateIp(new TcpClient("10.1.6.10", 502));
masterLoop = Task.Factory.StartNew(new Action(Update));Ну и следом метод с полями:
ModbusIpMaster mbMaster;
Task masterLoop;

private void Update()
{
while (true)
{
Dispatcher.Invoke(new Action(delegate()
{
progressBar.Value = mbMaster.ReadHoldingRegisters(1, 0, 1)[0];
}));
}
}И оно даже работает (файл NModbusTutWPF01.zip):

15802
Но так делать — не совсем ок. То есть совсем не ок. Видите, как в методе Update мы десятки раз в секунду идём в диспетчерский поток вне зависимости от того, изменился регистр или нет? Давайте-ка перепишем так, чтобы воспользоваться привязкой к данным и при этом не гонять её за зря. Дальше чуть сложнее.

Что нужно исправить и добавить? Контролы в WPF любят сами привязываться к источникам данных. А у нас вместо этого класс окна кормит зелёную полоску с ложечки и называет её по имени. Надо, чтобы был такой объект, который принимал бы на вход модбас-регистры много раз в секунду, а отдавал теги заинтересованным контролам только когда теги изменяются. При этом знать контролы поимённо этот объект не должен, иначе добавление контролов будет затруднено. Делаем вот такой класс:

public enum Tag
{
Reg0
}

/// <summary>
/// Готов принимать сырые регистры по опросной модели и преобразовывать
/// их в теги с уведомлением об изменениях по событийной модели
/// </summary>
/// <remarks>
/// Для простоты привязки сделан динамическим объектом. Свойства = теги.
/// </remarks>
public class Depoller : DynamicObject, INotifyPropertyChanged
{
// Сюда новые и старые значения тегов
IDictionary<tag, object=""> oldValues, newValues;

// Сюда названия тегов, изменившихся на данном проходе
List<tag> dirtyTags = new List<tag>();

// Через это событие другие объекты узнают об изменениях
public event PropertyChangedEventHandler PropertyChanged;

Dispatcher dispatcher;

public Depoller(Dispatcher d)
{
dispatcher = d;
oldValues = new Dictionary<tag, object="">();
newValues = new Dictionary<tag, object="">();
}

/// <summary>
/// Разбирает Modbus-регистры на теги
/// </summary>
///
Modbus-регистры как есть
public void Input(ushort[] data)
{
// Парсим
newValues[Tag.Reg0] = data[0];

// Составляем список изменившихся
lock (dirtyTags)
{
// Новый список
dirtyTags.Clear();

foreach (var pair in newValues)
{
Object val = null;
// Если тег записывается впервые или если он изменился
if (!oldValues.TryGetValue(pair.Key, out val) || !pair.Value.Equals(val))
{
oldValues[pair.Key] = pair.Value; // Запоминаем на следующий раз
dirtyTags.Add(pair.Key); // Добавляем в список изменённый на этот раз
}
}
}
if (PropertyChanged != null) // Если есть с кем поделиться этой радостной вестью
{
// В потоке пользовательского интерфейса
dispatcher.BeginInvoke(new Action(delegate()
{
lock (dirtyTags)
{
// Сообщить о каждом изменённом теге
foreach (var tag in dirtyTags)
{
PropertyChanged(this, new PropertyChangedEventArgs(tag.ToString()));
}
}
}));
}
}

/// <summary>
/// Доступ к тегам как к свойствам объекта
/// </summary>
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return oldValues.TryGetValue((Tag)Enum.Parse(typeof(Tag), binder.Name), out result);
}
}В перечислении Tag именуем свои теги. В методе Input разбираем регистры на теги. Скажем, если один тег типа float у вас передаётся двумя модбас-регистрами (типично), то именно в этом методе из двух ushort'ов (WORD'ов) получается один float (REAL). Дальше немножко магии, и у нас есть объект, который сообщает об изменениях тегов и отдаёт эти теги по запросу. Теперь в классе окна (а в серьёзном проекте лучше где-нибудь повыше) мы скармаливаем регистры уже объекту этого класса и задаём привязку контролов на него. Можно не ковыряя лишний раз код добавить текстовое поле только за счёт разметки, например:

15804
См. NModbusTutWPF02.zip.

Про кнопку, вызывающую действие на ПЛК — во второй части. Stay tuned.</tag,></tag,></tag></tag></tag,>

Вольд
20.12.2014, 12:21
Большой respect тебе, Yegor. Отличная тема.

rekbrjaaa
24.12.2015, 12:21
а с платф .NET 4.0 не будет работать?21491

greenwod
11.03.2016, 19:18
Yegor, спасибо за пример.

Не могу найти продолжение "Про кнопку, вызывающую действие на ПЛК" - можете дать ссылку?

Заранее спасибо!

Yegor
12.03.2016, 06:05
Да я так и не собрался написать. Там всё сводится к вызову функций записи по нажатию кнопки типа master.Write... Хотел ещё рассказать про развязку кнопок (gui) и обработчиков а ля MVVM, но это до меня всё расписано вдоль и поперёк.

greenwod
12.03.2016, 18:06
Да, спасибо!

master.WriteSingleRegister(1, 0, 96)
1 - адрес слейва
0 - адрес регистра
96 - значение


Продолжаю усложнять себе задачи :)
Пытаюсь подать/считать сигнал с входа/выхода.
Как я понимаю, для этого нужно использовать Writesinglecoil Readcoilstatus
Но не могу найти адреса для входов/выходов - в инструкции не нашел (у меня ПЛК110-24.60.Р.М).
Не подскажете как обращатся к входу/выходу ПЛК. Я так понимаю должны быть какая-то таблица "Вход/выход ПЛК" соответствует "Номер бита"?

Yegor
12.03.2016, 18:26
Такой таблицы нет. Вообще говоря слейв, который вы создаёте в ПЛК для связи с ПК, не имеет связей со входами и выходами. Эти связи вам нужно создать самому. По-простому можно в программе ПЛК самому разметить выходы через присваивание типа di10 := slaveRegA.9.

В боевых проектах мне приходится делать сложнее. Нулевой регистр отвожу на код команды, ещё несколько — на аргументы. Дальше в программе ПЛК пишу интерпретатор вроде
TYPE CMD: (NOP, ON, OFF); END_TYPE


CASE pc_cmd OF
ON: DI1 := TRUE;
OFF: DI1 := FALSE;
END_CASE
pc_cmd := NOP;При этом в мастере есть такой же
enum Cmd { Nop, On, Off }И, соответственно, я могу командовать контроллером примерно как master.WriteSingleRegister(1, 0, Cmd.On). Ну, естественно, там добавляются ещё аргументы и т.д.

Владимир Ситников
12.03.2016, 23:31
Yegor, а есть где-то описание "ПЛК110/Мx110 memory model (https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4)"?

В частности:
1) word tearing
2) out of order writes / happens-before / data race
3) out of thin air values

Насколько я понимаю, в момент работы цикла "сетевые переменные" своих состояний не меняют. Но как оно работает в момент "до цикла/после цикла"?
Какие-нибудь гарантии на tearing есть?

Yegor
13.03.2016, 09:08
Не попадалось. Да и не наблюдал таких эффектов. Но я не загружал ПЛК запросами настолько, чтобы это могло проявиться. Ну а модули вообще быстрого дуплексного интерфейса не имеют, чтобы это можно было хотя бы проверить (не говоря о том, чтобы столкнуться с этим в реальном проекте). Если в контексте темы, то лучше, конечно, приостанавливать опрос на время записи — так можно хотя бы себя обезопасить от вероятного рассинхрона.

greenwod
14.03.2016, 12:21
Такой таблицы нет. Вообще говоря слейв, который вы создаёте в ПЛК для связи с ПК, не имеет связей со входами и выходами. Эти связи вам нужно создать самому.

Спасибо, этого понимания мне не хватало.
Пока буду пользоваться более простым способом (напрямую задавать di10 := slaveRegA.9)

Столкнулся с еще одной проблемой, после запуска своей программы и выполнения какого-то действия (запись или чтение) я не могу подключится к ПЛК из Codesys.

ПЛК у меня подключен по Ethernet (Ip, шлюз и маска прописаны согласно настроек локальной сети).
При подключении из Codesys получаю ошибку: "Ошибка связи (#0): произошло отключение".

Также если потом пытаюсь подключится к ПЛК из своей программы например для чтения (или Вашего консольного приложения) как правило перед успешным подключением получаю две ошибки: "Конечный компьютер отверг запрос на подключение".

Возможно нужно закрывать подключение? Но в Вашем проекте Вы ничего подобного не делаете и ошибок нет.

Yegor
14.03.2016, 12:58
Выкладывайте проекты — будем смотреть в чём дело.

По-хорошему закрывать подключение, конечно, надо. И обрабатывать отключение извне тоже.

greenwod
14.03.2016, 16:45
Я после внесения правок в код скорее всего загружал только изменения, а нужно "Загружать все" - ошибка из-за этого была (только таким образом у меня получилось повторить ошибку).

voale
27.04.2016, 20:36
Да, спасибо!

master.WriteSingleRegister(1, 0, 96)
1 - адрес слейва
0 - адрес регистра
96 - значение


Продолжаю усложнять себе задачи :)
Пытаюсь подать/считать сигнал с входа/выхода.
Как я понимаю, для этого нужно использовать Writesinglecoil Readcoilstatus
Но не могу найти адреса для входов/выходов - в инструкции не нашел (у меня ПЛК110-24.60.Р.М).
Не подскажете как обращатся к входу/выходу ПЛК. Я так понимаю должны быть какая-то таблица "Вход/выход ПЛК" соответствует "Номер бита"?

greenwod
можешь выложить проект , интересно как ты справился с задачей
"Пытаюсь подать/считать сигнал с входа/выхода."
спасибо
и еще вопрос: не возникают ли у тебя проблемы как тут http://www.cyberforum.ru/csharp-net/thread388326.html
, т.е опрос идет исправно, а вот ответы на запросы, происходят только первые 35

voale
28.04.2016, 09:05
Да, спасибо!

master.WriteSingleRegister(1, 0, 96)
1 - адрес слейва
0 - адрес регистра
96 - значение


Продолжаю усложнять себе задачи :)
Пытаюсь подать/считать сигнал с входа/выхода.
Как я понимаю, для этого нужно использовать Writesinglecoil Readcoilstatus
Но не могу найти адреса для входов/выходов - в инструкции не нашел (у меня ПЛК110-24.60.Р.М).
Не подскажете как обращатся к входу/выходу ПЛК. Я так понимаю должны быть какая-то таблица "Вход/выход ПЛК" соответствует "Номер бита"?

greenwod
можешь выложить проект , интересно как ты справился с задачей
"Пытаюсь подать/считать сигнал с входа/выхода."
спасибо
и еще вопрос: не возникают ли у тебя проблемы как тут http://www.cyberforum.ru/csharp-net/thread388326.html
, т.е опрос идет исправно, а вот ответы на запросы, происходят только первые 35

voale
28.04.2016, 09:15
Да, спасибо!

master.WriteSingleRegister(1, 0, 96)
1 - адрес слейва
0 - адрес регистра
96 - значение


Продолжаю усложнять себе задачи :)
Пытаюсь подать/считать сигнал с входа/выхода.
Как я понимаю, для этого нужно использовать Writesinglecoil Readcoilstatus
Но не могу найти адреса для входов/выходов - в инструкции не нашел (у меня ПЛК110-24.60.Р.М).
Не подскажете как обращатся к входу/выходу ПЛК. Я так понимаю должны быть какая-то таблица "Вход/выход ПЛК" соответствует "Номер бита"?

greenwod
можешь выложить проект , интересно как ты справился с задачей
"Пытаюсь подать/считать сигнал с входа/выхода."
спасибо
и еще вопрос: не возникают ли у тебя проблемы как тут http://www.cyberforum.ru/csharp-net/thread388326.html
, т.е опрос идет исправно, а вот ответы на запросы, происходят только первые 35

Yegor
28.04.2016, 12:05
http://www.cyberforum.ru/csharp-net/thread388326.htmlТам неправильно организован приём ответа. Вообще прежде чем писать низкоуровневый обмен, надо запомнить:


что ответ может прийти не сначала;
что ответ может прийти не до конца;
что ответ может разбиться на несколько частей;
что несколько ответов могут прийти разом;
что может прийти вообще не ответ;
что ответ может не прийти;
что ответ может прийти через запрос;
что вместо ответа или вместе с ответом может прийти что угодно даже без запроса.


Если неохота всё это контролировать и вообще страшно покидать зону комфорта «запрос-ответ», то лучше пользоваться высокоуровневыми проверенными библиотеками а ля NModbus.

fillpackart
13.05.2016, 15:00
Yegor, вы не могли бы подсказать, как мне решить следующую проблему?
Есть сеть ПЛК63, подключенная к gprs модему Невод. Сам модем настроен на передачу данных на белый айпи у меня дома. Раньше я использовал программное обеспечение геолинка(прозводитель модема), которое эмулировало com-порт из соединения с модемом, и я мог его опрашивать вот таким образом:
var r = new ModbusSerialMaster(myPort);
r.ReadHoldingRegisters(10, 4098, 1);
Теперь же мне нужно осуществлять связь с сетью плк НЕ используя стороннее ПО. Отсюда вопрос - как мне из кода c# в Visual Studio (.NET) связаться со своими ПЛК?

voale
17.05.2016, 10:05
Такой таблицы нет. Вообще говоря слейв, который вы создаёте в ПЛК для связи с ПК, не имеет связей со входами и выходами. Эти связи вам нужно создать самому. По-простому можно в программе ПЛК самому разметить выходы через присваивание типа di10 := slaveRegA.9.

В боевых проектах мне приходится делать сложнее. Нулевой регистр отвожу на код команды, ещё несколько — на аргументы. Дальше в программе ПЛК пишу интерпретатор вроде[CODE]TYPE CMD: (NOP, ON, OFF); END_TYPE


подскажите плиз
slaveRegA.9 откуда берется?
и нулевой регистр на код команды это как будет?

Yegor
18.05.2016, 06:34
Отсюда вопрос - как мне из кода c# в Visual Studio (.NET) связаться со своими ПЛК?Попробуйте создавать мастера через ModbusSerialMaster.CreateRtu(TcpClient client) (либо CreateAscii по ситуации). Тогда обмен будет в формате последовательного порта, но канал останется TCP. Это вместо традиционного ModbusIpMaster.CreateIp, где формат сообщений иной.
подскажите плиз
slaveRegA.9 откуда берется?
и нулевой регистр на код команды это как будет?Всё это вы задаёте сами в конфигурации ПЛК. То есть добавляете туда модбас-слейв, добавляете в него регистры, именуете их подходящим образом.

madiggo
24.05.2016, 11:09
Коллеги, при использовании NModbus4 из NuGet столкнулся с проблемой, когда при пропадании связи (банально выдернут шнур) программа надолго зависала. Как оказалось, необходимо установить свойство ModbusIPMaster.Transport.ReadTimeout в какое-то разумное значение (в мс). По умолчанию там -1. Поставил 500 - работает, при выдергивании шнура исправно выкидывает исключение. Там ещё есть свойство WriteTimeout, тоже его надо установить во что-то приемлемое.
Проблема не ОВЕН-ориентированная, а NModbus4, но вдруг кому пригодится.

Yegor
25.05.2016, 21:26
А я вот узнал, что NModbus не умеет делать несколько слейвов на одном TCP-канале. Печаль-беда.

madiggo
26.05.2016, 11:52
А я вот узнал, что NModbus не умеет делать несколько слейвов на одном TCP-канале. Печаль-беда.
А ещё по ModbusIPMaster нельзя сказать, в каком состоянии соединение, нет никаких свойств, можно только попробовать сделать запрос и поймать исключение.

Yegor
26.05.2016, 19:49
Что именно вы подразумеваете? Как такие свойства по-вашему должны выглядеть и работать?

madiggo
27.05.2016, 11:10
Что именно вы подразумеваете? Как такие свойства по-вашему должны выглядеть и работать?
Ну хотя бы на чтение TcpClient. Конечно, его можно сначала создать и передать в конструктор его переменную. Но логичнее было бы получать из ModbusIPMaster. Зачем нужно? Чтобы без исключительной ситуации отлавливать отвалившееся соединение хотя бы.

Yegor
27.05.2016, 17:55
Есть подозрение, что вы что-то не так делаете. Tell, don't ask (http://martinfowler.com/bliki/TellDontAsk.html). По принципу инкапсуляции, вас не должно беспокоить состояние мастера и уж тем более свойства его транспорта. Вы просто должны быть готовы к тому, что мастер выдаст исключение. Отлавливать исключения проще, чем поддерживать чужое состояние (stateful vs stateless).

greenwod
30.05.2016, 11:51
greenwod
можешь выложить проект , интересно как ты справился с задачей
"Пытаюсь подать/считать сигнал с входа/выхода."
спасибо
и еще вопрос: не возникают ли у тебя проблемы как тут http://www.cyberforum.ru/csharp-net/thread388326.html
, т.е опрос идет исправно, а вот ответы на запросы, происходят только первые 35

Здравствуйте!
Я еще не успел написать что-то, что не стыдно выложить на форум :)

Вы присваиваете имя входу ПЛК (например in1).
Создаете в MODBUS (slave) регистр (например reg0).

В коде ПЛК присваиваете в нужный бит регистра значение своего входа:
reg0.2:=in1; (где .2 номер бита регистра).
Важно помнить, что сигнал может быть коротким и Ваша программа может не успеть его считать - я делал дополнительный регистр в котором контролировал сигнал.

В коде программы считываете значение регистра:
bool[] coilstatus = master.ReadCoils(1 , Bit , 1) [0]
Где Bit – адрес бита (в нашем случае будет 2).

Насчет проблем с первыми 35 запросами не стыкался. Используйте Nmodbus как описано вначале данной темы и проблем быть не должно.

greenwod
30.05.2016, 14:00
А я вот узнал, что NModbus не умеет делать несколько слейвов на одном TCP-канале. Печаль-беда.

Странно, мне казалось достаточно изменить адрес слейва в параметрах и в запросе в коде просто указывать нужный слейв (При обращении к слейву первый параметр в команде вроде-как отвечает за адрес слейва).
Но я на практике не проверял (сейчас нет под рукой ПЛК).
Кстати, а подключатся из кода к эмулятору ПЛК запущенному в Codesys нет возможности?

madiggo
31.05.2016, 15:39
Есть подозрение, что вы что-то не так делаете. Tell, don't ask (http://martinfowler.com/bliki/TellDontAsk.html). По принципу инкапсуляции, вас не должно беспокоить состояние мастера и уж тем более свойства его транспорта. Вы просто должны быть готовы к тому, что мастер выдаст исключение. Отлавливать исключения проще, чем поддерживать чужое состояние (stateful vs stateless).
Да я то всё делаю так, ловлю исключения. Но ох уж эти мне принципы... На мой взгляд, гораздо проще и безопаснее перед действием проверить, а имеет смысл его совершать? Люди вот под кирпич не ездют, не желая опытным путем проверять, стена там есть или всё-таки нет ;)

fillpackart
14.06.2016, 11:57
Попробуйте создавать мастера через ModbusSerialMaster.CreateRtu(TcpClient client) (либо CreateAscii по ситуации). Тогда обмен будет в формате последовательного порта, но канал останется TCP. Это вместо традиционного ModbusIpMaster.CreateIp, где формат сообщений иной.
А как настроить TcpClient? Модем передает данные на фиксированный айпи, сам модем имеет айди, так каким образом я из кода указываю, с каким модемом я соединяюсь?

Yegor
16.06.2016, 06:48
Если данные передаются по инициативе стороны модема и белый айпишник тут, а не там, то о каком мастере на принимающей стороне может идти речь? Делайте наоборот слейв тогда.

fillpackart
20.06.2016, 13:32
Если данные передаются по инициативе стороны модема и белый айпишник тут, а не там, то о каком мастере на принимающей стороне может идти речь? Делайте наоборот слейв тогда.

Данные передаются по инициативе опрашивающего ПК.

voale
30.11.2016, 11:03
А я вот узнал, что NModbus не умеет делать несколько слейвов на одном TCP-канале. Печаль-беда.

что это значит на одном канале? т.е нельзя использовать например 2устройства плк100(оба slave), которые в одной сети находятся, например с ip 192.168.1.3 и 192.168.1.4? или как это понять?

voale
30.11.2016, 13:32
для решения проблемы " TCP-сокет закрывается через 11-12 секунд бездействия "пытался сделать как в инструкции в NModbus
http://ftp.icpdas.com/pub/cd/8000cd/napdos/modbus/nmodbus/nmodbus_api_manual_v1.2_en.pdf
на странице 8 : If want to reconnect when offline, refer to following codes.

но это не помогает, может кто скинуть кусок кода кто как делает? заранее огромное спасибо.

voale
30.11.2016, 13:36
слейвы имеют адреса, если на одном ПК по 502 порту обратится к слейву с номером один и номером два, библиотека не сможет обеспечить работу со вторым слейвом. Вряд ли и другие бибки это делают, ни кто особо не задавался таким вопросом для промышленного применения, максимум для эмуляции

т.е нужно поменять порт на слейве с адресом 2 например(ставить порт 503 вместо 502) ?
и совсем нет решения, когда есть 2 и более слейвов? создавать отдельный tcp канал?

например, так создавать 2 отдельных подключения
слейв 1
master = ModbusIpMaster.CreateIp(new TcpClient("192.168.1.10", 502));
master.WriteSingleCoil(1, 11, true);

слейв2
master2 = ModbusIpMaster.CreateIp(new TcpClient("192.168.1.10", 502));
master2.WriteSingleCoil(2, 11, true);

voale
28.04.2017, 21:22
может кто знает как связать плк110 и считыватель проксимити карт(hid, e-marine)?
цель такая: как получать считанный код в программе на C# через modbus tcp?

zolex
19.06.2017, 17:27
Yegor ,у меня выскакивает сообщение хотя ПЛК настроен правильно!, что может быть в ОС настроено не так? может с правами админа надо файл открывать?как быть с брандмауэром?

voale
21.06.2017, 23:50
Важно помнить, что сигнал может быть коротким и Ваша программа может не успеть его считать - я делал дополнительный регистр в котором контролировал сигнал.

как именно контролировал подскажи подробнее?

Yegor
06.07.2017, 18:34
что может быть в ОС настроено не так?Проект для ПЛК лучше покажите.

S.A.D.
02.08.2017, 00:44
может кто знает как связать плк110 и считыватель проксимити карт(hid, e-marine)?
цель такая: как получать считанный код в программе на C# через modbus tcp?
я делал считывание RFID карт путём выбора считывателя с RS232 интерфейсом и получал код карты в порт через библиотеку SysLibCom в ASCII кодировке.

dimakovalenko93
08.09.2017, 12:06
Подскажите.
По вашому примеру все вышло и работает.
Но мне нужно передавать число типа Real.
Подскажите как ето реализовать.
Спасибо

wad71
12.08.2021, 09:36
На первой странице есть пример опроса плк из консольного приложения C#. По протоколу Modbus TCP. C# я только изучаю. Мне нужно написать мастера сети Modbus RTU для опроса входов МВ110-24.32ДН. Использую VS Community. NModbus подключил. Попробовал переделать пример с первой страницы с Modbus TCP на Modbus RTU. Не очень понятно, как это сделать. Нужно же где то номер порта, параметры подключения и т.д....
Попробовал пример Create RTU из мануала на NModbus.

using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Modbus.Device;
using System.Net.Sockets;

namespace ModbusTest
{
class Program
{
static void Main(string[] args)
{
SerialPort serialPort = new SerialPort(); //Create a new SerialPort object.
serialPort.PortName = “COM14”;
serialPort.BaudRate = 115200;
serialPort.DataBits = 8;
serialPort.Parity = Parity.None;
serialPort.StopBits = StopBits.One;
serialPort.Open();
ModbusSerialMaster master = ModbusSerialMaster.CreateRtu(serialPort);

}
}
}

Начеркало ошибок:
56520

Можно ли где то глянуть рабочий пример или что то похожее? Нужно же с чего то начать, а дальше буду разбираться..

BETEP
12.08.2021, 10:27
ftp://ftp.icpdas.com/pub/cd/8000cd/napdos/modbus/nmodbus/demo/

wad71
12.08.2021, 11:53
Я это уже видел. Для меня это пока сложновато и избыточно. Я не готов пока за WinForm приниматься. Консоль еще не полностью освоил. И выудить то, что мне нужно из этого проекта вряд ли смогу без посторонней помощи...

melky
12.08.2021, 11:58
wad71 есть открытые проекты, где есть Modbus, но если вы не можете выудить нужное без помощи, то вероятно не поможет так же...

wad71
12.08.2021, 13:35
Не так, чтобы совсем уж я деревянный... Из проекта для консоли, я думаю, смог бы нужное отсеять. По крайней мере времени уйдет намного меньше.

wad71
12.08.2021, 15:43
Подключил к компу через преобразователь USB/RS485 модуль ввода МУ110-32Р. Сетевой адрес модуля 16, в диспетчере задач он отображается на COM14.

Вот написал, руководствуясь мануалом на NModbus:



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Modbus.Device;
using System.Net.Sockets;
using System.IO.Ports;


namespace ModbusTest
{
class Program
{
static void Main(string[] args)
{
SerialPort serialPort = new SerialPort(); //Create a new SerialPort object.
serialPort.PortName = "COM14";
serialPort.BaudRate = 115200;
serialPort.DataBits = 8;
serialPort.Parity = Parity.None;
serialPort.StopBits = StopBits.One;
serialPort.Open();
ModbusSerialMaster master = ModbusSerialMaster.CreateRtu(serialPort);
//master.Transport.ReadTimeout = 300;

while (true)
{
Console.WriteLine("адрес регистра - ");
ushort registerAddress = ushort.Parse(Console.ReadLine());
Console.WriteLine("значение - ");
ushort value = ushort.Parse(Console.ReadLine());

master.WriteSingleRegister(16, registerAddress, value);
}
}
}
}

Сборка проекта без ошибок.

С консоли ввожу адрес регистра, затем значение ШИМ (0-1000).

В итоге выскакивает :
56529

Что делаю неправильно?

melky
12.08.2021, 16:20
а перевести ?

YuriBel
12.08.2021, 16:35
А сам модуль при этом настроен?

YuriBel
12.08.2021, 16:42
И еще. Из руководства на модуль: " ...запись осуществляется командой 16(0х10)....
Из описания протокола: 16 (0x10) — запись значений в несколько регистров хранения (Preset Multiple Registers).
Остается понять, какую команду отсылает функция WriteSingleRegister.
Мне почему-то кажется, что это будет 6 (0x06) — запись значения в один регистр хранения (Preset Single Register).

wad71
12.08.2021, 17:11
Перевел. Команда не поддерживается ведомым устройством. Переписал с использованием WriteMultipleRegisters. Так все работает без проблем. То есть МУ110 - 32Р не поддерживает запись одиночных регистров? Только группой?

melky
12.08.2021, 17:55
Возможно, читайте РЭ на модуль. Никто не запрещает множественной командой записывать и один регистр.

wad71
12.08.2021, 18:28
Спасибо. Действительно, в РЭ запись только группы регистров. И запись командой WriteMultipleRegisters в один регистр тоже работает.

wad71
12.08.2021, 19:24
А в библиотекеNModbus есть инструментарий для контроля утери пакетов или их целостности, подсчет контрольных сумм? Или как то своими силами это организовывать? Где об этом есть информация, пусть даже на английском?

wad71
13.08.2021, 18:04
Еще есть вопрос. Нужно вывести на экран измеренную температуру с ТРМ 202. Есть два варианта. Int16 со знаком в одном регистре или float32 в двух регистрах. В NModbus числа представлены ushort16. В мануале NModbus есть примеры преобразования ushort - float и наоборот. Пробовал использовать, но ничего правильного так и не получил на экране. Есть ли более понятный способ преобразования ushort - float ?

melky
13.08.2021, 20:14
Простите, это что за библиотека Modbus на ПК такая, которая не умеет работать с float и так далее?
Студенты что ли писали? Я могу понять, когда числа с плавающей точкой являются отсебятиной производителя прибора (встречал такое), но чтобы IEEE не понимало, это как бы..... может в мусорку такую библиотеку ?
И опять же, расчет CRC тоже должен быть, способов расчетов CRC Modbus в сети для C# предостаточно. Хотя собственно их два, табличный и расчетный с полиномом.

wad71
15.08.2021, 19:39
До crc пока руки не дошли. А вот с выводом на экран измеренных значений все наладилось. Signed int просто через двойное приведение типов правильно выводится. А float через несколько битовых операций пришел в норму. И в C# еще есть класс BitConverter, внем есть метод как раз для таких преобразований. Так, что все пишется-читается без проблем. Пробовал читать температуру и регистр статуса одновременно с 9 штук трм. Все нормально, правда быстрее чем за100 мс один прибор опросить не получается, программа начинает выбрасывать исключения. Хотя это не очень принципиально.

melky
16.08.2021, 10:45
Так паузы между запросами должны быть, приборы то не всегда готовы к ответу на запрос. Может по этому и исключения.
У вас CRC должна сама библиотека формировать, по крайней мере когда вы отправляете запрос, вы же указываете только номер регистра и номер команды чтения, а раз прибор отвечает, значит все нормально. Это вам при ответе надо убедиться, что ответ пришел полным и не искаженным. Опять же, возможно библиотека и это проверяет и выставляет флаг.

Вариаций может быть масса, типа
bool read = Читаем то-то, out error; и если read = true то ответ корректный, если false то смотрим что там за ошибка.

Это пример, не знаю, как там в бибке этой все устроено. А может надо самостоятельно CRC проверять получив ответ...

lagutin
26.08.2021, 09:48
Подниму тему. Я пытаюсь считать по modbus TCP c пр200. C#? ,библиотека Nmodbus4. Считываю через конвертор ER-108. Но это не суть. Я готовой программой все считываю. То есть сеть рабочая. В конверторе есть отладкаи вот там мне выдается
1. Рабочий вариант, готовой программой
1:05:47 RS485 PORT1 CONNECTION ACCEPTED 192.168.88.2 PORT 59132
1:05:47 RS485 PORT1 DATA RECEIVED, LEN=8
0x10 0x03 0x02 0x00 0x00 0x03
2. Нерабочий - моя недоделка
0:57:00 RS485 PORT1 CONNECTION ACCEPTED 192.168.88.2 PORT 52620
0:57:00 RS485 PORT1 DATA RECEIVED, LEN=12
0x00 0x01 0x00 0x00 0x00 0x06 0x10 0x04 0x02 0x00 0x00 0x01
Получается какие то непонятные данные приходят на запрос вначале. А с этих 0x10 вроде похожая посылка. Адрес 16, функциями и 03 и 04 все читается готовой прогой.... Не подскажите куда копнуть... Я пока попробую другие библиотеки попробовать. Потому как, вот qModMaster - тоже отказывается читать, хотя соединяется... А прога, которая все читает AVReporter Modbus Communication Tester.

lagutin
26.08.2021, 10:03
Вот, кстати, догадался :) и проверил посылку посылку от qModMaster
Такая же, как и у меня длинная
0:01:21 RS485 PORT1 DATA RECEIVED, LEN=12
0x00 0x14 0x00 0x00 0x00 0x06 0x10 0x04 0x02 0x00 0x00 0x01
?????

Евгений Кислов
26.08.2021, 10:08
Считываю через конвертор ER-108. Но это не суть.

Cуть как раз в этом. Ваш прибор - это конвертер интерфейсов, он передает пакет, полученный по TCP, в COM-порт без каких-либо преобразований.
"Готовая программа" отправляет по TCP кадр протокола Modbus RTU (обычно это называют Modbus RTU over TCP) - т.е. ПР200 в итоге получает фрейм Modbus RTU и корректно его обрабатывает.
Вы же с помощью Nmodbus4 отправляете по TCP кадр протокола Modbus TCP (с MBAP Header и без CRC) - и когда ПР200 получает фрейм Modbus TCP, то, естественно, считает его невалидным.
Вам надо разобраться, как с помощью вашей библиотеки отправлять по TCP фреймы протокола Modbus RTU.

lagutin
26.08.2021, 10:27
Я так и думал, что придется столкнуться с этим "Modbus RTU over TCP". Придется окунуться в эти "дебри". Но не сочтите за наглость, может у кого есть готовое решение по этой библиотеке... Сам я боюсь, не потяну......

lagutin
27.08.2021, 14:15
Если, кому интересно, вот решение. В общем, разница между RTU и TCP и , видимо, прочими "модбасами" в заголовках и наличии контрольной суммы. Возникла мысль поменять местами клиентов, то бишь comport и TCP клиента в библиотеке, но уж очень там код мудреный. Наутро пришла мысль просто подкинуть другого клиента в коде. И сработало. Для этой библиотеки, как я понял все без разницы. Создаем мастера, он формирует заголовки, а функции для всех одни. А мастера "всеядны". Заговорился, вот код.
Создаем клиента TCP:
tcpClient = new TcpClient();
IAsyncResult asyncResult = tcpClient.BeginConnect(ipAddress, tcpPort, null, null);
asyncResult.AsyncWaitHandle.WaitOne(3000, true); //wait for 3 sec
И вместо создания мастера TCP создаем мастера RTU :
masterRtu = ModbusSerialMaster.CreateRtu(tcpClient);
ushort[] rtrtgf = masterRtu.ReadInputRegisters(16, 512, 3);
И он прекрасно отправляет по сети посылку вида RTU и ПР200 через конвертер начала слать ответы. Ну пока связь прерывается, еще немного глючит кое где, но это я уже думаю детали.