Pull to refresh
-1
0
Сергей @viras777

Начальник ИТ

Send message
Тензор, наконец-то :)
Подскажите, вы планируете перейти в 2-ке на PostgreSQL с Pervasive? Очень расстраивает вопрос с его лицензированием почти под лям и экспортом через выгрузки.
И ещё пожелание, внедрите RabbitMQ. В СБиС достаточно много отчётов с продолжительным временем выполнения. С очередями так бы красиво было :)
Вот такие строки в /etc/crontab:
#minute hour mday month wday who command
*/5 * * * * <имя пользователя> php /usr/local/<путь>/electric_accounting_counters.php db_dir=<имя БД>

Как когда-то давно говорил мой научный руководитель, можно знать один язык программирования, два, три, а потом вы уже знаете все. Утрированно конечно, но близко к истине. Если серьёзно, то не видим причин плодить сущности, когда php устраивает.
На FreeBSD в cron'е выполняем приведённый выше php'ник каждые 5 минут. Чтобы не было дважды запущенных периодических задач, создаём лок-файл.
Код php использует официальный протокол Меркуриев и идеи от дядьки, код которого очень помог разобраться в протоколе. В моём примере пароль админа в счётчике заменён на стандартный. По умолчанию адрес счётчика — это последние 2 или 3 цифры в серийном номере. Каждая moxa и её порт опрашиваются параллельно. Запрашиваем по одному все счётчики, висящие на одном порту moxa.
Алгоритм (каждые 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);
?>


У нас есть своя система, в частности в ней одна из задач как раз энергоучёт. Закупать или ставить стороннее приложение не хотели по внутренним причинам, поэтому реализовали на php протокол опроса Меркуриев 230/234. В итоге около 60 счётчиков подключены группами по 7-10шт к преобразователям в Ethernet (Moxa) и на FreeBSD скрипт раз в 5 минут снимает актуальные показания или, если был пропущен какой-то часовой интервал, считывает их из памяти Меркурия. Если интересно, поделюсь кодом.
Вы не поверите, от 20 тыс тем, кто не хочет учиться, например, женщины 50+. Им поручается рутиная работа. До 45 тыс максимум, причём я регулярно спрашиваю сотрудников, что им не нравится, что хочется изменить, какие-то пожелания… Так вот зарплата как правило стоит где-то на 3-4м месте среди всех желаний. Самый популярный ответ почему люди не уходят от меня — это интересная работа, которая не надоедает :) Как вы понимаете в управлении заводом — очень много разнообразных направлений :) Второй по популярности — стабильность, у нас даже в кризисные годы ни разу не задерживали зарплату.
Как работодатель скажу, что у меня есть проекты по автоматизации промышленного производства. И для того чтобы написать «банальный» алгоритм по разузловке двигателя надо знать производство, техпроцесс, уметь читать документацию и наперёд думать за заказчика. И это помимо навыка выуживания информации по крохам из ответственных сотрудников предприятия… К чем это я… Если ко мне придёт кузнечик, то какой бы он продвинутый не был, я даже разговаривать с ним не стану, т.к. только на понимание производства уйдёт не один год.
Беру я людей у которых есть огонёк в глазах, здоровая инициатива и желание найти интересную работу НАДОЛГО. Знание языка программирования — это конечно здорово, фреймворка — вообще класс, написать грамотный sql запрос — отпад. Но этому можно и научиться за те же 2-3 месяца.
Знает соискатель супер-пупер новомодный фреймворк — пусть идёт в геймдев, пасьянс писать… У меня не будет смена парадигмы и инструментария каждые полгода…

Пы.Сы. Вышесказанное не отменяет моё шпыняние программеров на еженедельное изучение новых технологий и написание кратенького отчёта в абзац длинной и чём-то новом)))
2

Information

Rating
Does not participate
Location
Ярославль, Ярославская обл., Россия
Registered
Activity