Спасибо! Очень помогло в разборе ответа от термостата. В принципе с вашей помощью справился с задачей, поднял web сервер через который можно отправлять и принимать команды термостатам. Правда он возвращает ответ со странным опозданием на одну перезагрузку страницы. Отправляет вовремя, а выводит ответ только после перезагрузки, хотя все должно обрабатываться последовательно.. Если кто-нибудь ткнет где проблема, буду очень благодарен. Сразу извиняюсь за качество кода.. Учучь потихоньку.
#include <SPI.h>
#include <UIPEthernet.h>
// определяем конфигурацию сети
byte mac[] = {0xAE, 0xB2, 0x26, 0xE4, 0x4A, 0x5C}; // MAC-адрес
byte ip[] = {192, 168, 1, 10}; // IP-адрес
EthernetServer server(80); // создаем сервер, порт 80
EthernetClient client; // объект клиент
boolean flagEmptyLine = true; // признак строка пустая
char tempChar;
char urnFromRequest[51]; // строка URN из запроса
boolean urnReceived = false; // признак URN принят
unsigned int indUrn; // адрес в строке URN
int pind, valued, pulsed = 0, digit, TelLen;;
typedef struct {
char a;
char b;
} Btype;
byte SendData[8], SerialData[16], req[8];
int getNumbers(char *str, char *value);
String getRequest (char *str, char *value);
void setup() {
Ethernet.begin(mac, ip); // инициализация контроллера
server.begin(); // включаем ожидание входящих соединений
Serial.begin(4800);
Serial2.begin(4800);
Serial.print("Server address:");
Serial.println(Ethernet.localIP()); // выводим IP-адрес контроллера
Serial.print("");
pinMode(5, OUTPUT);
}
void loop() {
client = server.available(); // ожидаем объект клиент
if (client) {
flagEmptyLine = true;
flagEmptyLine = true;
urnReceived = false;
indUrn = 0xffff;
Serial.println("New request from client:");
while (client.connected()) {
if (client.available()) {
tempChar = client.read();
Serial.write(tempChar);
if ( urnReceived == false ) {
if ( indUrn == 0xffff ) {
if ( tempChar == '/' ) indUrn = 0;
}
else {
// запись строки
if ( tempChar == ' ' ) {
// URN закончен
urnFromRequest[indUrn] = 0;
urnReceived = true;
}
else {
// загрузка символа URN в строку
urnFromRequest[indUrn] = tempChar;
indUrn++;
if ( indUrn > 49 ) {
// переполнение
urnFromRequest[50] = 0;
urnReceived = true;
}
}
}
}
if (tempChar == '\n' && flagEmptyLine) {
// пустая строка, ответ клиенту
client.println(F("HTTP/1.1 200 OK"));
client.println(F("Content-Type: text/html; charset=utf-8"));
//client.println(F("Refresh: 2")); // обновить страницу автоматически
client.println(F("Connection: close"));
client.println();
client.println(F("<!DOCTYPE HTML>"));
client.println(F("<html>"));
client.println(F("<br>"));
client.println(urnFromRequest);
char *str = urnFromRequest;
char pin[3], value[2], pulse[5], modbus[17];
while (*str != '\r' && *str != '\0')
{
switch (*str)
{
case 'P':
pind = getNumbers(str, pin);
break;
case 'S':
valued = getNumbers(str, value);
break;
case 'T':
pulsed = getNumbers(str, pulse);
break;
case 'R':
getRequest(str, modbus);
digitalWrite(7, HIGH);
for (byte i = 0; i < 8; i++)
Serial2.write(SendData[i]);
digitalWrite(7, LOW);
delay(9);
TelLen = 0;
SerialData[TelLen] = (char)Serial2.read();
if (SerialData[TelLen] != 0) {
TelLen++;
}
while (Serial2.available()) {
SerialData[TelLen] = (char)Serial2.read();
TelLen++;
delay(1);
}
delay(500);
client.println(F("<br>"));
client.println(F("ModBus RTU ответ: "));
for (byte i = 0; i < TelLen; i++) {
client.println(SerialData[i], HEX);
Serial.println(SerialData[i]);
req[i] = SerialData[i];
}
delay(500);
break;
}
str++;
}
Serial.println("__________");
if (valued > 1)
Serial.println("ошибка или уже делали");
else {
digitalWrite(pind, valued);
if (pulsed) {
delay(pulsed);
digitalWrite(pind, !valued);
}
pulsed = 0;
valued = 2;
}
client.println(F("<br>"));
for (int i = 0; i <= 53; i++) {
client.println(("PIN "));
client.println(i);
client.println((digitalRead(i)));
client.println(F("<br>"));
}
for (int i = 54; i <= 69; i++) {
client.println(("PIN "));
client.println(i);
client.println((analogRead(i)));
client.println(F("<br>"));
}
client.println(F("<form action=\"http://192.168.1.10\" method=\"get\" name=\"form\">"));
client.println(F("PIN"));
client.println(F("<input type=\"number\" name=\"P\">"));
client.println(F("значение"));
client.println(F("<input type=\"number\" name=\"S\">"));
client.println(F("время"));
client.println(F("<input type=\"number\" name=\"T\">"));
client.println(F("ModBus RTU запрос"));
client.println(F("<input type=\"text\" name=\"R\">"));
client.print(F("<input type=\"submit\" text=\"передать\">"));
client.print( (float)millis() / 1000. );
client.println(F("<br>"));
for (byte i = 0; i < 8; i++) {
client.println(req[i], HEX);
}
client.println(F("<br>"));
for (byte i = 0; i < 8; i++) {
client.println(SerialData[i], HEX);
}
client.println(F("</html>"));
break;
}
if (tempChar == '\n') {
// новая строка
flagEmptyLine = true;
}
else if (tempChar != '\r') {
// в строке хотя бы один символ
flagEmptyLine = false;
}
}
}
delay(1);
client.stop();
Serial.println("Break");
for (byte i = 0; i < TelLen; i++) {
Serial.println(SerialData[i]);
req[i] = SerialData[i];
}
}
}
int getNumbers(char *str, char *value)
{
int i = 0;
str = str + 2;
digit = 0;
while ('0' <= *str && *str <= '9')
{
value[i] = *str;
digit = digit * 10 + value[i] - 48;
i++;
str++;
}
value[i] = '\0';
return (digit);
}
String getRequest(char *str, char *value)
{
int i = 0;
str = str + 2;
for (i = 0; i < 17; i++)
{
value[i] = *str;
str++;
}
value[i] = '\0';
convert(value, SendData);
return (value);
}
int convert(char *str, unsigned char *byte)
{
Btype *chp;
chp = (Btype*)str;
int i = 0;
unsigned char c1, b1;
while (chp[i].a) {
c1 = (chp[i].a >= 65) ? 55 : 48;
b1 = (chp[i].a - c1) << 4;
c1 = (chp[i].b >= 65) ? 55 : 48;
byte[i] = b1 | (chp[i].b - c1); i++;
}
return i;
}
Спасибо ещё раз за помощь в разборе отчета термостата, застрял немного, спалил выход шилда MAX485, и никак не мог понять почему ничего не принимает) Пришлось подпаяться напрямую к Rx Tx термостата на входе в его MAX485, так завелось..
Спасибо, стабилитроны добавлю, не думал что они с чего то так горят. Задержки были нужны для более удобной интерпретации отладочной информации в Serial0, они там временно. По поводу строчек:
while (Serial2.available()) {
if (TelLen < sizeof(SerialData) - 1) {
SerialData[TelLen] = Serial2.read();
}
else {
Serial2.read();
}
Не понимаю мы сравниваем длину SerialData которая по сути всегда равна 8? С TelLen который мы не меняем в этом цикле? Эта часть у меня не заработала, и я её не понял. Остальное, спасибо, исправил по вашему совету, так оно гораздо лучше выглядит. Но с чтением и выводом SerialData остается проблемка, оно отстает на один цикл.
У вас размер буфера задан жестко и он равен 16, что будет если ваше устройство "случайно" пошлет подряд больше 16 байт ?
У вас тут конечно нет проверки контрольной суммы, это важно, а то вы можете таких странных данных напихать в веб интерфейс , что человек будет долго удивляться.
485 - интерфейс токовой петли, при превышении напряжения между двумя устройствами (например разные блоки питания), этот самый ток выжигает входные ноги микросхемы... Вот такой косяк, а может китайская подделка.
Точно, спасибо! Тогда там нужно ещё добавить TelLen++; и все завелось.. Вот только все равно, после отправки команды на термостат выводяться нули, и только со второго запроса получаем предыдущий ответ.. До проверки контрольной суммы обязательно дойду, тем более вы уже подсказали как с ней быть.. Единственный нюанс есть еще термостат с привычным ModBus RTU, и он считает контрольные суммы иначе.. Про разные потенциалы я интуитивно догадывался, и отрубал блок питания от ноутбука при соединении, но не спасло..
Нужно понять, что означают нули. У вас же Serial2 - хардварный ? Попробуйте в 3 строке моего кода поставить печать buffSize для отладки. Возможно у вас не реализован метод availableForWrite.
Смысл в том, что когда вы что то выводите в порт, это не означает, что эти данные ушли. Они отправляются какое то время. В моем примере перед отправкой я проверяю размер буфера порта, одновременно ожидаю его освобождения. Далее я передаю все данные в буфер порта, а потом жду когда они уйдет, опять же проверяя свободный размер буфера. Только после этого я переключаю направление передачи.
Дальше нужно дождаться ответа от термостата. Нули могут читаться из порта если вы читаете его не убедившись в наличии данных (Serial.available()), либо из-за помех происходящих в момент переключения.
Вообще правильно читать данные не во время формирования страницы, а постоянно, в буфер, а передавать клиенту из буфера. Вот давайте прикинем, что будет, если у вас термостат выдаст не 8 байт , а вдруг 7.
Получается что после отправки он верно получает ответ, каждый раз, например мы запросили состояние, и судя по всему получили ответ, но он не ушёл в сформированную страничку в браузер, затем мы может отправить термостату что угодно, например FF, и только после этого браузер нарисует верный ответ на предыдущий запрос. То есть с отправкой и получением данных проблем нет, проблема с отрисовкой их в браузере с опозданием, как будто мы обрабатываем этот ответ после отрисовки страницы, и выводим уже соответственно при следующей перезагрузке. Но это не так. Не могу понять почему так происходит. К тому же в Serial0 выводится так же 0 сразу после получения, и следующий раз верный ответ но на предыдущий запрос.
По идее катастрофы не будет если мы получим 7 байт, то восьмой мы не заменим на новый, и он останется от предыдущего запроса, тут помог бы подсчет контрольной суммы.
Опрашивать Serial2 постоянно имеет смысл? Ведь slave отправляет данные только в ответ на запрос?
Но мы ведь проверяем Serial2.available() это как раз и говорит что там еще есть данные для чтения? Когда они кончаться, пусть даже на 7, условия цикла нарушиться и мы пойдем дальше. Разве не так?
У вас размер буфера задан жестко и он равен 16, что будет если ваше устройство "случайно" пошлет подряд больше 16 байт ?
У вас тут конечно нет проверки контрольной суммы, это важно, а то вы можете таких странных данных напихать в веб интерфейс , что человек будет долго удивляться.
485 - интерфейс токовой петли, при превышении напряжения между двумя устройствами (например разные блоки питания), этот самый ток выжигает входные ноги микросхемы... Вот такой косяк, а может китайская подделка.
Вообще-то не токовой петли, а дифференциальная линия.
Но земли, конечно, желательно иметь примерно одинаковые.
Хотя из опыта общения с видеокамерами 485-ый интерфейс дох только когда на него 220 подавали.
Потому что там земля шла по коаксиалу, хотя не всегда.
Должны быть две задачи, одна беседует с контролером, а вторая с веб клиентом. К моменту когда клиент только собрался спросить у сервера как там температура, сервер уже должен спросить об этом контролер и получить ответ.
Да, наверно это в идеале должно быть так. Хотя, с вашей помощью, получилось реализовать вполне рабочий вариант. Но нет предела совершенству) Намекните направление для реализации двух задач? Ведь что именно, в какой момент и у какого устройства запросит клиент заранее не известно. Получается формировать запрос и ждать ответ, все равно, придется после получения get запроса в теле веб сервера?
Я бы сделал как Cадман. От того как часто вы опрашиваете термостат зависит только свежесть данных у вас на сервере, которые вы и выдаете клиенту.
Например не взирая на то есть ли подключение веб клиента или нет, вы постоянно , раз в секунду (например) опрашиваете термостат, складываете полученные данные к себе в буфер. Если клиент подключился - отдаете ему данные из буфера. Вы же за хранение в озу данных никому не платите ? Каждый байт данных с термостата у вас не тарифицируется ? Бесплатно же ;) Терять то нечего.
Спасибо! Очень помогло в разборе ответа от термостата. В принципе с вашей помощью справился с задачей, поднял web сервер через который можно отправлять и принимать команды термостатам. Правда он возвращает ответ со странным опозданием на одну перезагрузку страницы. Отправляет вовремя, а выводит ответ только после перезагрузки, хотя все должно обрабатываться последовательно.. Если кто-нибудь ткнет где проблема, буду очень благодарен. Сразу извиняюсь за качество кода.. Учучь потихоньку.
Спасибо ещё раз за помощь в разборе отчета термостата, застрял немного, спалил выход шилда MAX485, и никак не мог понять почему ничего не принимает) Пришлось подпаяться напрямую к Rx Tx термостата на входе в его MAX485, так завелось..
Я тут уже сто раз рассказывал о проблемах китайских шилдов. У вас оно так будет гореть. Схема:
https://media2.24aul.ru/imgs/5320f704e1bf2714b47e5453/nabor-dlya-sborki-preobrazovatelya-interfeysov-v-rs485-1-3830949.jpg
Обязательно добавьте два стабилитрона.
Что то у вас работа с модбас шиной мутная какая то, попробуйте строки 106-121 заменить вот на это:
Кстати, а для чего задержки аж по полсекунды ?
Спасибо, стабилитроны добавлю, не думал что они с чего то так горят. Задержки были нужны для более удобной интерпретации отладочной информации в Serial0, они там временно. По поводу строчек:
Не понимаю мы сравниваем длину SerialData которая по сути всегда равна 8? С TelLen который мы не меняем в этом цикле? Эта часть у меня не заработала, и я её не понял. Остальное, спасибо, исправил по вашему совету, так оно гораздо лучше выглядит. Но с чтением и выводом SerialData остается проблемка, оно отстает на один цикл.
У вас размер буфера задан жестко и он равен 16, что будет если ваше устройство "случайно" пошлет подряд больше 16 байт ?
У вас тут конечно нет проверки контрольной суммы, это важно, а то вы можете таких странных данных напихать в веб интерфейс , что человек будет долго удивляться.
485 - интерфейс токовой петли, при превышении напряжения между двумя устройствами (например разные блоки питания), этот самый ток выжигает входные ноги микросхемы... Вот такой косяк, а может китайская подделка.
Точно, спасибо! Тогда там нужно ещё добавить TelLen++; и все завелось.. Вот только все равно, после отправки команды на термостат выводяться нули, и только со второго запроса получаем предыдущий ответ.. До проверки контрольной суммы обязательно дойду, тем более вы уже подсказали как с ней быть.. Единственный нюанс есть еще термостат с привычным ModBus RTU, и он считает контрольные суммы иначе.. Про разные потенциалы я интуитивно догадывался, и отрубал блок питания от ноутбука при соединении, но не спасло..
Да, пропустил. Не проверял.
Нужно понять, что означают нули. У вас же Serial2 - хардварный ? Попробуйте в 3 строке моего кода поставить печать buffSize для отладки. Возможно у вас не реализован метод availableForWrite.
Смысл в том, что когда вы что то выводите в порт, это не означает, что эти данные ушли. Они отправляются какое то время. В моем примере перед отправкой я проверяю размер буфера порта, одновременно ожидаю его освобождения. Далее я передаю все данные в буфер порта, а потом жду когда они уйдет, опять же проверяя свободный размер буфера. Только после этого я переключаю направление передачи.
Дальше нужно дождаться ответа от термостата. Нули могут читаться из порта если вы читаете его не убедившись в наличии данных (Serial.available()), либо из-за помех происходящих в момент переключения.
Вообще правильно читать данные не во время формирования страницы, а постоянно, в буфер, а передавать клиенту из буфера. Вот давайте прикинем, что будет, если у вас термостат выдаст не 8 байт , а вдруг 7.
Получается что после отправки он верно получает ответ, каждый раз, например мы запросили состояние, и судя по всему получили ответ, но он не ушёл в сформированную страничку в браузер, затем мы может отправить термостату что угодно, например FF, и только после этого браузер нарисует верный ответ на предыдущий запрос. То есть с отправкой и получением данных проблем нет, проблема с отрисовкой их в браузере с опозданием, как будто мы обрабатываем этот ответ после отрисовки страницы, и выводим уже соответственно при следующей перезагрузке. Но это не так. Не могу понять почему так происходит. К тому же в Serial0 выводится так же 0 сразу после получения, и следующий раз верный ответ но на предыдущий запрос.
По идее катастрофы не будет если мы получим 7 байт, то восьмой мы не заменим на новый, и он останется от предыдущего запроса, тут помог бы подсчет контрольной суммы.
Опрашивать Serial2 постоянно имеет смысл? Ведь slave отправляет данные только в ответ на запрос?
Да нет вы вместо отправки страницы клиенту будете доооолго ждать восьмого байта.
Но мы ведь проверяем Serial2.available() это как раз и говорит что там еще есть данные для чтения? Когда они кончаться, пусть даже на 7, условия цикла нарушиться и мы пойдем дальше. Разве не так?
У вас размер буфера задан жестко и он равен 16, что будет если ваше устройство "случайно" пошлет подряд больше 16 байт ?
У вас тут конечно нет проверки контрольной суммы, это важно, а то вы можете таких странных данных напихать в веб интерфейс , что человек будет долго удивляться.
485 - интерфейс токовой петли, при превышении напряжения между двумя устройствами (например разные блоки питания), этот самый ток выжигает входные ноги микросхемы... Вот такой косяк, а может китайская подделка.
Вообще-то не токовой петли, а дифференциальная линия.
Но земли, конечно, желательно иметь примерно одинаковые.
Хотя из опыта общения с видеокамерами 485-ый интерфейс дох только когда на него 220 подавали.
Потому что там земля шла по коаксиалу, хотя не всегда.
Не путайте мягкое с теплым. А может это я путаю. Пойду даташит посмотрю.
То что диф, это очевидно, а вот с токовой петлей это я что то из другой оперы... Это мне dali навеяло, там что то типа цифровой токовой петли.
Токовая не получится. Они в параллель сажаются, а токовая последовательности требует.
Проблема с чтением ответа, разрешилась с помощью небольшой задержки после отправки команды и перед чтением, эта задержка должна быть более 30 мс
Проблема с чтением ответа, разрешилась с помощью небольшой задержки после отправки команды и перед чтением, эта задержка должна быть более 30 мс
Собственно, задержка - это костыль. По хорошему надо на лету анализировать ответ и дожидаться (с учетом таймаута), когда ответ придет полностью.
Должны быть две задачи, одна беседует с контролером, а вторая с веб клиентом. К моменту когда клиент только собрался спросить у сервера как там температура, сервер уже должен спросить об этом контролер и получить ответ.
Да, наверно это в идеале должно быть так. Хотя, с вашей помощью, получилось реализовать вполне рабочий вариант. Но нет предела совершенству) Намекните направление для реализации двух задач? Ведь что именно, в какой момент и у какого устройства запросит клиент заранее не известно. Получается формировать запрос и ждать ответ, все равно, придется после получения get запроса в теле веб сервера?
Периодический опрос слейвов, хранение значений метрик в ОЗУ и отдача их по запросу спасут отца русской демократии.
Я бы сделал как Cадман. От того как часто вы опрашиваете термостат зависит только свежесть данных у вас на сервере, которые вы и выдаете клиенту.
Например не взирая на то есть ли подключение веб клиента или нет, вы постоянно , раз в секунду (например) опрашиваете термостат, складываете полученные данные к себе в буфер. Если клиент подключился - отдаете ему данные из буфера. Вы же за хранение в озу данных никому не платите ? Каждый байт данных с термостата у вас не тарифицируется ? Бесплатно же ;) Терять то нечего.
Что то я не увидел тут ModBus'а.
Это ж протокол, со своими правилами построения пакета и контрольной суммой.
У вас ни первого ни второго.
Пакет тут построен по правилам мод баса. А вот контрольная сумма нет. Но это уже было выяснено в первых обсуждениях.