Comments 37
Спасибо за полезную информацию! Счетчики массовые и так подключать и считывать данные с них правда легче
0
Руководство для тех, кто не вкуривает официальную вики проекта.
Но сам факт статьи радует.
Но сам факт статьи радует.
0
… либо можно купить RS-232 <-> LAN шлюз и использовать облачный сервис яЭнергетик. Он там чего-то стоит, но какие-то десятки рублей в месяц, кажется.
0
Могу попросить Романа, чтобы прочитал статью — может вышлет свои замечания :-)
0
Не плохо!
Тоже подумываю к своему счетчику подключится, только у меня Микрон, но протокол похож.
Тоже подумываю к своему счетчику подключится, только у меня Микрон, но протокол похож.
0
Статью, как писал модуль сбора данных для Fastwel IO, писать?
0
UFO just landed and posted this here
У нас есть своя система, в частности в ней одна из задач как раз энергоучёт. Закупать или ставить стороннее приложение не хотели по внутренним причинам, поэтому реализовали на php протокол опроса Меркуриев 230/234. В итоге около 60 счётчиков подключены группами по 7-10шт к преобразователям в Ethernet (Moxa) и на FreeBSD скрипт раз в 5 минут снимает актуальные показания или, если был пропущен какой-то часовой интервал, считывает их из памяти Меркурия. Если интересно, поделюсь кодом.
0
Код php использует официальный протокол Меркуриев и идеи от дядьки, код которого очень помог разобраться в протоколе. В моём примере пароль админа в счётчике заменён на стандартный. По умолчанию адрес счётчика — это последние 2 или 3 цифры в серийном номере. Каждая moxa и её порт опрашиваются параллельно. Запрашиваем по одному все счётчики, висящие на одном порту moxa.
Алгоритм (каждые 5 минут часа):
— Читаем серийный номер счётчика, для контроля;
— Читаем онлайн данные по фазам: силу тока, мощность, напряжение, косинус фи;
— Пишем в БД;
— Если первые 4 минуты любого часа дня, то считываем общее потребление за прошедший час;
— Если между 5-ю и 10-ю минутами 0-го часа, то проверяем все ли показания есть за предыдущий период в БД. Если что-то отсутствует, то считываем показания из памяти счётчика;
P.S. То, что данные читаются онлайн, не из памяти счётчиков и могут браться не точно с 0-минут до 0-минут следующего часа, а с дельтой в несколько секунд не является принципиальным моментом, т.к. эта ж дельта будет и в следующем часу.
Алгоритм (каждые 5 минут часа):
— Читаем серийный номер счётчика, для контроля;
— Читаем онлайн данные по фазам: силу тока, мощность, напряжение, косинус фи;
— Пишем в БД;
— Если первые 4 минуты любого часа дня, то считываем общее потребление за прошедший час;
— Если между 5-ю и 10-ю минутами 0-го часа, то проверяем все ли показания есть за предыдущий период в БД. Если что-то отсутствует, то считываем показания из памяти счётчика;
P.S. То, что данные читаются онлайн, не из памяти счётчиков и могут браться не точно с 0-минут до 0-минут следующего часа, а с дельтой в несколько секунд не является принципиальным моментом, т.к. эта ж дельта будет и в следующем часу.
Собственно сам код
#!/usr/local/bin/php
<?php
/**
* \file Считываем показания со счётчиков
*/
include_once realpath(dirname($_SERVER['SCRIPT_FILENAME'])).'/general.php';
include_once ABSOLUTE_CLASS.'db.php';
class eCounters {
// дескриптор открытого канала на моху
private $fp = 0;
// 0 - слать на email И ЗАПИСЫВАТЬ в базу. 1(потом пид) - отладка на экран и НЕ записывать в базу.
public $debug = 0;
public $db = true;
// Строка ошибки
public $errMsg = '';
// Параметры соединения
private $ip, $port, $dev;
// Параметры merc_gd
private $t3byte = 1;
private $t4byte = 2;
private $tSerial = 3;
private $tNormal = 4;
// Версия прошивки
private $ver;
// http://ab-log.ru/smart-house/mercury-230
function CRC_Modbus($val='') {
$Data=hex2bin($val);
$len=strlen($Data);
$Sum=0xFFFF;
$cou = 0;
while ($len--){
$Sum^= ord($Data[$cou]);
for ($shift_cnt=0; $shift_cnt<8; $shift_cnt++) {
if (($Sum&0x1)==1) $Sum=(($Sum>>1)^0xA001);
else $Sum>>=1;
}
$cou++;
}
$Sum=dechex($Sum);
$len=4-strlen($Sum);
while ($len--){
$Sum='0'.$Sum;
}
return $val.$Sum[2].$Sum[3].$Sum[0].$Sum[1];
}
// Добавляет 0 к началу числа до $lim длинны
function zeropad($num, $lim) {
return (strlen($num) >= $lim) ? $num : $this->zeropad('0'.$num, $lim);
}
// Переводим бинарные динные в текстовое 16-тиричное представление
function dd($data = '') {
$result = '';
$data2 = '';
for ( $j = 0; $j < count($data); $j++ ) {
$data2 = dechex(ord($data[$j]));
if ( strlen($data2) == 1 )
$result = '0'.$data2;
else
$result .= $data2;
}
return $result;
}
function _hexdec($hex_string) {
$result=hexdec($hex_string);
if ( $result <= 9 )
return '0'.$result;
else
return $result;
}
/*
* Отправляет на счётчик команду, читает и декодирует ответ
* $cmd - команда а отправку
* $resp_len - ожидаемая длинна ответа
* $factor - коэффициент, на который надо умножить результат, т.к. результат чтения из потока только целочисленный
* $total - тип ответа, в зависимости от этого отдаётся разное представление данных
* $recurse_in - сам себя вызывает в случае неудачной попытки работы с потоком
*/
function merc_gd($cmd, $resp_len, $factor = 1, $total = 0, $recurse_in = 0) {
if($this->debug >= 3)
echo $this->dev.': cmd:'.$cmd."\n";
if($total == 0)
$total = $this->t3byte;
// 3 попытки на выполнение команды
if($recurse_in)
$retry=0;
else
$retry=3;
do {
flush();
fwrite($this->fp, hex2bin($cmd));
// 6 попыток прочитать ответ
$read_retry=6;
$result='';
do {
$read_retry--;
if (false === ($result .= stream_get_contents($this->fp)))
$read_retry=0;
} while (strlen($result)!=$resp_len && $read_retry );
if ( !$read_retry || (bin2hex($result) != $this->CRC_Modbus(substr(bin2hex($result),0,strlen(bin2hex($result))-4)))) {
$this->close_connection();
if(!$retry--) {
// Почтой, что счётчик недоступен
if ($this->debug >= 1)
echo $this->dev.': Moxa error: Cant recive data from: '.$this->ip.':'.$this->port.' dev:'.$this->dev."\n";
else
$this->errMsg .= 'Cant recive data from: '.$this->ip.':'.$this->port.' dev:'.$this->dev."\r\n<br>";
return false;
}
if (false === $this->init_connection())
return false;
}
else
break;
} while (1);
$ret = array();
$start_byte = 1;
if ( $total == $this->t3byte )
{
// 3-х байтовый ответ по текущим показателям
for ( $i = 0; $i < 4; $i++ )
{
$mask=63; // 3Fh - надо убрать 2 старших бита, это направление энергии
$sign_mask=128; // 80h - направление течения
if ( (ord($result[$start_byte + $i * 3]) & $sign_mask) > 0 )
$sign=-1;
else
$sign=1;
if ( strlen($result) > $start_byte + 2 + $i * 3 )
$ret[$i] = hexdec($this->dd($result[$start_byte + $i * 3] & $mask).
$this->dd($result[$start_byte + $i * 3 + 2]).
$this->dd($result[$start_byte + $i * 3 + 1]))*$factor*$sign;
}
}
elseif ( $total == $this->t4byte )
// 4-х байтовый ответ
$ret[0] = hexdec($this->dd($result[$start_byte+1]).
$this->dd($result[$start_byte]).
$this->dd($result[$start_byte+3]).
$this->dd($result[$start_byte+2]))*$factor;
elseif ( $total == $this->tSerial )
// Тут запрос серийника
$ret[0] = $this->_hexdec($this->dd($result[$start_byte])).
$this->_hexdec($this->dd($result[$start_byte+1])).
$this->_hexdec($this->dd($result[$start_byte+2])).
$this->_hexdec($this->dd($result[$start_byte+3]));
elseif ( $total == $this->tNormal )
// Просто отдать строку
$ret[0] = bin2hex($result);
if($this->debug >= 3)
echo $this->dev.': ret:'.$ret[0]."\n";
return $ret;
}
function init_connection()
{
$retry=3;
do
{
$retry--;
sleep(1);
} while ( $retry && (false === ($this->fp = fsockopen($this->ip, $this->port, $errno, $errstr, 10))) );
if (! $retry)
{
//Попробуем перезапустить моху
if (false === file_get_contents('http://'.$this->ip.'/09Set.htm?Submit=Submit'))
{
// Почтой, что моха недоступна
if ($this->debug >= 1)
echo $this->dev.': Moxa unavailable: Can not connect to: '.$this->ip."\n";
else
$this->errMsg .= 'Moxa unavailable: Can not connect to: '.$this->ip."\r\n";
return false;
}
sleep(5);
$retry=3;
do
{
$retry--;
sleep(1);
} while ( $retry && (false === ($this->fp = fsockopen($this->ip, $this->port, $errno, $errstr, 10))) );
if (! $retry)
{
// Почтой, что моха недоступна
if ($this->debug >= 1)
{
echo $this->dev.': Moxa unavailable: Restart done, but can not connect to serial port to: '.$this->ip.':'.$this->port."\n";
echo $this->dev.": Moxa: $errstr ($errno)\n";
}
else
$this->errMsg .= 'Moxa unavailable: Restart done, but can not connect to serial port to: '.$this->ip.':'.$this->port."\r\nMoxa: $errstr ($errno)\r\n";
return false;
}
}
// около 5 мс стандартная длительность тайм-аута для скорости 9600 Бод
// но 30 милисекунд ещё не успевает, с запасом 50
stream_set_timeout($this->fp, 0, 500000);
// Инициализация соединения и передача пароля
// Последний аргумент - рекурсия = 1, чтобы был только 1 заход без рекурсии, иначе вызовет сам себя и...
if (false === ($init = $this->merc_gd($this->CRC_Modbus($this->dev.'0101010101010101'), 4, 1, $this->tNormal, 1)))
return false;
if ($this->debug >= 1)
echo $this->dev.': Init:'.$init[0]."\n";
// Проверим версию счётчика, если базовый № прошивки >= 9, то новые счётчики
if (false === ($ret = $this->merc_gd($this->CRC_Modbus($this->dev.'0803'), 6, 1, $this->tNormal, 1)))
return false;
if ($this->debug >= 1)
echo $this->dev.': Version:'.substr($ret[0], 2, 2).'.'.substr($ret[0], 4, 2).'.'.substr($ret[0], 6, 2)."\n";
$this->ver = substr($ret[0], 2, 2);
return true;
}
function close_connection()
{
if(!$this->fp)
return;
flush();
fwrite($this->fp, hex2bin($this->CRC_Modbus($this->dev.'02')));
stream_get_contents($this->fp);
sleep(1);
fclose($this->fp);
$this->fp=0;
}
// $only_total - 0 или не 0, т.е. только итоговое значение, без текущих
function get_from_nport_inner($only_total)
{
if (false === $this->init_connection())
return false;
// Серийный номер
if (false === ($serial = $this->merc_gd($this->CRC_Modbus($this->dev.'0800'), 10, 1, $this->tSerial)))
return false;
if ($this->debug >= 1)
echo $this->dev.': Serial:'.$serial[0]."\n";
if (!$only_total)
{
// Сила тока (А) по фазам
if (false === ($Ia = $this->merc_gd($this->CRC_Modbus($this->dev.'081621'), 12, 0.001)))
return false;
if ($this->debug >= 1)
echo $this->dev.": Ia: $Ia[0] + $Ia[1] + $Ia[2]\n";
// Мощность P (Вт) по фазам
$retry=3;
$done=0;
do {
if (false === ($Pv = $this->merc_gd($this->CRC_Modbus($this->dev.'081600'), 15, 0.01)))
return false;
if (round($Pv[0], 2) == round($Pv[1] + $Pv[2] + $Pv[3], 2))
{
$done=1;
$error = '';
}
else
{
if ( !$retry-- )
$done=1;
$error = "error, ".round($Pv[1] + $Pv[2] + $Pv[3], 2);
}
if ($this->debug >= 1)
echo $this->dev.": Pv: $Pv[0] = $Pv[1] + $Pv[2] + $Pv[3] $error\n";
} while( !$done );
// Напряжение U (В) по фазам
if (false === ($Uv = $this->merc_gd($this->CRC_Modbus($this->dev.'081611'), 12, 0.01)))
return false;
if ($this->debug >= 1)
echo $this->dev.": Uv: $Uv[0] + $Uv[1] + $Uv[2]\n";
// Коэффициент мощности (С) по фазам
$retry=3;
$done=0;
do {
if (false === ($Cos = $this->merc_gd($this->CRC_Modbus($this->dev.'081630'), 15, 0.001)))
return false;
if (round($Cos[0], 1) == round(($Cos[1] + $Cos[2] + $Cos[3])/3, 1))
{
$done=1;
$error = '';
}
else
{
if ( !$retry-- )
$done=1;
$error = "error, ".round(($Cos[1] + $Cos[2] + $Cos[3])/3, 1);
}
if ($this->debug >= 1)
echo $this->dev.": Cos: $Cos[0]= ($Cos[1] + $Cos[2] + $Cos[3])/3 $error\n";
} while( !$done );
for ( $j = 1; $j < 4; $j++ ) {
if ($Cos[$j] > 1)
$Cos[$j]=1;
}
if ($this->db)
{
$pgsql = new db('pgsql','');
$res=$pgsql->select('SELECT eid FROM electric_accounting_counters WHERE model_id=\''.$serial[0].'\'', 'num');
if($pgsql->error[0]!=00000){echo $pgsql->error[2];}
if (!isset($res[0][0]))
return false;
$eid=$res[0][0];
if($this->debug >= 1)
echo $this->dev.': eid='.$eid."\n";
$sql=<<<TEXT
INSERT INTO electric_accounting_piucos
VALUES (DEFAULT, $eid,
$Pv[1], $Pv[2], $Pv[3],
$Ia[0], $Ia[1], $Ia[2],
$Uv[0], $Uv[1], $Uv[2],
$Cos[0], $Cos[1], $Cos[2]);
TEXT;
$pgsql->set($sql);
}
}
$timea=date('Y-m-d H:00:00');
$timeb=date('Y-m-d H:i:s');
// Если первые 4 минуты часа
if (strtotime($timeb) - strtotime($timea) < 240 || $this->debug >= 1)
{
// Общее потребление
if (false === ($Tot = $this->merc_gd($this->CRC_Modbus($this->dev.'050000'), 19, 0.001, $this->t4byte)))
return false;
if ($this->debug >= 1)
echo $this->dev.": Total: $Tot[0]\n";
if (!isset($pgsql))
{
$pgsql = new db('pgsql','');
$res=$pgsql->select('SELECT eid FROM electric_accounting_counters WHERE model_id=\''.$serial[0].'\'', 'num');
if($pgsql->error[0]!=00000){echo $pgsql->error[2];}
if (!isset($res[0][0]))
return false;
$eid=$res[0][0];
if($this->debug >= 1)
echo $this->dev.': eid='.$eid."\n";
}
$res=$pgsql->select('SELECT total FROM electric_accounting_last_value WHERE eid='.$eid, 'num');
if (!isset($res[0][0])) {
if($this->debug >= 1)
echo $this->dev.': Last in DB=(none)'."\n";
if($this->debug >= 2)
echo $this->dev.": Insert first time 'last value'\n";
if ($this->db)
$pgsql->set('INSERT INTO electric_accounting_last_value VALUES ( DEFAULT, '.$eid.', '.round($Tot[0],3).' )');
}
else
{
if($this->debug >= 1)
echo $this->dev.': Last in DB='.$res[0][0]."\n";
if($this->debug >= 2) {
echo $this->dev.": Insert energy\n";
echo $this->dev.': Save energy to DB(pre) Tot:'.round($Tot[0],3).' last:'.round($res[0][0],3)."\n";
echo $this->dev.': Save energy to DB='.round(round($Tot[0],3)-round($res[0][0],3),3)."\n";
}
if ($this->db) {
$pgsql->set('UPDATE electric_accounting_last_value SET total='.round($Tot[0],3).' WHERE eid='.$eid);
$pgsql->set('INSERT INTO electric_accounting_energy VALUES ( DEFAULT, '.$eid.', '.round(round($Tot[0],3)-round($res[0][0],3),3).' )');
}
}
}
$timea=date('Y-m-d 00:05:00');
// $timea=date('Y-m-d H:05:00');
$timeb=date('Y-m-d H:i:s');
// Если между 5-ю минутами и 10-ю 0-го часа каждого дня
if ((strtotime($timeb) - strtotime($timea) < 240 && strtotime($timeb) > strtotime($timea)) || $this->debug >= 1)
{
// проверяем за последние 2 месяца
if (!isset($pgsql))
{
$pgsql = new db('pgsql','');
$res=$pgsql->select('SELECT eid FROM electric_accounting_counters WHERE model_id=\''.$serial[0].'\'', 'num');
if($pgsql->error[0]!=00000){echo $pgsql->error[2];}
if (!isset($res[0][0])) {
if ($this->debug >= 1) {
$this->close_connection();
return true;
}
else
return false;
}
$eid=$res[0][0];
if($this->debug >= 1)
echo $this->dev.': eid='.$eid."\n";
}
$timeb=date('Y-m-d');
// $timea=date('Y-m-d',strtotime('now - 2 month'));
// $timea=date('Y-m-d',strtotime('now - 3 week'));
$timea=date('Y-m-d',strtotime('now - 2 week'));
// $timea=date('Y-m-d',strtotime('now - 2 day'));
$sql=<<<SQL
WITH tt AS (
SELECT generate_series('{$timea}'::timestamp, '{$timeb}', '1 hour') AS dt)
SELECT * FROM tt
WHERE tt.dt NOT IN (
SELECT indication_date
FROM electric_accounting_energy
WHERE indication_date BETWEEN '{$timea}' AND '{$timeb}'
AND eid={$eid});
SQL;
$res=$pgsql->select($sql, 'num');
if (!isset($res[0][0])) {
$this->close_connection();
return true;
}
// получим последнюю записанную ячейку памяти
if (false === ($ret = $this->merc_gd($this->CRC_Modbus($this->dev.'0813'), 12, 1, $this->tNormal)))
return false;
// В разных версиях - разная адресация
if($this->ver >= 9) {
$addr = substr($ret[0], 3, 3).'0';
$h_addr = substr($ret[0], 2, 1);
}
else {
// адрес в памяти
$addr = substr($ret[0], 2, 4);
// байт состояния записи, нам нужен только 4-й бит (маской 10h) - это 17-й бит(старший) адреса памяти
$h_addr = hexdec(substr($ret[0], 6, 2)) & 16 / 16;
}
if($this->debug >= 3) {
echo $this->dev.': addr='.$addr."\n";
echo $this->dev.': h_addr='.$h_addr."\n";
}
// время и дата
$hh = substr($ret[0], 8, 2); $mm = substr($ret[0], 10, 2);
$dd = substr($ret[0], 12, 2); $mo = substr($ret[0], 14, 2); $yy = substr($ret[0], 16, 2);
$timeb = "$yy-$mo-$dd $hh:$mm:00";
if($this->debug >= 1)
echo $this->dev.': Last saved time:'.$timeb."\n";
// длительность периода интегрирования
$period = hexdec(substr($ret[0], 18, 2));
if($this->debug >= 1)
echo $this->dev.': Integrity period:'.$period."\n";
// прочитаем вариант исполнения счётчика
if (false === ($ret = $this->merc_gd($this->CRC_Modbus($this->dev.'0812'), 9, 1, $this->tNormal)))
return false;
// проверим есть ли память
if ((hexdec(substr($ret[0], 4, 2)) & 32) != 32 ) {
$this->close_connection();
return true;
}
// постоянная счётчика
switch (hexdec(substr($ret[0], 4, 2)) & 15) {
case 0:
$const = 5000;
break;
case 1:
$const = 25000;
break;
case 2:
$const = 1250;
break;
case 3:
$const = 500;
break;
case 4:
$const = 1000;
break;
case 4:
$const = 250;
break;
default:
if ($this->debug >= 1)
echo $this->dev.': Counter error: const error: '.$this->ip.':'.$this->port.' dev:'.$this->dev."\n";
else
$this->errMsg .= 'Counter error: const error: '.$this->ip.':'.$this->port.' dev:'.$this->dev."\r\n";
return true;
}
if($this->debug >= 1)
echo $this->dev.': Counter const:'.$const."\n";
// сколько памяти (нужен ли 17-й бит)
$ext_mem = hexdec(substr($ret[0], 8, 2)) & 128 / 128;
if($this->debug >= 2)
echo $this->dev.': ext_mem='.$ext_mem."\n";
// сколько памяти используется под запись мощности
// если установлен хотя бы 1 бит учёта любого вида технических потерь, то расширенная память не используется
if ($ext_mem == 1) {
if (false === ($ret = $this->merc_gd($this->CRC_Modbus($this->dev.'081e'), 5, 1, $this->tNormal)))
return false;
if($this->debug >= 2)
echo $this->dev.': check using ext_mem...'."\n";
if (hexdec(substr($ret[0], 2, 2)) > 0)
$ext_mem = 0;
if($this->debug >= 2)
echo $this->dev.': ext_mem='.$ext_mem."\n";
}
// Теперь в цикле пройдём по всем датам
$i = 0;
$db_date = $res[0][0];
if($this->debug >= 1)
echo $this->dev.': Reading memory...'."\n";
while (1) {
if($this->debug >= 1)
echo $this->dev.': Finding date:'.$db_date."\n";
// Найдём разницу во времени
$diff = strtotime($timeb)-strtotime($db_date);
if($this->debug >= 3)
echo $this->dev.': orig diff='.$diff."\n";
// Сколько периодов между ними
$diff = $diff / 60 / $period;
if($this->debug >= 3)
echo $this->dev.': priod diff='.$diff."\n";
// Теперь надо отступить ещё на час минус учтённый последний период,
// т.к. разница на конец часа(т.е. конец последнего периода)
$diff = $diff + 60 / $period - 1;
// Сколько ячеек памяти надо отступить
$diff *= 16;
if($this->debug >= 3) {
echo $this->dev.': cell diff='.$diff."\n";
echo $this->dev.': cell diff(hex)='.$this->zeropad(dechex($diff),4)."\n";
}
// Получим показания периодов часа
$j = 60 / $period;
$p = 0;
while( $j > 0 ) {
if ($ext_mem) {
$addr_a = hexdec($addr)+hexdec('10000')*$h_addr;
$addr_b = $addr_a - $diff;
if ($addr_b < 0 )
$addr_b = hexdec('20000')+$addr_b;
}
else {
$addr_a = hexdec($addr);
$addr_b = $addr_a - $diff;
if ($addr_b < 0 )
$addr_b = hexdec('10000')+$addr_b;
}
if($this->debug >= 3) {
echo $this->dev.': addr_a='.$this->zeropad(dechex($addr_a),4)."\n";
echo $this->dev.': addr_b='.$this->zeropad(dechex($addr_b),4)."\n";
}
if ($addr_b < hexdec('10000'))
$bit = '03';
else {
$bit = '83';
$addr_b -= hexdec('10000');
}
$addr_r = $this->zeropad(dechex($addr_b), 4);
if($this->debug >= 3)
echo $this->dev.': addr_r='.$addr_r."\n";
// Считываем показания
if (false === ($ret = $this->merc_gd($this->CRC_Modbus($this->dev.'06'.$bit.$addr_r.'0f'), 18, 1, $this->tNormal)))
return false;
// время и дата
$hh = substr($ret[0], 4, 2); $mm = substr($ret[0], 6, 2);
$dd = substr($ret[0], 8, 2); $mo = substr($ret[0], 10, 2); $yy = substr($ret[0], 12, 2);
$timea = "$yy-$mo-$dd $hh:$mm:00";
if($this->debug >= 1)
echo $this->dev.': Getting date:'.$timea."\n";
// проверим та ли дата в ячейке памяти
if (abs(strtotime($db_date)-strtotime($timea)) > 3600 ) {
if ($this->debug >= 1)
echo $this->dev.': Counter error: memory error, to long period ('.$db_date.'): '.$this->ip.':'.$this->port.' dev:'.$this->dev."\n";
else
$this->errMsg .= 'Counter error: memory error, to long period ('.$db_date.'): '.$this->ip.':'.$this->port.' dev:'.$this->dev."\r\n<br>";
$i++;
break;
}
// Показание за период
$p += hexdec(substr($ret[0], 18, 2).substr($ret[0], 16, 2)) * (60 / $period) / (2 * $const);
$j--;
$diff -= 16;
}
if($this->debug >= 1)
echo $this->dev.': Reading 1 cell done'."\n";
if ( $j > 0 ) {
$i++;
if (isset($res[$i][0])) {
$db_date = $res[$i][0];
continue;
}
else
break;
}
$p = $p / ( 60 / $period );
// Запишем в базу
if (isset($res[$i][0])) {
if ($res[$i][0] == $db_date) {
if ($this->debug >= 1)
echo $this->dev.': INSERT ( DEFAULT, '.$eid.', '.$p.', \''.$db_date.'\' )'."\n";
if ($this->db)
$pgsql->set('INSERT INTO electric_accounting_energy VALUES ( DEFAULT, '.$eid.', '.$p.', \''.$db_date.'\' )');
$i++;
if (isset($res[$i][0])) {
if (date('Y-m-d H:i:00',strtotime($db_date.' + 1 hour')) == $res[$i][0])
$db_date = $res[$i][0];
else
$db_date = date('Y-m-d H:i:00',strtotime($db_date.' + 1 hour'));
}
else
$db_date = date('Y-m-d H:i:00',strtotime($db_date.' + 1 hour'));
}
else {
if ($this->debug >= 1)
echo $this->dev.': UPDATE energy='.$p.' WHERE eid='.$eid.' AND indication_date=\''.$db_date.'\''."\n";
if ($this->db)
$pgsql->set('UPDATE electric_accounting_energy SET energy='.$p.' WHERE eid='.$eid.' AND indication_date=\''.$db_date.'\'');
$db_date = $res[$i][0];
}
}
else {
if ($this->debug >= 1)
echo $this->dev.': UPDATE energy='.$p.' WHERE eid='.$eid.' AND indication_date=\''.$db_date.'\' )'."\n";
if ($this->db)
$pgsql->set('UPDATE electric_accounting_energy SET energy='.$p.' WHERE eid='.$eid.' AND indication_date=\''.$db_date.'\'');
break;
}
}
}
// Закрытие соединения
$this->close_connection();
return true;
}
function get_from_nport_main($only_total=0)
{
$retry=3;
while ( $retry && (false === $this->get_from_nport_inner($only_total)) )
{
$retry--;
$this->close_connection();
sleep(5);
}
if (! $retry)
{
if ($this->debug >= 1)
echo $this->dev.': Moxa error: Communications error with: '.$this->ip.':'.$this->port."\n";
else
$this->errMsg .= 'Moxa error: Communications error with: '.$this->ip.':'.$this->port."\r\n\r\n";
}
}
function get_from_nport($ip, $port, $devs)
{
$this->ip = $ip;
$this->port = $port;
if(!isset($this->debug))
$this->debug = 0;
foreach($devs as $dev) {
$this->dev = $dev[0];
if(!isset($dev[1]))
$this->get_from_nport_main();
else
$this->get_from_nport_main($dev[1]);
}
}
}
function shutdown() {
// ob_end_clean();
posix_kill(getmypid(), SIGHUP);
}
// ---------------------------------------------------------------------
$cntr = new eCounters;
$cntr->debug = 0;
$executed = 0;
// Debug
//$cntr->debug = 2;
//$cntr->db = false;
//$cntr->get_from_nport('192.168.1.1', 4001, array(['25'],['31'],['3e'],['4e']));
//exit;
// ---------------------------------------------------------------------
// K трансф = K напряжения * K тока
//
// ---------------------------------------------------------------------
// Подстанция
//
// 1. 21(15h) Ф. Моторная K=60*400=24000 - 2-й транс.
// 2. 37(25h) Ф. 157 K=60*400=24000 - 1-й транс
$pid = pcntl_fork();
if($pid == -1)
// Fork not woking
$cntr->get_from_nport('192.168.1.1', 4002, array(['15'],['25']));
elseif($pid)
// Parent
$executed++;
else{
// Child
register_shutdown_function('shutdown');
$cntr->get_from_nport('192.168.1.1', 4002, array(['15'],['25']));
if ($cntr->errMsg != '')
mail(MAIL_FROM, 'Moxa error', $cntr->errMsg, 'From: moxa@eldin.ru' . "\r\n");
exit;
}
// ---------------------------------------------------------------------
if ($cntr->errMsg != '')
mail(MAIL_FROM, 'Moxa error', $cntr->errMsg, 'From: moxa@eldin.ru' . "\r\n");
while($executed) {
pcntl_wait($status);
$executed--;
}
unlink($lock_file);
?>
0
Единственный недостаток это то что нельзя использовать для коммерческого учета электроэнергии так как система не сертифицирована, ибо opensource и сертификация понятия взаимоисключающие. Кстати ни у кого нет протокола обмена для концентраторов меркурий 225.11 и 225.12?
0
как использовать php на клиентской стороне (формирование web-страниц пользователю) мне понятно, а как вы цикличность опрос+БД реализовали (при том что сбор данных это независимо выполняемая задача от просмотра этих данных)?
0
На FreeBSD в cron'е выполняем приведённый выше php'ник каждые 5 минут. Чтобы не было дважды запущенных периодических задач, создаём лок-файл.
0
как php встраивается в html и/или генерит html файл я понимаю… а как cron в данном случае запускает скрипт? и почему php тогда выбрали в этом случае?
0
Вот такие строки в /etc/crontab:
#minute hour mday month wday who command
*/5 * * * * <имя пользователя> php /usr/local/<путь>/electric_accounting_counters.php db_dir=<имя БД>
Как когда-то давно говорил мой научный руководитель, можно знать один язык программирования, два, три, а потом вы уже знаете все. Утрированно конечно, но близко к истине. Если серьёзно, то не видим причин плодить сущности, когда php устраивает.
#minute hour mday month wday who command
*/5 * * * * <имя пользователя> php /usr/local/<путь>/electric_accounting_counters.php db_dir=<имя БД>
Как когда-то давно говорил мой научный руководитель, можно знать один язык программирования, два, три, а потом вы уже знаете все. Утрированно конечно, но близко к истине. Если серьёзно, то не видим причин плодить сущности, когда php устраивает.
0
статью плюсануть не успел (срок голосования истек), плюсанул в карму…
статьи не пишутся в том числе потому. что это занимает немало времени (а тем более хорошие, полноЦЕННЫЕ, с картинками)… сами видите — написали через три года…
у меня есть два ынтерпрайз проекта, по которым можно статьи сделать, но времени нет…
так глядишь и в cpp кто-нибудь переведет ваш код…
насчет документации на сайте openSCADA — регистрируйтесь и дополняйте wiki своими примерами использования той или иной сущности (примеров использования там очень не хватает)… это же open source…
статьи не пишутся в том числе потому. что это занимает немало времени (а тем более хорошие, полноЦЕННЫЕ, с картинками)… сами видите — написали через три года…
у меня есть два ынтерпрайз проекта, по которым можно статьи сделать, но времени нет…
так глядишь и в cpp кто-нибудь переведет ваш код…
насчет документации на сайте openSCADA — регистрируйтесь и дополняйте wiki своими примерами использования той или иной сущности (примеров использования там очень не хватает)… это же open source…
0
хотя уже не столько примеры нужно сколько уже время патчей (некоторые вещи в QT гвоздями прибиты, например, поле ввода перечня атрибутов у параметра в узле Modbus — строка в 101 символ, а потомо перенос на строки и неважно что монитор 24" 1920х1200 и еще полно свободного места) и элементов (свой протокол для ширпотребной шалабушки, свои виджеты )… сколько лет уже разрабатывается продукт…
0
Патчи Роман прекрасно принимает. Так что правьте и предлагайте.
0
я про те, которые расходятся с идеологией и дизайнерским вкусом Романа… а так да, принимает… если…
0
Ну, в таком случае или терпеть, или искать другие пути. Например мне не нравится ни стиль, ни архитектура приложения. Но писать свое пока нет времени.
Ну и да, при создании модулей для рабочей ветки без внесения в общую кодовую базу нужно постоянно следить за каждым чихом. Внешне API может даже и не поменяться, но ваш модуль уже не будет работать должным образом.
Ну и да, при создании модулей для рабочей ветки без внесения в общую кодовую базу нужно постоянно следить за каждым чихом. Внешне API может даже и не поменяться, но ваш модуль уже не будет работать должным образом.
0
Возможно, будет кому-то будет интересна программка для сбора статистики и получения инфо под OpenWrt роутеры. Поставил больше десятка мини роутеров с этой програмкой и подключением по RS485 через USB переходник (~1$ на Алиэкспресс), статистика собирается удалённо для внутреннего учёта — полёт нормальный уже год.
0
Sign up to leave a comment.
Пишем протоколы счетчиков Меркурий 230 и Меркурий 200 для OpenSCADA