Опрос счетчика Меркурий через SMS

shura28
Offline
Зарегистрирован: 11.02.2014

Наверное никогда до конца не доделаю (как задумывал), поэтому с вашего позволения выложу, что есть AS IS - может кому-то пригодится. Тема не новая и наверняка уже много раз реализована, но все же...

Приблуда для опроса трехфазного счетчика Меркурий 230 ART-01 с помощью SMS опроса.

// опрос показаний счетчика Меркурий 230 ART-01 PQCSIN
#include "SIM900.h"
#include <SoftwareSerial.h>
#include "sms.h"
#include <OneWire.h>
#include <DallasTemperature.h>

#define ON true
#define OFF false

// Data wire is plugged into port 2 on the Arduino
#define ONE_WIRE_BUS 2

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);

SMSGSM sms;

char password[6] = "111111";  // пароль счетчика

int _LED1 = 41;
int _LED2 = 43;

byte answ[50];  // массив для записи ответа
bool echo = true;

char *mess[] = 
{
  "No answer!",
  "Echo is wrong!",
  "Something goes wrong!"
};

byte test_chanel[4] =  {0x00,         // сетевой адрес
                        0x00,         // тест канала связи
                        0x01,0xB0};   // CRC16 modbus
byte init_chanel[11] = {0x00,         // сетевой адрес
                        0x01,         // открытие канала связи
                        0x01,         // уровень доступа (потребитель)
                        0x01,0x01,0x01,0x01,0x01,0x01,  // пароль доступа
                        0x77,0x81};   // CRC16 modbus
byte opros1[6] =        {0x00,         // сетевой адрес
                        0x05,         // запрос на чтение регистров накопленной энергии
                        0x00,         // номер массива, номер месяца (от сброса, не имеет значения)
                        0x00,         // номер тарифа (энергия по сумме тарифов)
                        0x10,0x25};   // CRC16 modbus
byte opros2[6] =        {0x00,         // сетевой адрес
                        0x08,         // запрос на чтение параметров
                        0x16,         // чтение вспомогательных параметров (в т.ч. и напряжения)
                        0x11,         // BWRI (напряжение по фазам)
                        0x4F,0x8A};   // CRC16 modbus
byte opros3[6] =        {0x00,         // сетевой адрес
                        0x08,         // запрос на чтение параметров
                        0x16,         // чтение вспомогательных параметров (в т.ч. и напряжения)
                        0x21,         // BWRI (ток по фазам)
                        0x4F,0x9E};   // CRC16 modbus
byte opros4[6] =        {0x00,         // сетевой адрес
                        0x08,         // запрос на чтение параметров
                        0x16,         // чтение вспомогательных параметров (в т.ч. и напряжения)
                        0x00,         // BWRI (мощность по фазам)
                        0x8F,0x86};   // CRC16 modbus
byte close_chanel[4] =  {0x00,        // сетевой адрес
                         0x02,        // закрытие канала связи
                         0x80,0x71};  // CRC16 modbus

// int numdata;
boolean started=false;
char smsbuffer[160];
char n[20]; // phone number

// шаблон, который копируется в mySMS вызовом функции InitSMS
char t_mySMS[] = {'C', 'o', 'n', 's', '.', ':' , ' ', // 7
                0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0A,  //18
                'U', ':', ' ', // 21
                0x20, 0x20, 0x20, '/', 0x20, 0x20, 0x20, '/', 0x20, 0x20, 0x20, 0x0A, //33
                'A', ':', ' ', // 36
                0x20, 0x20, 0x20, '/', 0x20, 0x20, 0x20, '/', 0x20, 0x20, 0x20, 0x0A, // 48
                'P', ':', ' ', // 51
                0x20, 0x20, 0x20, '/', 0x20, 0x20, 0x20, '/', 0x20, 0x20, 0x20, 0x0A, // 63
                'T', ':', ' ', // 66
                0x20, 0x20, 0x20, 0x20, 0x20, 0x00}; // 72
char mySMS[72];

void LED1(boolean state)
{
  digitalWrite(_LED1, state);
}
void LED2(boolean state)
{
  digitalWrite(_LED2, state);
}

// Функция передает команду cmd длиной l (эль) в счетчик и
// возвращает число байт полученное в ответ и записанное в массив answ.
// Если функция возвращает отрицательное значение, то модуль этого значения - 
// есть индекс сообшения об ошибке в массиве mess.
int sendcom(byte *cmd, int l)
{
  int i = 0;  // счетчик всего полученных байт
  int j = 0;   // счетчик полученных байт без учета эха
  Serial2.write(cmd, l);
  delay(100);
  while (Serial2.available())
  {
    byte s = Serial2.read();
    delay(10);
    if (echo && (i<l))  // читаем эхо
    {
      if (s != cmd[i])
      {
        j = -1;
        break;
      }
    }
    else                // читаем ответ
    {
      if (j<sizeof(answ))
      answ[j] = s;
      j++;
    }
    i++;
  }
  return j;
}

// функция посимвольного вывода строки cmd в Serial в формате HEX
// отладочная функция
void arr2serial(byte *cmd, int l)
{
  for (int i=0; i< l; i++)
  {
    Serial.print(cmd[i], HEX);
    Serial.print(' ');
  }
  Serial.print('\n');
  
}

// инициализирует переменную mySMS шаблоном t_mySMS
void initSMS()
{
  for (int i=0; i<sizeof(mySMS); i++)
  {
    mySMS[i] = t_mySMS[i];
  }
}

void setup()
{
     pinMode(_LED1, OUTPUT);
     pinMode(_LED2, OUTPUT);
     LED1(OFF);
     LED2(OFF);
     // Инициализация температурного датчика
     sensors.begin();
     //Serial connection to meter.
     Serial2.begin(9600);
     for (int i=0; i<6; i++)   // записываем пароль
     {
       init_chanel[i+3] = (byte)password[i] - 48;
     }
     Serial.begin(9600);
     Serial.println("GSM Shield testing.");
     //Start configuration of shield with baudrate.
     if (gsm.begin(9600)) 
     {
       Serial.println("\nstatus=READY");
       started=true;
     } 
     else 
       Serial.println("\nstatus=IDLE");

     if(started) 
     {
       LED2(ON);     
     }
     LED1(OFF);  // LED1 включается в GSM::begin при включении GSM-модуля через GSM_ON

     sensors.requestTemperatures(); // читаем значение по умлочанию - 85С
};

void loop()
{
  if(started) 
  {
    char position = sms.IsSMSPresent(SMS_UNREAD);
    if (position)
    {
      LED1(ON);
      //get 1st sms
      sms.GetSMS(position,n,20,smsbuffer,160);
      Serial.println(n);
      Serial.println(smsbuffer);
      sms.DeleteSMS(1);
      initSMS();
      while (Serial2.available()) {Serial2.read();} // clear Serial2 input buffer
      sendcom(test_chanel, sizeof(test_chanel));
      int i1 = sendcom(init_chanel, sizeof(init_chanel));
      if (i1 > 0)
      {
        arr2serial(answ, i1);
        // общее потребление
        delay(100);
        i1 = sendcom(opros1, sizeof(opros1));
        if (i1 > 0)
        {
          arr2serial(answ, i1);
          unsigned long tmp = 0;
          char temp[12];
          tmp = (unsigned long)answ[2]<<24 | (unsigned long)answ[1]<<16 | (unsigned long)answ[4]<<8 | (unsigned long)answ[3];
          dtostrf((float)tmp / 1000, 9, 2, temp);
          strncpy(&mySMS[7], temp, strlen(temp));
          Serial.println(temp);
          // напряжение
          delay(100);
          i1 = sendcom(opros2, sizeof(opros2));
          if (i1 > 0)
          {
            arr2serial(answ, i1);
            dtostrf((float)(answ[3]<<8 | answ[2]) / 100, 3, 0, temp);
            strncpy(&mySMS[21], temp, 3);
            Serial.print(temp);

            dtostrf((float)(answ[6]<<8 | answ[5])/100, 3, 0, temp);
            strncpy(&mySMS[25], temp, strlen(temp));
            Serial.print("  ");
            Serial.print(temp);

            dtostrf((float)(answ[9]<<8 | answ[8]) / 100, 3, 0, temp);
            strncpy(&mySMS[29], temp, strlen(temp));
            Serial.print("  ");
            Serial.println(temp);
          }
          // ток
          delay(100);
          i1 = sendcom(opros3, sizeof(opros3));
          if (i1 > 0)
          {
            arr2serial(answ, i1);
            dtostrf((float)(answ[3]<<8 | answ[2]) / 1000, 3, 0, temp);
            strncpy(&mySMS[36], temp, 3);
            Serial.print(temp);

            dtostrf((float)(answ[6]<<8 | answ[5])/1000, 3, 0, temp);
            strncpy(&mySMS[40], temp, strlen(temp));
            Serial.print("  ");
            Serial.print(temp);

            dtostrf((float)(answ[9]<<8 | answ[8]) / 1000, 3, 0, temp);
            strncpy(&mySMS[44], temp, strlen(temp));
            Serial.print("  ");
            Serial.println(temp);
          }
          // мощность
          delay(100);
          i1 = sendcom(opros4, sizeof(opros4));
          if (i1 > 0)
          {
            arr2serial(answ, i1);
            dtostrf((float)(answ[1]<<16 | answ[3]<<8 | answ[2]) / 100, 3, 0, temp);
            strncpy(&mySMS[51], temp, 3);
            Serial.print(temp);

            dtostrf((float)(answ[4]<<16 | answ[6]<<8 | answ[5])/100, 3, 0, temp);
            strncpy(&mySMS[55], temp, strlen(temp));
            Serial.print("  ");
            Serial.print(temp);

            dtostrf((float)(answ[7]<<16 | answ[9]<<8 | answ[8]) / 100, 3, 0, temp);
            strncpy(&mySMS[59], temp, strlen(temp));
            Serial.print("  ");
            Serial.println(temp);
          }
          // температура
          sensors.requestTemperatures();
          delay(1000);
          dtostrf(sensors.getTempCByIndex(0), 5, 1, temp);
          arr2serial(temp, 5);
          strncpy(&mySMS[66], temp, 5);
        }
        else
        {
          mySMS[0] = mess[abs(i1)];
        }
        delay(100);
        i1 = sendcom(close_chanel, sizeof(close_chanel));
      }
      else
      {
        strcpy(mySMS, mess[abs(i1)]);
      }
      sms.SendSMS(n,mySMS);
      LED1(OFF);
    }
    delay(2000);
  }
};

Собирал в таком корпусе, поэтому край платы пришлось сточить напильником - иначе не помещалась.

Платы приклеивал термопистолетом.

Запитывал трансформаторным БП: Раздвоил "хвост" и отдельно подключал модем и Мегу.

Если у счетчика интерфейс CAN, то нужно брать модуль CAN <-> TTL, если RS-485, то соответственно. Тип интерфейса обозначен соответствующей буквой в названии счетчика.

В текущем состоянии работает следующим образом: при получении любой SMS, в ответ пасылает, текущие показания (общие), ток, напряжение и мощность пофазно. Плюс температуру.

Библеотеку для GSM-модуля залил в файлообменник:

https://transfiles.ru/i5z7y

К сожалению файл будет храниться только две недели, но чтобы все заработало без танцев, важно использовать именно эту библиотеку, так как я вносил изменения в оригинал (были проблемы с инициализацией GSM-модуля).

Ставил эту поделку на опрос счетчика. Примерно месяц проработала без каких либо замечаний и сбоев.

bwn
Offline
Зарегистрирован: 25.08.2014

А кто мешает запостить ее сюда? А то две недели, файлообменник...... Странный какой то проект.

shura28
Offline
Зарегистрирован: 11.02.2014

Не могу понять как загрузить файл (архив с кучей фалов внутри) отличный от картинки. Есть такая возможность ?

bwn
Offline
Зарегистрирован: 25.08.2014

Архивы нет. Только код и картинки.

MaksVV
Offline
Зарегистрирован: 06.08.2015

любой облачный диск вам в помощь

Oleg333
Offline
Зарегистрирован: 18.12.2018

Очень полезная и нужная вещь. У меня счетчик на даче в труднодоступном месте и давненько подумывал о чем нибудь подобном.

У меня вопросы:

 -Будет ли ваша библиотека работать с модулем GSM A6? Какие изменения в бибилиотеке?

- "текущие показания (общие)"  - это значит нет разделения на дневной и ночной тариф?

- Восьмой пин меги подключен на пин включения sim900. В скетче я этой строчки не увидел.

shura28
Offline
Зарегистрирован: 11.02.2014

В библеотеке исправлял номера пинов для работы через HWSerial (это предусмотрено самой либой), плюс были проблемы инициализации GSM-модуля при старте (даже как-то пытался искать помощи здесь на форуме) - пришлось править какую-то процедуру (уже не помню). Сама библиотека умеет работать с RESET и POWER GSM-модуля, поэтому в скетче и нет их упоминания. Так же добавил моргание светодиодом. Если GSM-модуль удачно старотонул, то загорается один из LED-ов. Если отсортировать файлы библиотеки по дате, то легко увидеть измененные файлы (вроде всего два).

Что касается использования других модулей, то к сожалению не подскажу. У самого были проблемы с выбором библиотеки под этот модуль - перебрал несколько.

Разделения на тарифы нет, то документация на счетчик есть в инете и описывает большое количество параметров (почти все) и как их вытащить. Поэтому, при желании, добавить труда не составит.

Единственное, что вызвало трудности при опросе счетчика, так это догадаться поставить паузу (delay) при получении данных от счетчика. Нужно обязательно иметь ввиду, что счетчик работает медленно и поэтому данные нужно считывать посимвольно с небольшой задержкой между чтением из порта. Этот нюанс уничтожил наибольшее количество нервных клеток :)