Commit 5fd2fe54 authored by Pavel Vainerman's avatar Pavel Vainerman

backported to p8 as 2.6-alt12.M80P.13 (with rpmbph script)

parents 4d8d4326 4478c815
......@@ -97,6 +97,9 @@ DB: Сделать регулируемый буфер на INSERT-ы БД, чт
или всё-таки на boost?
MB: выводить количество регистров опроса в getInfo()
ИДЕИ
-----
- ведение статистики по типам сообщений в каждом объекте (и в SM). Чтобы увидеть где происходит потеря пакетов (если происходит).
......
......@@ -18,7 +18,7 @@
Name: libuniset2
Version: 2.6
Release: alt11.M80P.12
Release: alt12.M80P.13
Summary: UniSet - library for building distributed industrial control systems
License: LGPL
......@@ -510,9 +510,12 @@ rm -f %buildroot%_libdir/*.la
# history of current unpublished changes
%changelog
* Tue Mar 07 2017 Pavel Vainerman <pv@altlinux.ru> 2.6-alt11.M80P.12
* Sat Mar 25 2017 Pavel Vainerman <pv@altlinux.ru> 2.6-alt12.M80P.13
- backport to ALTLinux p8 (by rpmbph script)
* Mon Mar 20 2017 Pavel Vainerman <pv@altlinux.ru> 2.6-alt13
- LogicProcessor: add "A2D" element (analog to discrete)
* Mon Feb 27 2017 Pavel Vainerman <pv@altlinux.ru> 2.6-alt12
- up version
......
......@@ -74,13 +74,13 @@ namespace uniset
// -------------------------------------------------------------------------
void Element::setChildOut()
{
bool _myout = getOut();
long _myout = getOut();
for( auto && it : outs )
for( auto&& it: outs )
it.el->setIn(it.num, _myout);
}
// -------------------------------------------------------------------------
std::shared_ptr<Element> Element::find(const ElementID& id )
std::shared_ptr<Element> Element::find( const ElementID& id )
{
for( const auto& it : outs )
{
......@@ -93,9 +93,9 @@ namespace uniset
return nullptr;
}
// -------------------------------------------------------------------------
void Element::addInput(size_t num, bool state)
void Element::addInput(size_t num, long value )
{
for( auto& it : ins )
for( auto&& it : ins )
{
if( it.num == num )
{
......@@ -105,10 +105,10 @@ namespace uniset
}
}
ins.emplace_front(num, state);
ins.emplace_front(num, value);
}
// -------------------------------------------------------------------------
void Element::delInput(size_t num )
void Element::delInput( size_t num )
{
for( auto it = ins.begin(); it != ins.end(); ++it )
{
......
......@@ -54,14 +54,14 @@ namespace uniset
virtual ~Element() {};
/*!< функция вызываемая мастером для элементов, которым требуется
/*! функция вызываемая мастером для элементов, которым требуется
работа во времени.
По умолчанию ничего не делает.
*/
virtual void tick() {}
virtual void setIn( size_t num, bool state ) = 0;
virtual bool getOut() const = 0;
virtual void setIn( size_t num, long value ) = 0;
virtual long getOut() const = 0;
inline ElementID getId() const
{
......@@ -81,7 +81,7 @@ namespace uniset
return outs.size();
}
virtual void addInput( size_t num, bool state = false );
virtual void addInput( size_t num, long value = 0 );
virtual void delInput( size_t num );
inline size_t inCount() const
{
......@@ -120,10 +120,10 @@ namespace uniset
struct InputInfo
{
InputInfo(): num(0), state(false), type(unknown) {}
InputInfo(size_t n, bool s): num(n), state(s), type(unknown) {}
InputInfo(): num(0), value(0), type(unknown) {}
InputInfo(size_t n, long v): num(n), value(v), type(unknown) {}
size_t num;
bool state;
long value;
InputType type;
};
......@@ -142,13 +142,13 @@ namespace uniset
{
public:
TOR( ElementID id, size_t numbers = 0, bool st = false );
TOR( ElementID id, size_t numbers = 0, bool outstate = false );
virtual ~TOR();
virtual void setIn( size_t num, bool state ) override;
virtual bool getOut() const override
virtual void setIn( size_t num, long value ) override;
virtual long getOut() const override
{
return myout;
return (myout ? 1 : 0);
}
virtual std::string getType() const override
......@@ -172,7 +172,7 @@ namespace uniset
TAND(ElementID id, size_t numbers = 0, bool st = false );
virtual ~TAND();
virtual void setIn( size_t num, bool state ) override;
virtual void setIn( size_t num, long value ) override;
virtual std::string getType() const override
{
return "AND";
......@@ -194,19 +194,18 @@ namespace uniset
TNOT( ElementID id, bool out_default );
virtual ~TNOT();
virtual bool getOut() const override
virtual long getOut() const override
{
return myout;
return ( myout ? 1 : 0 );
}
/* num игнорируется, т.к. элемент с одним входом
*/
virtual void setIn( size_t num, bool state ) override ;
/*! num игнорируется, т.к. элемент с одним входом */
virtual void setIn( size_t num, long value ) override ;
virtual std::string getType() const override
{
return "NOT";
}
virtual void addInput( size_t num, bool state = false ) override {}
virtual void addInput( size_t num, long value = 0 ) override {}
virtual void delInput( size_t num ) override {}
protected:
......
......@@ -108,23 +108,19 @@ void LProcessor::build( const string& lfile )
if( sid == DefaultObjectId )
{
dcrit << "НЕ НАЙДЕН ИДЕНТИФИКАТОР ДАТЧИКА: " << it->name << endl;
continue;
ostringstream err;
err << "Unknown sensor ID for " << it->name;
dcrit << err.str() << endl;
throw SystemError(err.str());
}
EXTInfo ei;
ei.sid = sid;
ei.state = false;
ei.value = false;
ei.el = it->to;
ei.iotype = conf->getIOType(sid);
ei.numInput = it->numInput;
if( ei.iotype == UniversalIO::UnknownIOType )
{
dcrit << "Unkown iotype for sid=" << sid << "(" << it->name << ")" << endl;
continue;
}
extInputs.emplace_front( std::move(ei) );
}
......@@ -134,20 +130,16 @@ void LProcessor::build( const string& lfile )
if( sid == DefaultObjectId )
{
dcrit << "НЕ НАЙДЕН ИДЕНТИФИКАТОР ВЫХОДА: " << it->name << endl;
continue;
ostringstream err;
err << "Unknown sensor ID for " << it->name;
dcrit << err.str() << endl;
throw SystemError(err.str());
}
EXTOutInfo ei;
ei.sid = sid;
ei.el = it->from;
ei.iotype = conf->getIOType(sid);
if( ei.iotype == UniversalIO::UnknownIOType )
{
dcrit << "Unkown iotype for sid=" << sid << "(" << it->name << ")" << endl;
continue;
}
extOuts.emplace_front(std::move(ei));
}
......@@ -161,11 +153,11 @@ void LProcessor::build( const string& lfile )
*/
void LProcessor::getInputs()
{
for( auto& it : extInputs )
for( auto&& it : extInputs )
{
// try
// {
it.state = (bool)ui.getValue(it.sid);
it.value = ui.getValue(it.sid);
// }
}
}
......@@ -174,7 +166,7 @@ void LProcessor::processing()
{
// выcтавляем все внешние входы
for( const auto& it : extInputs )
it.el->setIn(it.numInput, it.state);
it.el->setIn(it.numInput, it.value);
// проходим по всем элементам
for_each( sch->begin(), sch->end(), [](Schema::ElementMap::value_type it)
......@@ -190,7 +182,7 @@ void LProcessor::setOuts()
{
try
{
ui.setValue(it.sid, it.el->getOut(), DefaultObjectId);
ui.setValue(it.sid, it.el->getOut());
}
catch( const uniset::Exception& ex )
{
......
......@@ -34,6 +34,11 @@
-# \b "ИЛИ" \b (OR)
-# \b "Задержка" \b (Delay)
-# \b "Отрицание" \b (NOT)
-# \b "Преобразование аналогового значения в логическое" \b (A2D)
Стоит отметить, что по мере развития, процесс стал поддерживать не только логические операции,
а работу с числовыми(аналоговыми) величинами. Например элемент "TA2D".
Но в названии оставлено "Logic".
\section sec_lpShema Конфигурирование
Конфигурирование процесса осуществляется при помощи xml-файла задающего
......@@ -153,18 +158,16 @@ namespace uniset
struct EXTInfo
{
uniset::ObjectId sid;
UniversalIO::IOType iotype;
bool state;
std::shared_ptr<Element> el;
int numInput = { -1};
uniset::ObjectId sid = { uniset::DefaultObjectId };
long value = { 0 };
std::shared_ptr<Element> el = { nullptr };
int numInput = { -1 };
};
struct EXTOutInfo
{
uniset::ObjectId sid;
UniversalIO::IOType iotype;
std::shared_ptr<Element> el;
uniset::ObjectId sid = { uniset::DefaultObjectId };
std::shared_ptr<Element> el = { nullptr };
};
typedef std::list<EXTInfo> EXTList;
......@@ -180,9 +183,7 @@ namespace uniset
timeout_t smReadyTimeout = { 30000 } ; /*!< время ожидания готовности SM, мсек */
std::string logname = { "" };
std::atomic_bool canceled = {false};
std::string fSchema = {""};
private:
......
......@@ -11,7 +11,7 @@ libUniSet2LProcessor_la_LIBADD = $(top_builddir)/lib/libUniSet2.la \
$(top_builddir)/extensions/lib/libUniSet2Extensions.la $(SIGC_LIBS)
libUniSet2LProcessor_la_CXXFLAGS = -I$(top_builddir)/extensions/include \
-I$(top_builddir)/extensions/SharedMemory $(SIGC_CFLAGS)
libUniSet2LProcessor_la_SOURCES = Element.cc TOR.cc TAND.cc TDelay.cc TNOT.cc \
libUniSet2LProcessor_la_SOURCES = Element.cc TOR.cc TAND.cc TDelay.cc TNOT.cc TA2D.cc \
Schema.cc SchemaXML.cc LProcessor.cc PassiveLProcessor.cc
bin_PROGRAMS = @PACKAGE@-logicproc @PACKAGE@-plogicproc
......
......@@ -132,7 +132,7 @@ void PassiveLProcessor::sensorInfo( const uniset::SensorMessage* sm )
for( auto& it : extInputs )
{
if( it.sid == sm->id )
it.state = sm->value ? true : false;
it.value = sm->value;
}
}
// -------------------------------------------------------------------------
......
......@@ -168,7 +168,7 @@ namespace uniset
}
// -------------------------------------------------------------------------
void Schema::setIn( Element::ElementID ID, int inNum, bool state )
void Schema::setIn(Element::ElementID ID, int inNum, long state )
{
auto it = emap.find(ID);
......@@ -176,7 +176,7 @@ namespace uniset
it->second->setIn(inNum, state);
}
// -------------------------------------------------------------------------
bool Schema::getOut( Element::ElementID ID )
long Schema::getOut( Element::ElementID ID )
{
auto it = emap.find(ID);
......
......@@ -39,8 +39,8 @@ namespace uniset
void unlink( Element::ElementID rootID, Element::ElementID childID );
void extlink( const std::string& name, Element::ElementID childID, int numIn );
void setIn( Element::ElementID ID, int inNum, bool state );
bool getOut( Element::ElementID ID );
void setIn( Element::ElementID ID, int inNum, long state );
long getOut( Element::ElementID ID );
struct INLink;
struct EXTLink;
......
......@@ -20,6 +20,7 @@
#include "Extensions.h"
#include "Schema.h"
#include "TDelay.h"
#include "TA2D.h"
// -----------------------------------------------------------------------------
namespace uniset
{
......@@ -87,6 +88,11 @@ namespace uniset
bool defout = xml.getIntProp(it, "default_out_state");
manage( make_shared<TNOT>(ID, defout) );
}
else if( type == "A2D" )
{
int filterValue = xml.getIntProp(it, "filterValue");
manage( make_shared<TA2D>(ID, filterValue) );
}
else
{
ostringstream msg;
......@@ -143,6 +149,7 @@ namespace uniset
}
dinfo << "SchemaXML: set Out: from=" << fID << " to=" << tID << endl;
outList.emplace_front(tID, el);
}
}
......
/*
* Copyright (c) 2015 Pavel Vainerman.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, version 2.1.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Lesser Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// -------------------------------------------------------------------------
#include <iostream>
#include "Extensions.h"
#include "TA2D.h"
// -----------------------------------------------------------------------------
namespace uniset
{
// -------------------------------------------------------------------------
using namespace std;
using namespace uniset::extensions;
// -------------------------------------------------------------------------
TA2D::TA2D(Element::ElementID id , long filterValue ):
Element(id),
myout(false),
fvalue(filterValue)
{
ins.emplace_front(1, myout);
}
TA2D::~TA2D()
{
}
// -------------------------------------------------------------------------
void TA2D::setIn( size_t num, long value )
{
// num игнорируем, т.к у нас всего один вход..
bool prev = myout;
myout = ( fvalue == value );
if( prev != myout )
Element::setChildOut();
}
// -------------------------------------------------------------------------
long TA2D::getOut() const
{
return ( myout ? 1 : 0 );
}
// -------------------------------------------------------------------------
void TA2D::setFilterValue( long value )
{
if( fvalue != value && myout )
myout = false;
fvalue = value;
}
// -------------------------------------------------------------------------
} // end of namespace uniset
/*
* Copyright (c) 2015 Pavel Vainerman.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, version 2.1.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Lesser Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//--------------------------------------------------------------------------
#ifndef TA2D_H_
#define TA2D_H_
// --------------------------------------------------------------------------
#include "Element.h"
// --------------------------------------------------------------------------
namespace uniset
{
// ---------------------------------------------------------------------------
// "A2D"(analog to discrete)
// Преобразование аналогового датчика в дискретный по заданному значению. (Value=XXX --> True).
// Может быть один вход и много выходов.
class TA2D:
public Element
{
public:
TA2D( Element::ElementID id, long filterValue=1 );
virtual ~TA2D();
/*! num игнорируется, т.к. элемент с одним входом */
virtual void setIn( size_t num, long value ) override;
virtual long getOut() const override;
virtual std::string getType() const override
{
return "A2D";
}
void setFilterValue( long value );
protected:
TA2D(): myout(false) {};
bool myout;
long fvalue = { 1 };
private:
};
// --------------------------------------------------------------------------
} // end of namespace uniset
// ---------------------------------------------------------------------------
#endif
// ---------------------------------------------------------------------------
......@@ -33,17 +33,17 @@ namespace uniset
{
}
// -------------------------------------------------------------------------
void TAND::setIn(size_t num, bool state )
void TAND::setIn( size_t num, long value )
{
// cout << this << ": input " << num << " set " << state << endl;
for( auto& it : ins )
for( auto&& it : ins )
{
if( it.num == num )
{
if( it.state == state )
if( it.value == value )
return; // вход не менялся можно вообще прервать проверку
it.state = state;
it.value = value;
break;
}
}
......@@ -53,9 +53,9 @@ namespace uniset
// проверяем изменился ли выход
// для тригера 'AND' проверка до первого 0
for( auto& it : ins )
for( auto&& it : ins )
{
if( !it.state )
if( !it.value )
{
myout = false;
brk = true;
......
......@@ -41,12 +41,12 @@ namespace uniset
{
}
// -------------------------------------------------------------------------
void TDelay::setIn( size_t num, bool state )
void TDelay::setIn( size_t num, long value )
{
bool prev = myout;
// сбрасываем сразу
if( !state )
if( !value )
{
pt.setTiming(0); // reset timer
myout = false;
......@@ -92,9 +92,9 @@ namespace uniset
}
}
// -------------------------------------------------------------------------
bool TDelay::getOut() const
long TDelay::getOut() const
{
return myout;
return (myout ? 1 : 0);
}
// -------------------------------------------------------------------------
void TDelay::setDelay( timeout_t timeMS )
......
......@@ -34,8 +34,8 @@ namespace uniset
virtual ~TDelay();
virtual void tick() override;
virtual void setIn( size_t num, bool state ) override;
virtual bool getOut() const override;
virtual void setIn( size_t num, long value ) override;
virtual long getOut() const override;
virtual std::string getType() const override
{
return "Delay";
......
......@@ -36,10 +36,10 @@ namespace uniset
{
}
// -------------------------------------------------------------------------
void TNOT::setIn( size_t num, bool state )
void TNOT::setIn( size_t num, long value )
{
bool prev = myout;
myout = !state;
myout = ( value ? false : true ); // отрицание.. !value
if( prev != myout )
Element::setChildOut();
......
......@@ -32,7 +32,7 @@ namespace uniset
if( num != 0 )
{
// создаём заданное количество входов
for( unsigned int i = 1; i <= num; i++ )
for( size_t i = 1; i <= num; i++ )
{
ins.emplace_front(i, st); // addInput(i,st);
......@@ -46,18 +46,18 @@ namespace uniset
{
}
// -------------------------------------------------------------------------
void TOR::setIn( size_t num, bool state )
void TOR::setIn( size_t num, long value )
{
// cout << getType() << "(" << myid << "): input " << num << " set " << state << endl;
for( auto& it : ins )
for( auto&& it : ins )
{
if( it.num == num )
{
if( it.state == state )
if( it.value == value )
return; // вход не менялся можно вообще прервать проверку
it.state = state;
it.value = value;
break;
}
}
......@@ -67,9 +67,9 @@ namespace uniset
// проверяем изменился ли выход
// для тригера 'OR' проверка до первой единицы
for( auto& it : ins )
for( auto&& it : ins )
{
if( it.state )
if( it.value )
{
myout = true;
brk = true;
......
......@@ -40,6 +40,8 @@
<item id="504" iotype="DI" name="In5_S" textname="In5"/>
<item id="505" iotype="DI" name="In6_S" textname="In6"/>
<item id="506" iotype="DI" name="Out1_S" textname="Out1"/>
<item id="507" iotype="DI" name="Out2_S" textname="Out2"/>
<item id="508" iotype="AI" name="AI_S" textname="AI_S"/>
</sensors>
<thresholds name="thresholds">
</thresholds>
......
......@@ -15,6 +15,12 @@
---- |---|TOR4|-----| | | |
2 ----| | | | | |
---- ---- -------
---
| |
AI_S --|A2D|-- Output_S
| |
---
</text-view>
<elements>
......@@ -24,6 +30,7 @@
<item id="4" type="OR" inCount="2"/>
<item id="5" type="OR" inCount="2"/>
<item id="6" type="Delay" inCount="1" delayMS="3000"/>
<item id="7" type="A2D" filterValue="6"/>
</elements>
<connections>
......@@ -39,6 +46,8 @@
<item type="int" from="4" to="5" toInput="2" />
<item type="int" from="5" to="6" toInput="1" />
<item type="out" from="6" to="Out1_S"/>
<item type="ext" from="AI_S" to="7" toInput="1" />
<item type="out" from="7" to="Out2_S" />
</connections>
</Schema>
......@@ -6,6 +6,7 @@
#include "UniSetTypes.h"
#include "Schema.h"
#include "TDelay.h"
#include "TA2D.h"
// -----------------------------------------------------------------------------
using namespace std;
using namespace uniset;
......@@ -140,6 +141,31 @@ TEST_CASE("Logic processor: elements", "[LogicProcessor][elements]")
e.setIn(1, true);
CHECK( e.getOut() );
}
SECTION( "TA2D" )
{
TA2D e("1",10);
REQUIRE_FALSE( e.getOut() );
e.setIn(1, 5);
REQUIRE_FALSE( e.getOut() );
e.setIn(1, 10);
REQUIRE( e.getOut() );
e.setIn(1, 9);
REQUIRE_FALSE( e.getOut() );
e.setFilterValue(5);
REQUIRE_FALSE( e.getOut() );
e.setIn(1, 5);
REQUIRE( e.getOut() );
e.setIn(1, 6);
REQUIRE_FALSE( e.getOut() );
}
}
// -----------------------------------------------------------------------------
TEST_CASE("Logic processor: schema", "[LogicProcessor][schema]")
......@@ -148,7 +174,7 @@ TEST_CASE("Logic processor: schema", "[LogicProcessor][schema]")
sch.read("schema.xml");
CHECK( !sch.empty() );
REQUIRE( sch.size() == 6 );
REQUIRE( sch.size() == 7 );
REQUIRE( sch.find("1") != nullptr );
REQUIRE( sch.find("2") != nullptr );
......@@ -156,6 +182,7 @@ TEST_CASE("Logic processor: schema", "[LogicProcessor][schema]")
REQUIRE( sch.find("4") != nullptr );
REQUIRE( sch.find("5") != nullptr );
REQUIRE( sch.find("6") != nullptr );
REQUIRE( sch.find("7") != nullptr );
REQUIRE( sch.findOut("TestMode_S") != nullptr );
......@@ -192,12 +219,16 @@ TEST_CASE("Logic processor: lp", "[LogicProcessor][logic]")
auto e6 = sch->find("6");
REQUIRE( e6 != nullptr );
auto e7 = sch->find("7");
REQUIRE( e7 != nullptr );
CHECK_FALSE( e1->getOut() );
CHECK_FALSE( e2->getOut() );
CHECK_FALSE( e3->getOut() );
CHECK_FALSE( e4->getOut() );
CHECK_FALSE( e5->getOut() );
CHECK_FALSE( e6->getOut() );
CHECK_FALSE( e7->getOut() );
// e1
ui->setValue(500, 1);
......@@ -249,5 +280,22 @@ TEST_CASE("Logic processor: lp", "[LogicProcessor][logic]")
CHECK_FALSE( e6->getOut() );
msleep(2100);
CHECK( e6->getOut() );
// e7 (Out2_S=507, AI_S=508)
CHECK_FALSE( e7->getOut() );
ui->setValue(508, 1);
msleep(1000);
REQUIRE_FALSE( e7->getOut() );
REQUIRE_FALSE( ui->getValue(507) );
ui->setValue(508, 6);
msleep(1000);
REQUIRE( e7->getOut() );
msleep(1000);
REQUIRE( ui->getValue(507) );
ui->setValue(508, 7);
msleep(1000);
REQUIRE_FALSE( e7->getOut() );
msleep(1000);
REQUIRE_FALSE( ui->getValue(507) );
}
// -----------------------------------------------------------------------------
......@@ -15,6 +15,12 @@
---- |---|TOR4|-----| | | |
2 ----| | | | | |
---- ---- -------
---
| |
AI_S --|A2D|-- Output_S
| |
---
</text-view>
<elements>
......@@ -23,7 +29,8 @@
<item id="3" type="AND" inCount="2"/>
<item id="4" type="OR" inCount="2"/>
<item id="5" type="OR" inCount="2"/>
<item id="6" type="Delay" inCount="1" delayMS="3000"/>
<item id="6" type="Delay" inCount="1" delayMS="300"/>
<item id="7" type="A2D" filterValue="6"/>
</elements>
<connections>
......@@ -39,6 +46,8 @@
<item type="int" from="4" to="5" toInput="2" />
<item type="int" from="5" to="6" toInput="1" />
<item type="out" from="6" to="TestMode_S"/>
<item type="ext" from="AI_S" to="7" toInput="1" />
<item type="out" from="7" to="Output_S" />
</connections>
</Schema>
......@@ -104,6 +104,8 @@ extensions/LogicProcessor/TDelay.cc
extensions/LogicProcessor/TDelay.h
extensions/LogicProcessor/TNOT.cc
extensions/LogicProcessor/TOR.cc
extensions/LogicProcessor/TA2D.h
extensions/LogicProcessor/TA2D.cc
extensions/ModbusMaster/tests/Makefile.am
extensions/ModbusMaster/tests/mbmaster-test-configure.xml
extensions/ModbusMaster/tests/MBTCPTestServer.cc
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment