Вход

Просмотр полной версии : ModBus rtu Linux ПВТ100



art_air
21.06.2018, 15:48
Добрый день!
В университете поставили задачу получить данные о температуре и влажности с прибора ПВТ100 и вывести их в консоль линукса (Ubuntu 16.04). Общаюсь с оборудованием через ssh. Физически потрогать или что-то сделать не могу (пару раз в день там находятся люди, которых можно попросить что-то сделать). Как мне сказали, там стоит преобразователь USB-to-RS485. (больше мне подробностей не сообщили, но если что-то спросить, то думаю, в ответе проблем у них не будет). Раньше не был знаком в вашей продукцией, как и с интерфейсом RS-485 и протоколом ModBus. Первым делом начал гуглить, что и как. Собственно нашел много всяких примеров, но ни один не смог заставаить работать. Из того, что нашел, пробовал: (информация с этого сайта тык (https://stackoverflow.com/questions/17081442/python-modbus-library))


minimalmodbus
modbus-tk

Так вот, для начала опробовал я библиотеку modbus-tk, но ничего не вышло (дальше я с ней не разбирался).
Перешёл к библиотеке minimalmodbus и уделил ей больше внимания.

Вот руководство по эксплуотации (http://www.owen.ru/uploads/rie_pvt100_2579.pdf) для моего устройства. От туда взял, что:


baudrate = 9600
bytesize = 8
stopbits = 1
Контроль четности: отсутствует
адрес сетевого прибора: 16 (10cc, как я понял)

Далее мне нужно получить данные сначал просто с датчика температуры:
Номер первого регистра (из даташита) = 0x0102 hex или 258 10сс

Собственно вот са код программы: (с использованием minimalmodbus)

import minimalmodbus
import serial

port = '/dev/ttyUSB0' # serial port
slave_adress = 16 # 10cc
## Number of the first register 0x0102 16cc or 258 10cc ##
hex_number = 102
dec_number = 258
dec_name_number = 2
register_number = dec_name_number
number_of_decimals = 2 # temperature value from -4000 to +12000 C
baudrate = 9600 # from datasheet
bytesize = 8 # from datasheet
stopbits = 1 # from datasheet
timeout = 0.1 # where to get it from?

def main():
print("starting...")

minimalmodbus.BAUDRATE = baudrate
minimalmodbus.TIMEOUT = timeout
instrument = minimalmodbus.Instrument(port, slave_adress, mode='rtu')
instrument.mode = minimalmodbus.MODE_RTU # rtu or ascii mode
print(instrument.read_register(register_number, numberOfDecimals=2, functioncode=3, signed=True)) # Registernumber, number of decimals
instrument.debug = True

main()

print(instrument.read_register(register_number, numberOfDecimals=2, functioncode=3, signed=True))
В документации к данной библиотеке написано, что все используется в 10сс (ссылкаl (http://minimalmodbus.readthedocs.io/en/master/readme.html))

Тогда получается, я ввожу следующие данные:


register_number = 258 (номер нашего первого регистра в dec)
numberOfDecimals =2 (тк надо поделить на 100)
functioncode = 3 (чтение значений из нескольких регистров хранения, в даташите только его можно использовать для чтения, как я понял)
signed=True (тк мне нужен знак)

При запуске получаю ошибку:


Traceback (most recent call last):
File "./ModBus_RTU_master.py", line 30, in <module>
main()
File "./ModBus_RTU_master.py", line 27, in main
print(instrument.read_register(register_number, numberOfDecimals=2, functioncode=3, signed=True)) # Registernumber, number of decimals
File "/usr/local/lib/python3.5/dist-packages/minimalmodbus.py", line 258, in read_register
return self._genericCommand(functioncode, registeraddress, numberOfDecimals=numberOfDecimals, signed=signed)
File "/usr/local/lib/python3.5/dist-packages/minimalmodbus.py", line 697, in _genericCommand
payloadFromSlave = self._performCommand(functioncode, payloadToSlave)
File "/usr/local/lib/python3.5/dist-packages/minimalmodbus.py", line 795, in _performCommand
response = self._communicate(request, number_of_bytes_to_read)
File "/usr/local/lib/python3.5/dist-packages/minimalmodbus.py", line 930, in _communicate
raise IOError('No communication with the instrument (no answer)')
OSError: No communication with the instrument (no answer)

Дальше я решил проверить само устройство: (сделал всё, что нашел)


sudo chmod 0777 /dev/ttyUSB0 (это я ещё сделал первым делом)


ls -all /dev/ttyUSB0


crwxrwxrwx 1 root dialout 188, 0 Jun 21 08:25 /dev/ttyUSB0

stty -F /dev/ttyUSB0 -a


speed 9600 baud; rows 0; columns 0; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;
eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; discard = ^O; min = 0; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread clocal -crtscts
-ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff
-iuclc -ixany imaxbel -iutf8
-opost -olcuc -ocrnl -onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
-isig -icanon -iexten -echo -echoe -echok -echonl -noflsh -xcase -tostop -echoprt
-echoctl -echoke -flusho -extproc

Дальше решил проверить все slave adress с 1 по 258 для registeradress 1-3 (название прибора по даташиту) и для всех возможных скоростей обмена 1200, 2400, 4800, 9600, 19200, 38400, 57600

Вот код программы:


import minimalmodbus
import serial

port = '/dev/ttyUSB0' # serial port
slave_adress = 1 # 10cc
## Number of the first register 0x0102 16cc or 258 10cc ##
hex_number = 102
dec_number = 258
register_number = dec_number
number_of_decimals = 1 # temperature value from -4000 to +12000 C
baudrate = 57600 # from datasheet
bytesize = 8 # from datasheet
stopbits = 1 # from datasheet
timeout = 0.1 # where to get it from?

def main():
print("starting...")

minimalmodbus.BAUDRATE = baudrate
minimalmodbus.TIMEOUT = timeout

for baudrate_test in [57600, 38400, 19200, 9600, 4800, 2400, 1200]:
minimalmodbus.BAUDRATE = baudrate_test
print(baudrate_test)
for slave_adress_test in range(247):
instrument = minimalmodbus.Instrument(port, slave_adress_test+1)
for register_number_test in [1, 2, 3]:
try:
print(instrument.read_register(register_number_tes t, 4, 3, True)) # Registernumber, number of decimals
print("slave adress: " + str(slave_adress_test+1) + " adress: " + str(register_number_test) + " OK")
instrument.debug = True
except:
i = 0
print(slave_adress_test+1)

main()

Дальше я посмотрел вот это (http://www.owen.ru/forum/showthread.php?t=26877&highlight=Modbus+rtu+python) на форуме, но я также не смог ничего дельного получуть.

Я прошу вас мне помочь или направить в какую-нибудь сторону, тыкнуть в ошибку. Мне необходимо после выходных сдать эту работу, а пока я ничего не смог получить с данного устройства.

PS
Появилась информация о переходнике: Silicon Labs CP210x USB to UART Bridge
Также теперь есть возможность подключаться дистанционно через teamviewer

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

melky
21.06.2018, 17:43
Это вам на форум программистов больше с такими вопросами. При чем ищите по питону. Быстрее помогут.

YuriBel
21.06.2018, 19:02
Я бы посоветовал отложить программирование в сторону(пока). Будучи на вашем месте, я бы первым делом поискал эмулятор модбас мастера для линукс, и запустив его, убедился, что связка порт-преобразователь-провода-датчик работает, и вы получаете ответ от прибора. А уже будучи убежденным, что все это работает, можно браться за написание программы и изучение незнакомых библиотек. Может у вас тривиальным образом провода попутаны, или питания на приборе нет.

capzap
21.06.2018, 22:41
Возможно нужно так вводить настройки порта instrument.serial.baudrate = 9600

danilk
21.06.2018, 23:01
Появилась информация о переходнике: Silicon Labs CP210x USB to UART Bridge
Также теперь есть возможность подключаться дистанционно через teamviewer

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

пользователя добавить в группу dialout
с 485 этот конвертер на лине работать вряд ли будет. нужен ftdi
есть лишние, цена 500₽.

melky
21.06.2018, 23:46
danilk они прекрасно работают, только вот смущает to UART, а разве ПВТ100 UART ? там вроде нормальный RS485

danilk
22.06.2018, 07:38
У меня есть переписка с силиконлабс на тему 210х. Работает только часть чипов. У меня был вроде 2102 и я 2 недели убил на это. С тех пор запасся нормальными конвертерами

melky
22.06.2018, 08:58
Ну у меня китайский на CP2102 USB-TTL, на Raspberry с пол пинка...

danilk
22.06.2018, 09:14
Да, 232 и TTL все ок, а 485 нет. Такой нюанс

melky
22.06.2018, 09:38
Так а при чем тут CP2102 разве в самом чипе реализован RS485, там же наверное обвязка сверху есть какая-то, ну или должна быть. Вот она наверное и хромает.

danilk
22.06.2018, 09:54
Вот она наверное и хромает.

не в обвязке дело, а в драйвере ядра. Под виндой все ок. Под линуксом я его тоже заводил, но работал крайне нестабильно. Ничего делать с ним не будут, тк микросхема снята с производства. Истину говорю, тк сам столкнулся с проблемой. На малинафоруме есть тоже пара веток с 2102 и 485.

Трофимов Артем
22.06.2018, 10:27
Добрый день. как то обучаясь Сям писал консольную программу обмена с МВ110-8А для опроса датчика под убунту. прикладываю исходник, возможно помогут.
в scaner.c посмотрите как работает программа
в my_header_for_modbus.h смените путь ведения лога
то что чип определяется to UART - всё нормально. в большинстве случаев за ним стоит микруха преобразования в RS485. просто комп видит только "лицо" чипа со стороны USB а не дальнейший каскад преобразования
p.s. код работает но не идеально. в то время не учитывал задержки переключения Rx/Tx , поэтому могут быть пропуски пакета

danilk
22.06.2018, 13:32
# ---------------instrument settings--------------------------------
try:
COM = MySerial.ComPort(portName, baudRate, timeout=0.07)
except:
raise Exception('Error openning port!')

try:
MVA = Owen.OwenDevice(COM, 16)
print MVA
except Owen.OwenProtocolError:
print 'Модуль ввода отсутствует'
s_log(u'Модуль ввода отсутствует')

try:
MU = Owen.OwenDevice(COM, 8)
print MU
except Owen.OwenProtocolError:
print 'Модуль вывода отсутствует'
s_log(u'Модуль вывода отсутствует')

MMU = minimalmodbus.Instrument(portName, slaveaddress=8, mode='rtu') # port name, slave address (in decimal)
MMU.debug = False
MMU.serial.baudrate = baudRate

try:
if MMU.read_register(pwmPeriodReg + SSRPwm0) <> Freq:
MMU.write_register(pwmPeriodReg + SSRPwm0, Freq)
print 'Корректный период ШИМ'
mModInitStr += u'Корректный период ШИМ,'
else:
mModInitStr += u'Корректировка ШИМ не нужна,'
except IOError, ValueError:
try:
if MMU.read_register(pwmPeriodReg + SSRPwm0) <> Freq:
MMU.write_register(pwmPeriodReg + SSRPwm0, Freq)
print 'Корректный период ШИМ'
mModInitStr += u'Корректный период ШИМ,'
else:
mModInitStr += u'Корректировка ШИМ не нужна,'
except IOError, ValueError:
print 'Ошибка установки периода ШИМ'
mModInitStr += u'Ошибка установки периода ШИМ,'

try:
MMU.write_register(SSRPwm0, 0)
MMU.write_register(Fan1, 0)
MMU.write_register(Cont1, 0)
MMU.write_register(Bell1, 0)
print 'Порты в нуле'
mModInitStr += u' Порты в нуле'
except IOError, ValueError:
try:
MMU.write_register(SSRPwm0, 0)
MMU.write_register(Fan1, 0)
MMU.write_register(Cont1, 0)
MMU.write_register(Bell1, 0)
print 'Порты в нуле'
mModInitStr += u' Порты в нуле'
except IOError, ValueError:
print 'Ошибка установки портов'
mModInitStr += u' Ошибка установки портов'




class TempThread(QtCore.QThread): # работа с АЦП в потоке
def __init__(self, temp_signal, parent=None):
super(TempThread, self).__init__(parent)
self.temp_signal = temp_signal
self.isRun = False
self.counter=0 # ошибки
self.counter2=0 # операции чтения
self.temp_array = np.array([[0.0, 0],
[0.0, 0],
[0.0, 0],
[0.0, 0],
[0.0, 0],
[0.0, 0],
[0.0, 0]])

def run(self):
global portIsBusy
while self.isRun:
a = datetime.datetime.now()
s = time.localtime()
Ch = 1
while portIsBusy:
print 'temp busy', portIsBusy
time.sleep(0.05)
while Ch <= 3:
try:
portIsBusy = True
terr = self.temp_array[Ch][0]
# читаем с адреса базовый-1
result = MVA.GetIEEE32('rEAd', Ch-1, withTime=True)
print 'Ch', Ch, 'res:', result
self.temp_array[Ch][0] = round(result['value'],1)
self.temp_array[Ch][1] = int(0)
except Owen.OwenUnpackError as e:
self.error_unpack(e, terr, Ch, s) # обрабатываем ошибку раскодировки данных
except Owen.OwenProtocolError:
try: # пробуем еще раз
terr = self.temp_array[Ch][0]
# читаем с адреса базовый-1
result = MVA.GetIEEE32('rEAd', Ch - 1, withTime=True)
print 'Ch', Ch, 'res:', result
self.temp_array[Ch][0] = round(result['value'], 1)
self.temp_array[Ch][1] = int(0)
except Owen.OwenUnpackError as e:
self.error_unpack(e, terr, Ch, s) # обрабатываем ошибку раскодировки данных
except Owen.OwenProtocolError:
print 'Модуль ввода не ответил, канал: ' + str(Ch)
s_log(u'Модуль ввода не ответил, канал: ' + str(Ch) + ' ' + str(
s.tm_hour) + ':' + str(s.tm_min) + ':' + str(s.tm_sec))
self.counter += 1
if COM.isOpen():
COM.close()
COM.open()

print self.temp_array[Ch]
Ch+=1
portIsBusy = False
print '-------------------',str(s.tm_hour), ':', str(s.tm_min), ':', str(s.tm_sec), '-------------------'
self.temp_signal.emit(self.temp_array)
self.counter2 +=1
error_buffer[0] = u'Ошибки = ' + str(self.counter) + u', ' + u'Вызовы = ' + str(self.counter2)
print error_buffer[0]
sleepparam = float(str(datetime.datetime.now() - a)[-6:]) / 1000000
print '-------------------', sleepparam, '-------------------'
time.sleep(5 - sleepparam)

def stop(self):
self.isRun = False

def error_unpack(self, e, terr, Ch, s):
if len(e.data) == 1:
self.temp_array[Ch][1] = int(1)
self.temp_array[Ch][0] = terr
# это код ошибки
if ord(e.data[0]) == 0xfd:
print 'Обрыв датчика'
s_log(u'Обрыв датчика, канал: ' + str(Ch) + ' ' + str(s.tm_hour) + ':' + str(
s.tm_min) + ':' + str(s.tm_sec))
elif ord(e.data[0]) == 0xff:
print 'Некорректный калибровочный коэффициент'
s_log(u'Некорректный калибровочный коэффициент, канал: ' + str(Ch) + ' ' + str(
s.tm_hour) + ':' + str(s.tm_min) + ':' + str(s.tm_sec))
elif ord(e.data[0]) == 0xfb:
print 'Измеренное значение слишком мало'
s_log(u'Измеренное значение слишком мало, канал: ' + str(Ch) + ' ' + str(
s.tm_hour) + ':' + str(s.tm_min) + ':' + str(s.tm_sec))
elif ord(e.data[0]) == 0xfa:
print 'Измеренное значение слишком велико'
s_log(u'Измеренное значение слишком велико, канал: ' + str(Ch) + ' ' + str(
s.tm_hour) + ':' + str(s.tm_min) + ':' + str(s.tm_sec))
elif ord(e.data[0]) == 0xf7:
print 'Датчик отключен'
s_log(u'Датчик отключен, канал: ' + str(Ch) + ' ' + str(
s.tm_hour) + ':' + str(s.tm_min) + ':' + str(s.tm_sec))
elif ord(e.data[0]) == 0xf6:
print 'Данные температуры не готовы'
s_log(u'Данные температуры не готовы ' + str(Ch) + ' ' + str(
s.tm_hour) + ':' + str(s.tm_min) + ':' + str(s.tm_sec))
elif ord(e.data[0]) == 0xf0:
print 'Значение заведомо неверно'
s_log(u'Значение заведомо неверно, канал: ' + str(Ch) + ' ' + str(
s.tm_hour) + ':' + str(s.tm_min) + ':' + str(s.tm_sec))
else:
print 'wtf it needs?'
s_log(u'Неизвестная ошибка ввода, канал: ' + str(Ch) + ' ' + str(
s.tm_hour) + ':' + str(s.tm_min) + ':' + str(s.tm_sec))
if COM.isOpen():
COM.close()
COM.open()

art_air
22.06.2018, 18:36
Да, действительно связи с портом не было. Ждал, пока кто-нибудь там появится и проверит. Проблема была в том, что провод на переходнике переломился. Данная программа работает исправно.

Спасибо!

art_air
22.06.2018, 18:38
Добрый день. как то обучаясь Сям писал консольную программу обмена с МВ110-8А для опроса датчика под убунту. прикладываю исходник, возможно помогут.
в scaner.c посмотрите как работает программа
в my_header_for_modbus.h смените путь ведения лога
то что чип определяется to UART - всё нормально. в большинстве случаев за ним стоит микруха преобразования в RS485. просто комп видит только "лицо" чипа со стороны USB а не дальнейший каскад преобразования
p.s. код работает но не идеально. в то время не учитывал задержки переключения Rx/Tx , поэтому могут быть пропуски пакета
Спасибо большое!

art_air
22.06.2018, 18:46
Всем спасибо большое за ответы!

art_air
26.06.2018, 12:22
Всем добрый день! Работу одобрили, но задали два вопроса:
1) Как несколько программ могут работать с одним последовательным устройством, представляющим шину?
(Я же правильно понимаю, тут проблема в том, что если одновременно две программы отправят сообщение разным датчикам, например с адресом 16 и 1, и ответы приду в одно время, то я сниму полную кашу?)
2) Если программа хочет начать работать с датчиком, а он уже работает с другим процессом, как реализовать корректное ожидание освобождение процесса? И как это отслеживать?
(Я думал перед опросом датчика, слушать шину, если что-то проходит, то значит, что кто-то общается, тогда ждать, пока канал не замолчит, но это костыль какой-то. Например, если датчик шлёт раз в 30 секунд ответ, то я получу, что шина свободна)

Я буду благодарен за какие-нибудь направления или советы, так как кроме идеи, которую я выдвинул выше, я ничего не могу придумать. Гуглил, но ничего не нашёл, скорее всего не так гуглил.

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

Трофимов Артем
26.06.2018, 12:41
Вам нужны объекты синхронизации. используйте мьютексы . также в программах закрывайте порт после приёма пакет, тогда другая программа сможет открыть порт и пускай он висит пока не сможет открыть порт, как открыла сделала цикл обмена закрыла, уснула на нужный период. либо сделать основную программу диспетчер запросов, которая будет управлять другими программами обмена, будет обеспечивать последовательность их выполнения, но это уже скорей не программы отдельные будут, а потоки внутри одного приложения.

p.s. впринципе когда две программы обращаются к одному последовательному порту - не есть хорошее решение, и лучше пересмотреть архитектуру приложения

art_air
26.06.2018, 18:31
Вам нужны объекты синхронизации. используйте мьютексы . также в программах закрывайте порт после приёма пакет, тогда другая программа сможет открыть порт и пускай он висит пока не сможет открыть порт, как открыла сделала цикл обмена закрыла, уснула на нужный период. либо сделать основную программу диспетчер запросов, которая будет управлять другими программами обмена, будет обеспечивать последовательность их выполнения, но это уже скорей не программы отдельные будут, а потоки внутри одного приложения.

p.s. впринципе когда две программы обращаются к одному последовательному порту - не есть хорошее решение, и лучше пересмотреть архитектуру приложения

Артём, спасибо большое!
Да, я уже понял, что это плохая идея, пока искал информацию. Сейчас пойду прочту про "мьютексы" и воспользуюсь советом про открытие/закрытие порта.

danilk
27.06.2018, 10:49
также в программах закрывайте порт после приёма пакет

я свой класс порта делал, чтобы в нем автоматом закрывался порт в части передачи пакетом, зачем в основной программе эти операции?

Трофимов Артем
27.06.2018, 13:09
я свой класс порта делал, чтобы в нем автоматом закрывался порт в части передачи пакетом, зачем в основной программе эти операции?

лишь чтобы поиграться на начальном уровне , пока входишь в тему. да и вцелом при системном программировании не раз сталкивался с информацией, что держать постоянно открытым порт/файл/сокет не есть есть хорошо.