Commit 6cbb72b6 authored by Claes Sjofors's avatar Claes Sjofors

Sev deadband linear regression added

parent d9245c13
...@@ -517,8 +517,10 @@ int sev_server::mainloop() ...@@ -517,8 +517,10 @@ int sev_server::mainloop()
qcom_sEvent *ep = (qcom_sEvent*) get.data; qcom_sEvent *ep = (qcom_sEvent*) get.data;
new_event.m = ep->mask; new_event.m = ep->mask;
if (new_event.b.terminate) if (new_event.b.terminate) {
delete m_db;
exit(0); exit(0);
}
break; break;
} }
default: ; default: ;
......
...@@ -41,8 +41,10 @@ ...@@ -41,8 +41,10 @@
#include "pwr.h" #include "pwr.h"
#include "pwr_class.h" #include "pwr_class.h"
#include "pwr_baseclasses.h"
#include "rt_mh_net.h" #include "rt_mh_net.h"
#include "rt_sev_net.h" #include "rt_sev_net.h"
#include "sev_valuecache.h"
using namespace std; using namespace std;
...@@ -66,6 +68,25 @@ typedef struct { ...@@ -66,6 +68,25 @@ typedef struct {
unsigned int eventstore_msg_cnt; unsigned int eventstore_msg_cnt;
} sev_sStat; } sev_sStat;
typedef struct {
pwr_tTime time;
pwr_tFloat32 value;
int stored;
} sev_StoredFloat32;
typedef struct {
pwr_tTime time;
pwr_tInt32 value;
int stored;
} sev_StoredInt32;
typedef struct {
int size;
int first;
int last;
void *values;
} sev_sStoredValues;
class sev_attr { class sev_attr {
public: public:
sev_attr() : type(pwr_eType_), size(0), elem(0) { sev_attr() : type(pwr_eType_), size(0), elem(0) {
...@@ -94,18 +115,25 @@ class sev_event { ...@@ -94,18 +115,25 @@ class sev_event {
class sev_item { class sev_item {
public: public:
sev_item() : deadband_active(0), last_id(0), value_size(0), old_value(0), first_storage(1), status(0), logged_status(0), sev_item() : deadband_active(0), last_id(0), value_size(0), old_value(0), first_storage(1), status(0), logged_status(0),
idx(0), deleted(0) cache(0), idx(0), deleted(0) {
{ /*memset( old_value, 0, sizeof(old_value));*/} /*memset( old_value, 0, sizeof(old_value));*/
sev_item( const sev_item& x) : id(x.id), oid(x.oid), creatime(x.creatime), modtime(x.modtime), }
storagetime(x.storagetime), sevid(x.sevid), scantime(x.scantime), deadband(x.deadband), options(x.options), sev_item( const sev_item& x) : id(x.id), oid(x.oid), creatime(x.creatime), modtime(x.modtime),
storagetime(x.storagetime), sevid(x.sevid), scantime(x.scantime), deadband(x.deadband), options(x.options),
deadband_active(x.deadband_active), last_id(x.last_id), value_size(x.value_size), old_value(x.old_value), deadband_active(x.deadband_active), last_id(x.last_id), value_size(x.value_size), old_value(x.old_value),
first_storage(x.first_storage), first_storage(x.first_storage),
attrnum(x.attrnum), attr(x.attr), status(x.status), logged_status(x.logged_status), attrnum(x.attrnum), attr(x.attr), status(x.status), logged_status(x.logged_status), cache(0),
idx(x.idx), deleted(x.deleted) { idx(x.idx), deleted(x.deleted) {
strncpy( tablename, x.tablename, sizeof(tablename)); strncpy( tablename, x.tablename, sizeof(tablename));
strncpy( oname, x.oname, sizeof(oname)); strncpy( oname, x.oname, sizeof(oname));
strncpy( description, x.description, sizeof(description)); strncpy( description, x.description, sizeof(description));
} if ( x.cache)
cache = new sev_valuecache(*x.cache);
}
~sev_item() {
if ( cache)
delete cache;
}
unsigned int id; unsigned int id;
char tablename[256]; char tablename[256];
pwr_tOid oid; pwr_tOid oid;
...@@ -128,6 +156,7 @@ class sev_item { ...@@ -128,6 +156,7 @@ class sev_item {
vector<sev_attr> attr; vector<sev_attr> attr;
pwr_tStatus status; pwr_tStatus status;
pwr_tStatus logged_status; pwr_tStatus logged_status;
sev_valuecache *cache;
unsigned int idx; unsigned int idx;
int deleted; int deleted;
}; };
......
...@@ -736,6 +736,8 @@ int sev_dbms::create_table( pwr_tStatus *sts, char *tablename, pwr_eType type, ...@@ -736,6 +736,8 @@ int sev_dbms::create_table( pwr_tStatus *sts, char *tablename, pwr_eType type,
if ( cnf_get_value( "sevMysqlEngine", engine, sizeof(engine)) != 0) if ( cnf_get_value( "sevMysqlEngine", engine, sizeof(engine)) != 0)
snprintf( enginestr, sizeof(enginestr), " engine=%s", engine); snprintf( enginestr, sizeof(enginestr), " engine=%s", engine);
if ( cdh_NoCaseStrcmp( engine, "innodb") == 0)
strcat( enginestr, " row_format=compressed");
if ( options & pwr_mSevOptionsMask_PosixTime) { if ( options & pwr_mSevOptionsMask_PosixTime) {
if ( options & pwr_mSevOptionsMask_HighTimeResolution) { if ( options & pwr_mSevOptionsMask_HighTimeResolution) {
...@@ -993,10 +995,12 @@ int sev_dbms::get_items( pwr_tStatus *sts) ...@@ -993,10 +995,12 @@ int sev_dbms::get_items( pwr_tStatus *sts)
item.scantime = atof(row[13]); item.scantime = atof(row[13]);
item.deadband = atof(row[14]); item.deadband = atof(row[14]);
item.options = strtoul(row[15], 0, 10); item.options = strtoul(row[15], 0, 10);
item.attrnum = 1; item.attrnum = 1;
m_items.push_back( item); m_items.push_back( item);
if ( item.options & pwr_mSevOptionsMask_DeadBandLinearRegr)
add_cache( m_items.size()-1);
} }
mysql_free_result( result); mysql_free_result( result);
...@@ -1009,6 +1013,32 @@ int sev_dbms::get_items( pwr_tStatus *sts) ...@@ -1009,6 +1013,32 @@ int sev_dbms::get_items( pwr_tStatus *sts)
int sev_dbms::store_value( pwr_tStatus *sts, int item_idx, int attr_idx, int sev_dbms::store_value( pwr_tStatus *sts, int item_idx, int attr_idx,
pwr_tTime time, void *buf, unsigned int size) pwr_tTime time, void *buf, unsigned int size)
{
if ( m_items[item_idx].options & pwr_mSevOptionsMask_DeadBandLinearRegr) {
double value;
switch ( m_items[item_idx].attr[0].type) {
case pwr_eType_Float32:
value = *(pwr_tFloat32 *)buf;
break;
case pwr_eType_Float64:
value = *(pwr_tFloat64 *)buf;
break;
case pwr_eType_Int32:
value = *(pwr_tInt32 *)buf;
break;
default:
return 0;
}
m_items[item_idx].cache->add( value, &time);
m_items[item_idx].cache->evaluate();
return 1;
}
else
return write_value( sts, item_idx, attr_idx, time, buf, size);
}
int sev_dbms::write_value( pwr_tStatus *sts, int item_idx, int attr_idx,
pwr_tTime time, void *buf, unsigned int size)
{ {
if(size != m_items[item_idx].value_size) { if(size != m_items[item_idx].value_size) {
//Something is seriously wrong //Something is seriously wrong
...@@ -2083,6 +2113,9 @@ int sev_dbms::add_item( pwr_tStatus *sts, pwr_tOid oid, char *oname, char *aname ...@@ -2083,6 +2113,9 @@ int sev_dbms::add_item( pwr_tStatus *sts, pwr_tOid oid, char *oname, char *aname
m_items.push_back( item); m_items.push_back( item);
*idx = m_items.size() - 1; *idx = m_items.size() - 1;
if ( item.options & pwr_mSevOptionsMask_DeadBandLinearRegr)
add_cache( *idx);
*sts = SEV__SUCCESS; *sts = SEV__SUCCESS;
return 1; return 1;
} }
...@@ -3883,6 +3916,42 @@ int sev_dbms::store_stat( sev_sStat *stat) ...@@ -3883,6 +3916,42 @@ int sev_dbms::store_stat( sev_sStat *stat)
return 1; return 1;
} }
void sev_dbms::write_db_cb( void *data, int idx, double value, pwr_tTime *time)
{
pwr_tStatus sts;
sev_dbms *dbms = (sev_dbms *)data;
switch( dbms->m_items[idx].attr[0].type) {
case pwr_eType_Float32: {
pwr_tFloat32 v = value;
dbms->write_value( &sts, idx, 0, *time, &v, sizeof(v));
break;
}
case pwr_eType_Float64: {
pwr_tFloat64 v = value;
dbms->write_value( &sts, idx, 0, *time, &v, sizeof(v));
break;
}
case pwr_eType_Int32: {
pwr_tInt32 v = value;
dbms->write_value( &sts, idx, 0, *time, &v, sizeof(v));
break;
}
default: ;
}
}
void sev_dbms::add_cache( int item_idx)
{
if ( m_items[item_idx].options & pwr_mSevOptionsMask_DeadBandMeanValue)
m_items[item_idx].cache = new sev_valuecache( sev_eCvType_Mean, m_items[item_idx].deadband,
m_items[item_idx].scantime);
else
m_items[item_idx].cache = new sev_valuecache( sev_eCvType_Point, m_items[item_idx].deadband,
m_items[item_idx].scantime);
m_items[item_idx].cache->set_write_cb( write_db_cb, this, item_idx);
}
int sev_dbms::begin_transaction() int sev_dbms::begin_transaction()
{ {
char query[20]; char query[20];
...@@ -3917,6 +3986,9 @@ sev_dbms::~sev_dbms() ...@@ -3917,6 +3986,9 @@ sev_dbms::~sev_dbms()
{ {
printf("Freeing memory\n"); printf("Freeing memory\n");
for(size_t idx = 0; idx < m_items.size(); idx++) { for(size_t idx = 0; idx < m_items.size(); idx++) {
if ( m_items[idx].cache)
// Write last value
m_items[idx].cache->write(0);
if( m_items[idx].old_value != 0 ) { if( m_items[idx].old_value != 0 ) {
free(m_items[idx].old_value); free(m_items[idx].old_value);
m_items[idx].old_value = 0; m_items[idx].old_value = 0;
......
...@@ -139,6 +139,8 @@ class sev_dbms : public sev_db { ...@@ -139,6 +139,8 @@ class sev_dbms : public sev_db {
pwr_tFloat32 deadband, pwr_tMask options, unsigned int *idx); pwr_tFloat32 deadband, pwr_tMask options, unsigned int *idx);
int store_value( pwr_tStatus *sts, int item_idx, int attr_idx, int store_value( pwr_tStatus *sts, int item_idx, int attr_idx,
pwr_tTime time, void *buf, unsigned int size); pwr_tTime time, void *buf, unsigned int size);
int write_value( pwr_tStatus *sts, int item_idx, int attr_idx,
pwr_tTime time, void *buf, unsigned int size);
int get_values( pwr_tStatus *sts, pwr_tOid oid, pwr_tMask options, float deadband, char *aname, int get_values( pwr_tStatus *sts, pwr_tOid oid, pwr_tMask options, float deadband, char *aname,
pwr_eType type, unsigned int size, pwr_tFloat32 scantime, pwr_tTime *creatime, pwr_eType type, unsigned int size, pwr_tFloat32 scantime, pwr_tTime *creatime,
pwr_tTime *starttime, pwr_tTime *starttime,
...@@ -161,6 +163,7 @@ class sev_dbms : public sev_db { ...@@ -161,6 +163,7 @@ class sev_dbms : public sev_db {
char *dbName() { return sev_dbms_env::dbName();} char *dbName() { return sev_dbms_env::dbName();}
char *pwrtype_to_type( pwr_eType type, unsigned int size); char *pwrtype_to_type( pwr_eType type, unsigned int size);
static int timestr_to_time( char *tstr, pwr_tTime *ts); static int timestr_to_time( char *tstr, pwr_tTime *ts);
static void write_db_cb( void *data, int idx, double value, pwr_tTime *time);
int check_objectitem( pwr_tStatus *sts, char *tablename, pwr_tOid oid, char *oname, char *aname, int check_objectitem( pwr_tStatus *sts, char *tablename, pwr_tOid oid, char *oname, char *aname,
pwr_tDeltaTime storagetime, pwr_tDeltaTime storagetime,
char *description, pwr_tFloat32 scantime, char *description, pwr_tFloat32 scantime,
...@@ -200,6 +203,7 @@ class sev_dbms : public sev_db { ...@@ -200,6 +203,7 @@ class sev_dbms : public sev_db {
int alter_engine( pwr_tStatus *sts, char *tablename); int alter_engine( pwr_tStatus *sts, char *tablename);
int optimize( pwr_tStatus *sts, char *tablename); int optimize( pwr_tStatus *sts, char *tablename);
int store_stat( sev_sStat *stat); int store_stat( sev_sStat *stat);
void add_cache( int item_idx);
int begin_transaction(); int begin_transaction();
int commit_transaction(); int commit_transaction();
inline char* create_colName(unsigned int index, char *attributename) { inline char* create_colName(unsigned int index, char *attributename) {
......
/*
* Proview Open Source Process Control.
* Copyright (C) 2005-2016 SSAB EMEA AB.
*
* This file is part of Proview.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 2 of
* the License, or (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Proview. If not, see <http://www.gnu.org/licenses/>
*
* Linking Proview statically or dynamically with other modules is
* making a combined work based on Proview. Thus, the terms and
* conditions of the GNU General Public License cover the whole
* combination.
*
* In addition, as a special exception, the copyright holders of
* Proview give you permission to, from the build function in the
* Proview Configurator, combine Proview with modules generated by the
* Proview PLC Editor to a PLC program, regardless of the license
* terms of these modules. You may copy and distribute the resulting
* combined work under the terms of your choice, provided that every
* copy of the combined work is accompanied by a complete copy of
* the source code of Proview (the version used to produce the
* combined work), being distributed under the terms of the GNU
* General Public License plus this exception.
**/
#include <stdio.h>
#include <string.h>
#include <float.h>
#include <math.h>
#include "pwr.h"
#include "co_time.h"
#include "rt_gdh.h"
#include "pwr_baseclasses.h"
#include "sev_valuecache.h"
const int sev_valuecache::m_size = VALUECACHE_SIZE;
int sev_valuecache::idx( int index)
{
if ( !m_length)
return 0;
if ( index >= m_length)
return 0;
int i = m_last - index;
if ( i < 0)
i += m_size;
return i;
}
sev_sCacheValue& sev_valuecache::operator[]( const int index)
{
return m_val[idx(index)];
}
void sev_valuecache::add( double val, pwr_tTime *t)
{
double time;
pwr_tDeltaTime dt;
time_Adiff_NE( &dt, t, &m_start_time);
time = time_DToFloat64( 0, &dt);
// Store optimized write index before adding
m_last_opt_write = get_optimal_write();
bool update_k = m_length < m_size;
if ( !m_length) {
m_val[0].val = val;
m_val[0].time = time;
m_length++;
}
else {
if ( ++m_last >= m_size)
m_last -= m_size;
m_val[m_last].val = val;
m_val[m_last].time = time;
m_length++;
if ( m_last == m_first) {
m_first++;
if ( m_first >= m_size)
m_first -= m_size;
m_length--;
}
}
if ( !m_inited) {
write( 0);
m_inited = true;
return;
}
if ( update_k) {
calculate_k();
// Update epsilon for all data
calculate_epsilon();
}
else
calculate_epsilon(0);
}
void sev_valuecache::evaluate()
{
int value_added = 1;
while( 1) {
if ( !check_deadband()) {
// Store optimal value
write( m_last_opt_write + value_added);
}
else
break;
calculate_k();
calculate_epsilon();
m_last_opt_write = get_optimal_write();
value_added = 0;
}
}
void sev_valuecache::calculate_k()
{
double xysum = 0;
double x2sum = 0;
for ( int i = 0; i < length(); i++) {
int ii = idx(i);
xysum += (m_val[ii].val - m_wval.val) * (m_val[ii].time - m_wval.time);
x2sum += (m_val[ii].val - m_wval.val) * (m_val[ii].val - m_wval.val);
}
if ( x2sum < DBL_EPSILON) {
m_k = 1E32;
m_m = m_wval.time;
m_deadband = m_deadband_time;
}
else {
m_k = x2sum/xysum;
m_m = m_wval.val - m_wval.time * m_k;
m_deadband = m_deadband_value +
fabs(atan(m_k)) / (M_PI/2) * ( m_deadband_time - m_deadband_value);
}
}
void sev_valuecache::write( int index)
{
int ii = idx(index);
double wval, wtime;
if ( m_type == sev_eCvType_Mean) {
if ( fabs(m_last_k) < 1) {
m_wval.val = m_wval.val + m_last_k * (m_val[ii].time - m_wval.time);
m_wval.time = m_val[ii].time;
}
else {
m_wval.time = m_wval.time + (m_val[ii].val - m_wval.val) / m_last_k;
m_wval.val = m_val[ii].val;
}
wval = m_wval.val;
wtime = m_wval.time;
}
else {
wval = m_val[ii].val;
wtime = m_val[ii].time;
m_wval = m_val[ii];
}
if ( index == 0) {
m_last = m_first = 0;
m_length = 0;
}
else {
m_first = ii + 1;
if ( m_first >= m_size)
m_first -= m_size;
m_length = m_last - m_first + 1;
if ( m_length < 0)
m_length += m_size;
}
if ( m_write_cb) {
pwr_tTime time;
time_Aadd( &time, &m_start_time, time_Float64ToD( 0, wtime));
(m_write_cb)( m_userdata, m_useridx, wval, &time);
}
}
// Calculate epsilon for all
void sev_valuecache::calculate_epsilon()
{
if ( m_length == 1) {
m_val[m_first].epsilon = 0;
return;
}
for ( int i = 0; i < m_length; i++)
calculate_epsilon(i);
}
// Calculate epsilon for one index
void sev_valuecache::calculate_epsilon( int index)
{
int ii = idx(index);
if ( m_k >= 1E32) {
m_val[ii].epsilon = fabs( m_val[ii].time - m_wval.time);
}
else {
m_val[ii].epsilon = fabs(m_val[ii].val - m_k * m_val[ii].time - m_m) / sqrt( 1 + m_k * m_k);
}
}
// Check deadband for one index
// Returns true if all values inside deadband.
bool sev_valuecache::check_deadband( int index)
{
if ( m_val[idx(index)].epsilon > m_deadband)
return false;
return true;
}
// Check deadband for all values
// Returns true if all values inside deadband.
bool sev_valuecache::check_deadband()
{
for ( int i = 0; i < m_length; i++) {
int ii = idx(i);
if ( m_val[ii].epsilon > m_deadband)
return false;
}
return true;
}
int sev_valuecache::get_optimal_write()
{
if ( m_type == sev_eCvType_Mean) {
m_last_k = m_k;
return 0;
}
double min_weight = 10E32;
int ii;
double dist;
double weight;
int min_idx = 0;
for ( int i = 0; i < m_length; i++) {
if ( m_length == m_size && i == m_length - 1)
continue;
ii = idx(i);
dist = sqrt( (m_val[ii].val - m_wval.val) * (m_val[ii].val - m_wval.val) +
(m_val[ii].time - m_wval.time) * (m_val[ii].time - m_wval.time));
weight = m_val[ii].epsilon / dist;
if ( weight < min_weight) {
min_weight = weight;
min_idx = i;
}
}
return min_idx;
}
/*
* Proview Open Source Process Control.
* Copyright (C) 2005-2016 SSAB EMEA AB.
*
* This file is part of Proview.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 2 of
* the License, or (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Proview. If not, see <http://www.gnu.org/licenses/>
*
* Linking Proview statically or dynamically with other modules is
* making a combined work based on Proview. Thus, the terms and
* conditions of the GNU General Public License cover the whole
* combination.
*
* In addition, as a special exception, the copyright holders of
* Proview give you permission to, from the build function in the
* Proview Configurator, combine Proview with modules generated by the
* Proview PLC Editor to a PLC program, regardless of the license
* terms of these modules. You may copy and distribute the resulting
* combined work under the terms of your choice, provided that every
* copy of the combined work is accompanied by a complete copy of
* the source code of Proview (the version used to produce the
* combined work), being distributed under the terms of the GNU
* General Public License plus this exception.
**/
#ifndef sev_valuecache_h
#define sev_valuecache_h
#include "pwr.h"
#include "co_time.h"
typedef enum {
sev_eCvType_Point,
sev_eCvType_Mean,
} sev_eCvType;
typedef struct {
double val;
double time;
double epsilon;
} sev_sCacheValue;
#define VALUECACHE_SIZE 20
class sev_valuecache {
static const int m_size;
sev_eCvType m_type;
void *m_userdata;
int m_useridx;
int m_length;
int m_first;
int m_last;
bool m_inited;
sev_sCacheValue m_val[VALUECACHE_SIZE];
sev_sCacheValue m_wval;
double m_k;
double m_m;
double m_deadband;
double m_deadband_value;
double m_deadband_time;
int m_last_opt_write;
pwr_tTime m_start_time;
double m_last_k;
void (*m_write_cb)( void *, int , double, pwr_tTime *);
public:
sev_valuecache( sev_eCvType type, double deadband_value, double deadband_time) :
m_type(type), m_userdata(0), m_useridx(0), m_length(0), m_first(0), m_last(0), m_inited(false),
m_deadband_value(deadband_value), m_deadband_time(deadband_time), m_write_cb(0) {
memset( &m_wval, 0, sizeof(m_wval));
time_GetTime(&m_start_time);
}
sev_valuecache( const sev_valuecache& x) : m_type(x.m_type), m_userdata(x.m_userdata), m_useridx(x.m_useridx),
m_length(x.m_length),
m_first(x.m_first), m_last(x.m_last), m_inited(x.m_inited), m_k(x.m_k), m_deadband(x.m_deadband),
m_deadband_value(x.m_deadband_value), m_deadband_time(x.m_deadband_time), m_last_opt_write(x.m_last_opt_write),
m_start_time(x.m_start_time), m_last_k(x.m_last_k), m_write_cb(x.m_write_cb) {
memcpy( m_val, x.m_val, sizeof(m_val));
memcpy( &m_wval, &x.m_wval, sizeof(m_wval));
}
~sev_valuecache() {}
int length() { return m_length;}
int idx( int index);
sev_sCacheValue& operator[]( const int index);
sev_sCacheValue& wval() { return m_wval;}
void add( double value, pwr_tTime *time);
void evaluate();
void calculate_k();
void write( int index);
void calculate_epsilon();
void calculate_epsilon( int index);
bool check_deadband( int index);
bool check_deadband();
int get_optimal_write();
double epsilon(int index) { return m_val[idx(index)].epsilon;}
double get_k() { return m_k;}
void set_write_cb( void (*write_cb)( void *, int, double, pwr_tTime *), void *userdata, int idx) {
m_write_cb = write_cb;
m_userdata = userdata;
m_useridx = idx;
}
};
#endif
...@@ -106,6 +106,26 @@ SObject pwrb:Type ...@@ -106,6 +106,26 @@ SObject pwrb:Type
EndBody EndBody
EndObject EndObject
!/** !/**
! Deadband calculated with linear regression of last points.
!*/
Object DeadBandLinearRegr $Bit
Body SysBody
Attr PgmName = "DeadBandLinearRegr"
Attr Text = "DeadBandLinearRegr"
Attr Value = 64
EndBody
EndObject
!/**
! Store a mean value calculated with linear regression.
!*/
Object DeadBandMeanValue $Bit
Body SysBody
Attr PgmName = "DeadBandMeanValue"
Attr Text = "DeadBandMeanValue"
Attr Value = 128
EndBody
EndObject
!/**
! Not yet implemented. ! Not yet implemented.
!*/ !*/
Object Parameter $Bit Object Parameter $Bit
......
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