Unofficial Node.JS Library for TwinCAT ADS Communication, designed to simplify connection to a Beckhoff PLC, and automatically generating a Symbol Map ADS Protocol from Beckhoff for ease of read/write and subscribe data operations.
The tc-context
library achieves this by fetching all the type data and initial symbol data at the moment connection and caching the memory locations of each individual symbol, as well as their parent symbol relationship. This grants the possibility to write partial structures directly into TwinCAT Function_Blocks
and Structures
, subscribing to complex objects, and clearing multiple Namespaces, all with single-line instructions at minimal run-time cost.
This library is made possible, thanks to the ads-client library by Jussi Isotalo<EMAIL_ADDRESS>If you are in search for a smaller library, focusing on ADS communication, make sure to check theads-client library.
For detailed information, see the official documentation.
Below is a quick example of creating a TcContext
for a Beckhoff PLC located at localhost, and performing read/write/clear operations. For more complex operations, and behavior definitions, see individual chapters on the subject matter.
MAIN(PRG)
PROGRAM MAIN
VAR
booleanValue : BOOL := TRUE;
numericValue : INT := 10;
structuredValue : Foo;
arrayValue : ARRAY [0..9] OF STRING := ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j']
END_VAR
Foo(FB)
FUNCTION_BLOCK Foo
VAR
realValue : REAL;
stringValue : STRING := 'hello world';
END_VAR
index.js
const { TcContext } = require('tc-context');
TcContext.create().then(async context => {
//Reading the program's 'MAIN' Namespace
let result = await context.$.MAIN.$get
/**
* result : {
* booleanValue : true,
* numericValue : 10,
* structuredValue : {
* realValue : 0,
* stringValue : 'hello world'
* },
* arrayValue : ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j']
* }
*/
//Clearing all members of a Structured Value
await context.$.MAIN.structuredValue.$clear()
result = await context.$.MAIN.structuredValue.$get;
/**
* result : { realValue : 0, stringValue : '' }
*/
//Setting a Numeric value
await context.$.MAIN.numericValue.$set(5);
result = await context.$.MAIN.numericValue.$get;
/**
* result : 5
*/
//Setting an Array
await context.$.MAIN.arrayValue.$set(['1', '2', '3']);
result = await context.$.MAIN.arrayValue.$get;
/**
* result : ['1', '2', '3', 'd', 'e', 'f', 'g', 'h', 'j']
*/
//Setting a program's Namespace values
const set = {
booleanValue : false,
structuredValue : {
realValue : 15,
stringValue : 'i am alive'
}
}
await context.$.MAIN.$set(set)
result = await context.$.MAIN.arrayValue.$get;
/**
* result : {
* booleanValue : false,
* numericValue : 5,
* structuredValue : {
* realValue : 15,
* stringValue : 'i am alive'
* },
* arrayValue : ['1', '2', '3', 'd', 'e', 'f', 'g', 'h', 'j']
* }
*/
//Clearing Namespace
await context.$.MAIN.$clear();
result = await context.$.MAIN.arrayValue.$get;
/**
* result : {
* booleanValue : false,
* numericValue : 0,
* structuredValue : {
* realValue : 0,
* stringValue : ''
* },
* arrayValue : ['', '', '', '', '', '', '', '', '']
* }
*/
//Disconnecting Context and killing bindings
await context.kill()
})
Run the following command to install package in your project:
npm install tc-context
Include the module in js:
const { TcContext } = require('tc-context')
The tc-context
library uses the ads-client library for connecting and communicating with a Beckhoff PLC over the ADS Protocol. Because of that, the connection setup configuration is equal to the connection setup of the ads-client, since the supplied settings are routed directly to the ads-client
.
See ads-client for detailed information on different types of possible connections.
const { TcContext } = require('tc-context')
//Connecting to a Localhost Beckhoff PLC
TcContext.create().then(async context => {
//Result contains the currently active context
//Perform operation with the created context....
//When done, kill the context
await context.kill();
})
In order to create a new TcContext
Object, a call to the TcContext.create()
functions must be made. By default, if no arguments are passed to the TcContext.create()
function, it is assumed to that the connection that will be made is to the localhost located PLC. Explicit settings can be provided as the first argument to the TcContext.create()
function.
NOTE: The TcContext
object must be explicitly killed at the end of its use, through the TcContext.kill()
method call. This will clean up all subscription handles, termination the connection, and clear the generated map, thus ensuring no memory leaks.
const { TcContext } = require('tc-context')
const settings = {
targetAmsNetId: '<IP_ADDRESS>.1.1',
targetAdsPort: 851
//And more....
}
//Connecting to a Localhost Beckhoff PLC
TcContext.create(settings).then(async context => {
//Result contains the currently active context for <IP_ADDRESS>.1.1:851
//Perform operation with the created context....
//When done, kill the context
await context.kill();
})
The settings are routed to the ads-client Client.connect()
method directly, without any modification. Hence, for more detailed and up-to-date information, on different connection patters, see the official documentation of ads-client.
The TcContext
Object is composed of 3 Components :
TcSymbolRegistry
- Storage for all the Symbol Maps created based based on the data gathered by TcCom
', passed through the TcTypeRegistry
. This property can be accessed by the TcContext.symbols
property, and the registered symbol maps can be accessed by the TcContext.symbols.namespaces
property. The TcSymbolNamespace
represents the Programs and any Global Variable Lists present in the PLC.TcContext.$
, which corresponds to TcContext.symbols.namespaces
TcTypeRegistry
- Storage for all the processed types, which are gathered by TcCom
Component. This component can be accessed through TcContext.types
property, and the registered types can be queried by TcContext.types.has(<type name>)
.TcCom
- Component responsible for communication and data passing between TcContext
and the PLC. This component can be accessed through TcContext.COM
property.Upon a successful TcContext
creation, a full TcSymbol
map is created, mirroring what is currently loaded in the Beckhoff PLC. The create TcSymbolNamespaces
can be accessed either through TcContext.symbols.namespaces
or TcContext.$
properties.
In order to avoid naming conflicts with symbols declared in the PLC, all public methods provided by the TcSymbol
Object begin with ``$` symbol.
IMPORTANT: Type safety is important and there is no implicit type conversion. This is to ensure, that whatever is written is well defined by both ends of the system. In case of type mismatch, an exception TcBindingInvalidTypeException
is raised and no operation is performed.
MAIN(PRG)
PROGRAM MAIN
VAR
booleanValue : BOOL;
numericValue : INT;
rangeValue : BYTE(50..100);
stringValue : STRING(50);
enumValue : MyEnum;
arrayValue : ARRAY[0..4] OF Foo;
END_VAR
Foo(FB)
FUNCTION_BLOCK Foo
VAR
{attribute 'Default' := '22.3'}
realValue : REAL;
{attribute 'ReadOnly'}
stringValue : STRING;
END_VAR
MyEnum(ENUM)
TYPE MyEnum :
(
member1 := 0,
member2 := 1,
member3 := 2
);
END_TYPE
The example above would produce a TcContext
of the following type:
TcContext.$.MAIN
- the MAIN Program.booleanValue
- symbol of type TcBooleanSymbol : TcSymbol
.numericValue
- symbol of type TcNumericSymbol : TcSymbol
.rangeValue
- symbol of type TcNumericSymbol : TcSymbol
with explicit value borders at [50-100]
, and a write of a value outside these borders raises the TcBindingOutOfRangeException
exception.stringValue
- symbol of type TcStringSymbol : TcSymbol
, with a max length of [50]
characters, and any attempt to write a string of [50+]
length raises the TcBindingOutOfRangeException
exception.enumValue
- symbol of type TcEnumSymbol : TcSymbol
, which only accepts input strings from ['MyEnum.member1', 'MyEnum.member2', 'MyEnum.member3']
list, and any write of value outside that list raises the TcBindingOutOfRangeException
exception.arrayValue
- symbol of type TcArraySymbol : TcSymbol
, which represents an array of size [5]
of type Foo
type Function_Block
[0-4]
- symbols of type TcStructureSymbol : TcSymbol
.realValue
- symbol of type TcNumericSymbol : TcSymbol
, with an explicit default value of 22.3
, which will be set, when ``TcSymbol.$clear()` is called.stringValue
- symbol of type TcStringSymbol : TcSymbol
, which is set to ReadOnly
, thus any call to TcSymbol.$clear()
or TcSymbol.$set()
results in an exception TcBindingReadOnlyException
, as well as a call to .stringValue
parent TcSymbol.$clear()
method will ignore this symbolconst { TcContext } = require('tc-context')
TcContext.create().then(async context => {
//Exception TcBindingOutOfRangeException
await context.$.MAIN.rangeValue.$set(10)
//MAIN.enumValue is set to 1
await context.$.MAIN.enumValue.$set('MyEnum.member2')
//Exception TcBindingReadOnlyException
await context.$.MAIN.arrayValue[0].stringValue.$set('hello world')
//For [0-4] all the .realValue members are set to 22.3
//while .stringValue member is left untouched
await context.$.MAIN.arrayValue.$clear()
//Explicit TcContext kill call
await context.kill();
})
The tc-context
library currently supports all of the TwinCAT Data Types, with the exception of:
NOTE: The TcContext
Object filters out unsupported members, when generating Symbol Maps. If the end result of a Structure
, Funtion_Block
or Array
, is a Symbol with no Children, no mapping will be created for that Object as well.
MAIN(PRG)
PROGRAM MAIN
VAR
booleanValue : BOOL;
filteredFB : Foo;
filteredArr : ARRAY[0..9] OF REFERENCE TO Foo;
END_VAR
Foo(FB)
FUNCTION_BLOCK Foo
VAR
ptr: POINTER TO INT;
ref : REFERENCE TO BYTE;
END_VAR
index.js
const { TcContext } = require('tc-context')
TcContext.create().then(async context => {
//booleanValue is defined
const booleanValue = context.$.MAIN.booleanValue
//filteredFB is undefined
const filteredFB = context.$.MAIN.filteredFB
//filteredArr is undefined
const filteredArr = context.$.MAIN.filteredArr
//Explicit TcContext kill call
await context.kill();
})
MAIN(PRG)
PROGRAM MAIN
VAR
booleanValue : BOOL;
numericValue : INT;
stringValue : STRING(50);
enumValue : MyEnum;
END_VAR
MyEnum(ENUM)
TYPE MyEnum :
(
member1 := 0,
member2 := 1,
member3 := 2
);
END_TYPE
index.js
const { TcContext } = require('tc-context')
TcContext.create().then(async context => {
//Returns the current value of MAIN.booleanValue
const booleanValue = await context.$.MAIN.booleanValue.$get
//Sets the value of MAIN.booleanValue
await context.$.MAIN.booleanValue.$set(true);
//Sets the value of MAIN.enumValue to MyEnum.member2 (1) and then read it back
const enumValue = await context.$.MAIN.enumValue.$set('MyEnum.member2')
.then(() => context.$.MAIN.enumValue.$get )
if (enumValue === 'MyEnum.member2') {
//Clears the MAIN.enumValue to MyEnum.member1 (0)
await context.$.MAIN.enumValue.$clear()
}
//Explicit TcContext kill call
await context.kill();
})
The TcSymbol
Object supports the following data operations:
$set(val)
- method for writing a value to the Binding of the TcSymbol
Object.
Promise
is fulfilled, it returns the value, which was successfully written to the TcSymbol
. Promise
is rejected, returns a TcException
Type Object, with information regarding the error$get
- property for reading the value of the TcSymbol
Object Binding.
Promise
is fulfilled, it returns the value, which was successfully read from the TcSymbol
. Promise
is rejected, returns a TcException
Type Object, with information regarding the error$clear()
- method for clearing the Binding of the TcSymbol
based on either its default value, or the explicitly specified Default
Attribute value
Promise
is fulfilled, clearing has successfully completedPromise
is rejected, returns a TcException
Type Object, with information regarding the errorMAIN(PRG)
PROGRAM MAIN
VAR
booleanValue : BOOL;
numericValue : INT;
stringValue : STRING(50);
foo : Foo;
END_VAR
Foo(FB)
FUNCTION_BLOCK Foo
VAR
booleanValue : BOOL;
arrayValue : ARRAY[0..9] OF INT;
END_VAR
index.js
const { TcContext } = require('tc-context')
TcContext.create().then(async context => {
//Activate on-change detection
await context.$.MAIN.numericValue.$subscribe()
await context.$.MAIN.foo.$subscribe()
//Attach event handlers
context.$.MAIN.numericValue.$on('changed', (e) => {
console.log(`Boolean Value has changed to ${e.data}`);
})
context.$.MAIN.foo.$on('changed', (e) => {
console.log(`{
booleanValue : ${e.data.booleanValue},
arrayValue : ${e.data.arrayValue.toString()}
}`)
})
//When no longer change detection is needed, disable it
await context.$.MAIN.foo.$unsubscribe();
//Subscribe to an array.
await context.$.MAIN.foo.arrayValue.$subscribe();
context.$.MAIN.foo.arrayValue.$on('changed', (e) => {
console.log(e.data.toString())
})
//Explicit TcContext kill call will also
//automatically unsubscribe all active change subscriptions
await context.kill();
})
TcSymbol
Objects are capable of subscribing to a data changed
event of type TcSymbolChangeEvent
. When subscribed, if the PLC symbol changes its value, any handlers, which were attached, will be invoked. To subscribe TcSymbol.$subscribe()
must be called, and to disable change detection TcSymbol.$unsubscribe()
is called. Handlers are added to the TcSymbol
by way of calling TcSymbol.$on(...)
and TcSymbol.$once(...)
and are removed through TcSymbol.$off(...)
. When unsubscribing, event handlers are not removed, they are simply ignored.
When subscribing to Structures
,Function_Blocks
and Arrays
, any change that happens to the data of that symbol, will also result in the changed
event. This includes change in symbol that are not supported.
MAIN(PRG)
PROGRAM MAIN
VAR
foo : Foo;
END_VAR
Foo(FB)
FUNCTION_BLOCK Foo
VAR
booleanValue : BOOL := FALSE;
ptr : POINTER TO STRING;
bar : Bar
END_VAR
Bar(FB)
FUNCTION_BLOCK Bar
VAR
numericValue : INT := 5;
END_VAR
index.js
const { TcContext } = require('tc-context')
TcContext.create().then(async context => {
//Activate on-change detection
await context.$.MAIN.foo.$subscribe()
//Attach event handlers
context.$.MAIN.foo.$on('changed', (e) => {
console.log(e.data);
}
//Hypothetical scenario of indefinite run-time
//When hypothetical scenario is finished
await context.kill();
})
In the example above, a TcSymbol.$subscribe()
is activated on MAIN.foo
, which would mean, any change happening in MAIN.foo
will invoke the changed
event. When MAIN.foo.booleanValue
changes from FALSE to TRUE, the TcEvent.data
will contain an output:
{
booleanValue : true,
bar : {
numericValue : 5
}
}
Following the previous scenario, if MAIN.foo.bar.numericValue
has a change from 5 to 10, this will also produce the changed
event, with the following output:
{
booleanValue : true,
bar : {
numericValue : 10
}
}
Lastly, even though MAIN.foo.ptr
is not supported, and a binding to it is not created, it is still part of the data layout of MAIN.foo
Symbol. It is because of this a change to the pointer value (not what its pointing to), will also produce a changed
event, with the following output (The 2 previous examples have happened) :
{
booleanValue : true,
bar : {
numericValue : 10
}
}
MAIN(PRG)
PROGRAM MAIN
VAR
numericValue : INT;
END_VAR
index.js
const { TcContext } = require('tc-context')
TcContext.create().then(async context => {
//Subscribe with a sampling rate of 1s
await context.$.MAIN.numericValue.$subscribe(1000)
//Attach event handlers
context.$.MAIN.numericValue.$on('changed', (e) => {
console.log(e.data);
}
//When hypothetical scenario is finished
await context.kill();
})
For Symbols, the value of which changes rapidly, and detection of the change each time would either be too costly or unneeded, it is possible to explicitly set the sampling rate in milliseconds, as the argument for TcSymbol.$subscribe()
. This will keep any changes of the Symbol from emitting changed
event between the sampling period.
MAIN(PRG)
PROGRAM MAIN
VAR
foo : Foo;
END_VAR
Foo(FB)
FUNCTION_BLOCK Foo
VAR
booleanValue : BOOL;
numericValue : INT;
stringValue : STRING;
bar : Bar
END_VAR
Bar(FB)
FUNCTION_BLOCK Bar
VAR
numericValue : INT;
booleanValue : BOOL;
END_VAR
index.js
const { TcContext } = require('tc-context')
TcContext.create().then(async context => {
await context.$.MAIN.foo.$set({ booleanValue : true, numericValue : 10});
let result = await context.$.MAIN.foo.$get;
/**
* result : {
* booleanValue : true
* numericValue : 10
* stringValue : ''
* bar : {
* numericValue : 0
* booleanValue : false
* }
* }
*/
await context.$.MAIN.foo.$set({
stringValue : 'hello world'
bar : {
numericValue : 15,
booleanValue : true
}
})
result = await context.$.MAIN.foo.$get;
/**
* result : {
* booleanValue : true
* numericValue : 10
* stringValue : 'hello world'
* bar : {
* numericValue : 15
* booleanValue : true
* }
* }
*/
//Writing to a non existent child, will result in an exception
await context.$.MAIN.foo.$set({ nonExistent : 56.6 })
.catch(err => console.log(err));
/**
* TcBindingOutOfRangeException : ....
*/
context.$.MAIN.foo.bar.$each(symbol => {
console.log(symbol.$binding.path)
})
/**
* Output :
* MAIN.foo.bar.numericValue
* MAIN.foo.bar.booleanValue
*/
await context.$.MAIN.foo.$clear();
result = await context.$.MAIN.foo.$get;
/**
* result : {
* booleanValue : false
* numericValue : 0
* stringValue : ''
* bar : {
* numericValue : 0
* booleanValue : false
* }
* }
*/
await context.kill();
})
TcSymbol
Objects are capable of operating on Structures
and Function_Blocks
. For TcContext
both Structures
and Function_Blocks
are treated equally as structures of data. It is possible to write only explicit parts of a TcSymbol
symbol, bound to a Structure
or Function_Block
Calling TcSymbol.$clear()
will clear all the members of the Structure
or Function_Block
, with the only exception of ignoring any child marked with {attribute 'ReadOnly'}.
By calling TcSymbol.$get
property, a full data-map of all nested-children is returned, with the exception of symbol marked with {attribute 'Ignore'}
When writing to a child, which is not part of TcSymbol
, the operation will result in an exception of type TcBindingOutOfRangeException
.
MAIN(PRG)
PROGRAM MAIN
VAR
foo : Foo;
END_VAR
Foo(FB)
FUNCTION_BLOCK Foo
VAR
booleanValue : BOOL;
END_VAR
{attribute 'TcRpcEnable'}
METHOD basicTask : INT
VAR_INPUT
lhs : INT,
rhs : INT
END_VAR
basicTask := lhs + rhs;
{attribute 'TcRpcEnable'}
METHOD complexTask : BOOL
VAR_INPUT
lhs : INT;
rhs : INT;
END_VAR
VAR_OUTPUT
sum : INT;
diff : INT;
END_VAR
complexTask := TRUE;
sum := lhs + rhs;
diff := lhs - rhs;
index.js
const { TcContext } = require('tc-context')
TcContext.create().then(async context => {
const basicTask = await context.$.MAIN.foo.basicTask({ lhs : 10, rhs : 15 });
/**
* basicTask : {
* result : 25
* }
*/
const complexTask = await context.$.MAIN.foo.complexTask({ lhs : 15, rhs : 10 });
/**
* basicTask : {
* result : 25
* outputs : {
* sum : 25,
* diff : 5
* }
* }
*/
await context.kill();
})
It is possible to invoke RPC Methods, marked with {attribute 'TcRpcEnable'}
, on the PLC Side through the TcContext
. Upon completion, a Javascript Object is returned with a field result
, which contains the return value of the method call.
If the method has VAR_OUTPUT
variables, the return of the method call will also contain outputs
field, which store the values.
NOTE: As of now, no type checking is performed when passing values to the method call, it is a direct route to the ads-client. See official documentation for the limitations of Rpc Method calls. This is important when dealing with enumerators
. When writing and reading enumerators
directly, their type is included, however when passing them to a Rpc Method, the type name of the enum must be omitted.
If the method call fails, exceptions of type TcComIsInvalidException
or TcComMethodCallException
are raised.
MAIN(PRG)
PROGRAM MAIN
VAR
myUnion : MyUnion;
END_VAR
MyUnion(UNION)
TYPE DUT :
UNION
a : LREAL;
b : LINT;
c : WORD;
END_UNION
END_TYPE
index.js
const { TcContext } = require('tc-context')
TcContext.create().then(async context => {
let result = await context.$.MAIN.myUnion.$get
/**
* result : {
* a : 0,
* b : 0,
* c : 0
* }
*/
await context.$.MAIN.myUnion.c.$set(10)
let result = await context.$.MAIN.myUnion.$get
/**
* result : {
* a : 4.94065645841247E-323,
* b : 10,
* c : 10
* }
*/
await context.kill();
})
UNIONs
are considered Structured symbols in the tc-context
library, and are accessed and manipulated the same way as Structured TcSymbols
.
MAIN(PRG)
PROGRAM MAIN
VAR
arr : ARRAY[0..4] OF BOOLEAN;
END_VAR
index.js
const { TcContext } = require('tc-context')
TcContext.create().then(async context => {
await context.$.MAIN.arr.$set([true, true, true]);
let result = await context.$.MAIN.arr.$get;
/**
* result : [true, true, true, false, false]
*/
await context.$.MAIN.arr.$clear();
result = await context.$.MAIN.arr.$get;
/**
* result : [false, false, false, false, false]
*/
await context.$.MAIN.arr[3].$set(true);
result = await context.$.MAIN.arr[3].$get;
/**
* result : true
*/
result = await context.$.MAIN.arr.$get;
/**
* result : [false, false, true, false, false]
*/
context.$.MAIN.arr.$each(symbol => {
console.log(symbol.$binding.name)
})
/**
* Output :
* arr[0]
* arr[1]
* arr[2]
* arr[3]
* arr[4]
*/
await context.kill();
})
The TcContext
is capable of generating a TcSymbol
for TwinCAT array types, allowing for reading, writing, clearing and subscribing operations on the array as a whole, as well as accessing individual members through the subscript operator.
NOTE: The indexes of TcSymbol
array are synchronized with the TwinCAT array indexes. This means the starting index of a TcSymbol
of an array
is that of the starting symbol of the TwinCAT array
.
MAIN(PRG)
PROGRAM MAIN
VAR
arr1 : ARRAY[0..4] OF INT;
arr2 : ARRAY[1..5] OF INT;
END_VAR
index.js
const { TcContext } = require('tc-context')
TcContext.create().then(async context => {
console.log({
startIndex : context.$.MAIN.arr1.$startIndex,
length : context.$.MAIN.arr1.$length
})
/**
* Output : { startIndex : 0, length : 5 }
*/
console.log({
startIndex : context.$.MAIN.arr2.$startIndex,
length : context.$.MAIN.arr2.$length
})
/**
* Output : { startIndex : 1, length : 5 }
*/
context.$.MAIN.arr1.$each((symbol, index) => {
console.log({ index, name : symbol : symbol.$binding.key})
})
/**
* Output :
* { index : 0, symbol : arr1[0]}
* { index : 1, symbol : arr1[1]}
* { index : 2, symbol : arr1[2]}
* { index : 3, symbol : arr1[3]}
* { index : 4, symbol : arr1[4]}
*/
context.$.MAIN.arr2.$each((symbol, index) => {
console.log({ index, name : symbol : symbol.$binding.key})
})
/**
* Output :
* { index : 1, symbol : arr1[1]}
* { index : 2, symbol : arr1[2]}
* { index : 3, symbol : arr1[3]}
* { index : 4, symbol : arr1[4]}
* { index : 5, symbol : arr1[5]}
*/
//elem is undefined
const elem = context.$.MAIN.arr2[0];
await context.kill();
})
MAIN(PRG)
PROGRAM MAIN
VAR
arr1 : ARRAY[0..2, 0..2] OF INT;
arr2 : ARRAY[0..2] OF ARRAY[0..2] OF INT;
END_VAR
index.js
const { TcContext } = require('tc-context')
TcContext.create().then(async context => {
let result = await context.$.MAIN.arr1.$get;
/**
* result : [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
*/
result = await context.$.MAIN.arr1[1].$get;
/**
* result : [0, 0, 0]
*/
await context.$.MAIN.arr2[2].$set([1, 2, 3])
result = await context.$.MAIN.arr2.$get;
/**
* result : [[0, 0, 0], [0, 0, 0], [1, 2, 3]]
*/
await context.$.MAIN.arr1[1][1].$set(10);
result = await context.$.MAIN.arr1[1].$get;
/**
* result : [0, 10, 0]
*/
result = await context.$.MAIN.arr2.$clear()
.then(() => context.$.MAIN.arr2.$get)
/**
* result : [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
*/
result = await context.$.MAIN.arr1[1].$clear()
.then(() => context.$.MAIN.arr1.$get)
/**
* result : [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
*/
await context.kill();
})
TwinCAT allows for 2 different ways of declaring Multidimensional Arrays :
ARRAY[dimension1, dimension2, ...., dimensionN] OF <type>
ARRAY[dimension1] OF ARRAY[dimension2] OF ..... ARRAY[dimensionN] of <type>
Regardless of the case, the TcContext
treats both of these multidimensional arrays as multidimensional Javascript Arrays, and will process them accordingly. This allows for TcSymbol.$get
, TcSymbol.$set()
, TcSymbol.$clear()
and TcSymbol.$subscribe()
operations on any of the dimension levels.
MAIN(PRG)
PROGRAM MAIN
VAR
numericValue : INT[10..50];
stringValue : STRING(10);
foo : Foo;
END_VAR
Foo(FB)
FUNCTION_BLOCK Foo
VAR
arrayValue : ARRAY[0..4] OF int;
enumValue : MyEnum;
END_VAR
MyEnum(ENUM)
TYPE MyEnum :
(
member1 := 0,
member2 := 1,
member3 := 2
);
END_TYPE
index.js
const { TcContext, TcSymbols } = require('tc-context')
TcContext.create().then(async context => {
if (context.$.MAIN.numericValue instanceof TcSymbols.TcNumericSymbol) {
console.log({
min : context.$.MAIN.numericValue.$lowerBorder
max : context.$.MAIN.numericValue.$upperBorder
})
}
/**
* Output :
* { min : 10, max : 50 }
*/
if (context.$.MAIN.stringValue instanceof TcSymbols.TcStringSymbol) {
console.log({ length : context.$.MAIN.stringValue.$length })
}
/**
* Output :
* { length : 10 }
*/
if (context.$.MAIN.foo instanceof TcSymbols.TcStructureSymbol) {
context.$.MAIN.foo.$each(symbol => {
if (symbol instanceof TcSymbols.TcArraySymbol) {
console.log({ length : symbol.$length })
} else if (symbol instanceof TcSymbols.TcEnumSymbol) {
console.log({ fields : symbol.$fields })
}
})
}
/**
* Output :
* { length : 5 }
* { fields : ['MyEnum.member1', 'MyEnum.member2', 'MyEnum.member3']}
*/
//Explicit TcContext kill call
await context.kill();
})
TcSymbol
provides the base foundation for TcContext
Symbol Map creation. Each of the TcSymbols
, has a concrete type, depending on the PLC Symbol Type. The concrete TcSymbol
performs type checks and range checks, as well as exposes additional, type specific, functionality. The concrete types of TcSymbol
and their unique functionality is described below.
There is no implicit type conversion done. All symbols must be provided with the exact type of value, when performing TcSymbol.$set()
operations. The reason is to ensure there is no ambiguity when operating on the PLC.
TcBooleanSymbol
represents the BOOL
Data Type of the PLC, and is the most similar to the base TcSymbol
. The only difference is, that it performs type checking abd throwing an exception of type TcBindingInvalidTypeException
, if during the TcSymbol.$set()
call, the value passed is not of type boolean
.
TcNumericSymbol
represents all the numeric Data Type Symbols of the PLC, and exposes additional symbol information, in the form of TcNumericSymbol.$upperBorder
and TcNumericSymbol.$lowerBorder
.
When performing a TcSymbol.$set()
, the input value is check to be of type number
, and that the value is within the specified range. Failure to comply results in exceptions of type TcBindingInvalidTypeException
and TcBindingOutOfRangeException
respectfully.
TcStringSymbol
represents STRING
and WSTRING
Data Types of the PLC, and exposes the TcStringSymbol.$length
property, which states the maximum length of a string, that can be written to it.
TcStringSymbol
performs type checking on the the passed argument to TcSymbol.$set()
to make sure it is of type string
and does not exceed the specified length. Failure to comply results in exceptions of type TcBindingInvalidTypeException
and TcBindingOutOfRangeException
respectfully.
TcEnumSymbol
represents the ENUM
Data Types of the PLC and provides the TcEnumSymbol.$fields
property. This property provides a list of strings of all the acceptable inputs to the `TcSymbol.
The TcEnumSymbol
performs type checking on the the passed argument to TcSymbol.$set()
to make sure it is of type string
and is part of the TcEnumSymbol.$fields
list. Failure to comply results in exceptions of type TcBindingInvalidTypeException
and TcBindingOutOfRangeException
respectfully.
IMPORTANT: Alias to ENUM
is treated as its own unique type. Be aware of this.
MAIN(PRG)
PROGRAM MAIN
VAR
enumValue : MyEnum;
END_VAR
MyEnum(ENUM)
TYPE MyEnum :
(
member1 := 0,
member2 := 1,
member3 := 2
);
END_TYPE
index.js
const { TcContext, TcEnumSymbol } = require('tc-context');
TcContext.create().then(async context => {
if (context.$.MAIN.enumValue instanceof TcSymbols.TcEnumSymbol) {
console.log(context.$.MAIN.enumValue.$fields)
}
/**
* Output :
* ['MyEnum.member1', 'MyEnum.member2', 'MyEnum.member3']
*/
await context.$.MAIN.enumValue.$set('MyEnum.member3');
let result = await context.$.MAIN.enumValue.$get;
/**
* result : 'MyEnum.member3'
*/
await context.$.MAIN.enumValue.$set('WrongValue')
.catch(err => console.log(err))
/**
* Output :
* TcBindingOutOfRangeException
*/
await context.kill()
})
TcStructureSymbol
represents Structure
and Function_Block
Data Types of the PLC. As of now, no distinction between the two types is made from the pointer of view of TcContext
.
The TcStructureSymbol
performs a type check, to make sure any argument that is passed to TcSymbol.$set()
is a plain Javascript Object, otherwise a TcBindingInvalidTypeException
exception is raised.
Additionally, TcStructureSymbol
provides TcStructureSymbol.$each()
function, which iterates over all of its children TcSymbols
.
TcArraySymbol
represents ARRAY[...] OF <type>
Data Type of the PLC. Similarly to the TcStructureSymbol
, the TcArraySymbol
also provides a TcArraySymbol.$each()
function, which iterates over all of its children TcSymbols
.
Due to TwinCAT allowing to explicitly specify the starting index of an array, that start index can be read through the TcArraySymbol.$startIndex
property. Lastly, TcArraySymbol.$length
allows to read the length of the TcArraySymbol
array.
When writing to TcSymbol.$set()
, the TcArraySymbol
will perform type checking on the input argument, to make sure it is of type array
, as well as that the written array does not exceed the length of the TcArraySymbol
. Failure to comply results in exceptions of type TcBindingInvalidTypeException
and TcBindingOutOfRangeException
respectfully.
In a situation the where the TcContext
was reinitialized
, any previously stored TcSymbol
becomes invalidated. One way to managed invalidation, is by assigning a callback to TcSymbol.$onInvalidated()
method. This method will be called, when the TcContext
invalidates the TcSymbol
. Invalidation in a Symbol Map happens from the bottom-up, where children are invalidated before their parents are.
Beckhoff PLC Symbol can be marked with special attributes, which are processed by TcContext
. These attributes alter the behavior of some TcSymbols
and impose additional rules.
All Primitive Data Types (Not Structures
, Function_Blocks
or Arrays
) support the Default
Attribute. This attribute informs what value to write upon a call to TcSymbol.$clear()
. If no value is specified, the value that is written, is that of a an initial value (false for boolean
, 0 for numbers
and empty string for string
).
Structures
, Function_Blocks
and Arrays
are not affected by the Default
attribute.
MAIN(PRG)
PROGRAM MAIN
VAR
{attribute 'Default' := 'true'}
booleanValue : BOOL;
{attribute 'Default' := '5'}
numericValue : INT;
{attribute 'Default' := 'hello world'}
stringValue : STRING(50);
{attribute 'Default' := 'member3'}
enumValue : MyEnum;
END_VAR
MyEnum(ENUM)
TYPE MyEnum :
(
member1 := 0,
member2 := 1,
member3 := 2
);
END_TYPE
index.js
const { TcContext } = require('tc-context')
TcContext.create().then(async context => {
//MAIN.booleanValue is set to true
await context.$.MAIN.booleanValue.$clear()
//MAIN.numericValue is set to 5
await context.$.MAIN.numericValue.$clear()
//MAIN.stringValue is set to 'hello world'
await context.$.MAIN.stringValue.$clear()
//MAIN.enumValue is set to 'MyEnum.member3'
await context.$.MAIN.enumValue.$clear()
//Explicit TcContext kill call
await context.kill();
})
All Bindable Types support the ReadOnly
Attribute. The ReadOnly
attributes ensures that no write operation can be made to the TcSymbol
Object, either through the TcSymbol.$set()
method or TcSymbol.$clear()
. If a write operation is made to a ReadOnly
Object, a TcBindingReadOnlyException
Exception is thrown.
If TcStructureSymbol
or TcArraySymbol
is marked as ReadOnly
, then all of their children TcSymbols
are marked as ReadOnly
as well. When calling TcSymbol.$clear()
on TcStructureSymbol
Object that is not ReadOnly
, but has children marked as ReadOnly
, those children will be ignored during the TcSymbol.$clear()
operation.
MAIN(PRG)
PROGRAM MAIN
VAR
{attribute 'ReadOnly'}
booleanValue : BOOL;
{attribute 'ReadOnly'}
numericValue : INT;
{attribute 'ReadOnly'}
readOnlyFoo : Foo;
normalFoo : Foo;
END_VAR
Foo(FB)
FUNCTION_BLOCK Foo
VAR
{attribute 'ReadOnly'}
stringValue: STRING := 'hello world';
numericValue : BYTE := 100;
END_VAR
index.js
const { TcContext } = require('tc-context')
TcContext.create().then(async context => {
//MAIN.booleanValue will throw exception
await context.$.MAIN.booleanValue.$clear().catch(err => {
//MAIN.booleanValue is ReadOnly. This block will execute
})
//MAIN.numericValue will throw exception
await context.$.MAIN.numericValue.$clear().catch(err => {
//MAIN.numericValue is ReadOnly. This block will execute
})
//MAIN.foo will throw exception
await context.$.MAIN.foo.$clear().catch(err => {
//MAIN.foo is ReadOnly. This block will execute
})
//This will not raise an exception, however MAIN.normalFoo.stringValue will be left untouched
const result = await context.$.MAIN.normalFoo.$clear().then(() => context.$.MAIN.normalFoo.$get);
/**
* result : {
* stringValue : 'hello world'
* numericValue : 0
* }
*/
//Explicit TcContext kill call
await context.kill();
})
All bindable types support the Ignore
Attribute. The Ignore
attributes ensures that no TcSymbol
generation takes place by the TcContext
. The Ignore
attribute, when applied to TcStructureSymbol
and TcArraySymbol
, will also be applied to its children. If the end result of a TcStructureSymbol
is an Object with no children, then that TcStructureSymbol
will also be ignored.
MAIN(PRG)
PROGRAM MAIN
VAR
booleanValue : BOOL;
numericValue : INT;
foo : Foo;
END_VAR
Foo(FB)
FUNCTION_BLOCK Foo
VAR
{attribute 'Ignore'}
stringValue: STRING := 'hello world';
byteValue : BYTE := 100;
END_VAR
index.js
const { TcContext } = require('tc-context')
TcContext.create().then(async context => {
//booleanValue is defined
const booleanValue = context.$.MAIN.booleanValue;
//numericValue is defined
const numericValue = context.$.MAIN.numericValue;
//byteValue is defined
const byteValue = context.$.MAIN.foo.byteValue;
//stringValue is undefined
const stringValue = context.$.MAIN.foo.stringValue;
//Explicit TcContext kill call
await context.kill();
})
It is possible to apply an alias to the events produced by TcSymbols
. This way it is possible to narrow-down event handling, based on the specified alias.
MAIN(PRG)
PROGRAM MAIN
VAR
foo : Foo;
END_VAR
Foo(FB)
FUNCTION_BLOCK Foo
VAR
{attribute 'onSet' := 'stringSet'}
stringValue: STRING := 'hello world';
END_VAR
index.js
const { TcContext } = require('tc-context')
TcContext.create().then(async context => {
//Listen for the aliased event
context.$.MAIN.foo.stringValue.$on('stringSet', (e) => {
console.log('set was aliased to stringSet : ', e.data);
})
//Explicit TcContext kill call
await context.kill();
})
When aliasing a TcSymbol's
event, the default event name will be replaced by the provided name, based on the Attribute Parameter. These attributes are:
onSet
- will replace the set
eventonGet
- will replace the get
eventonClear
- will replace the cleared
eventonChange
- will replace the changed
event.All events emitted by the components of TcContext
inherit from TcEvent
. The TcEvent
in TcContext
are designed to propagate the emitted event up from the component , all the way to the TcContext
Object.
MAIN(PRG)
PROGRAM MAIN
VAR
booleanValue : BOOL;
numericValue : INT;
END_VAR
index.js
const { TcContext } = require('tc-context')
TcContext.create().then(async context => {
context.$.MAIN.booleanValue.$on('set', () => {
console.log('set was called on MAIN.booleanValue')
})
context.on('set', () => {
console.log('event propagated to context, where it was also caught')
})
await context.$.MAIN.booleanValue.$set(true);
/**
* When set completed output :
* 'set was called on MAIN.booleanValue'
* 'context also caught the event'
*/
//Explicit TcContext kill call
await context.kill();
})
If the TcEvent
has been handled, and the propagation of the event is no longer wanted, a call to TcEvent.stopPropagation()
function will stop any propagation up towards TcContext
.
MAIN(PRG)
PROGRAM MAIN
VAR
foo : Foo;
END_VAR
Foo(FB)
FUNCTION_BLOCK Foo
VAR
{attribute 'onChange' := 'booleanChanged'}
booleanValue: BOOL;
END_VAR
index.js
const { TcContext } = require('tc-context')
TcContext.create().then(async context => {
context.$.on('booleanChanged', (e) => {
console.log('caught booleanChanged event')
e.stopPropagation()
})
context.on('booleanChanged', () => {
//This will never happen, because event stopped Propagated
})
//Explicit TcContext kill call
await context.kill();
})
List of all events produced by the tc-context
library.
| Component | Event | Event Type | Description |
|---|---|---|---|
TcComponent |
killed |
TcContextKilledEvent |
When the current TcContext is killed and no longer is valid |
reinitialized |
TcContextReinitializedEvent |
When the current TcContext was rebuild and is once again valid |
|
TcCom |
connected |
TcComConnectedEvent |
When the current TcContext established the initial connection to the PLC |
disconnected |
TcComDisconnectedEvent |
When the current connection to the PLC has been closed | |
sourceChanged |
TcComSourceChangedEvent |
When the PLC Code base changes during an active connection | |
connectionLost |
TcComConnectionLostEvent |
When the TcCom looses connection to the Target PLC | |
reconnected |
TcComReconnectedEvent |
When the TcCom reestablishes the connection to the Target PLC | |
TcSymbol |
set |
TcSymbolSetEvent |
When a .$set() operation was completed |
get |
TcSymbolGetEvent |
When a .$get operation was completed |
|
cleared |
TcSymbolClearedEvent |
When a .$clear() operation was completed |
|
changed |
TcSymbolChangedEvent |
When the symbol value changed post .$subscribe() operation |
|
TcSymbolRegistry |
created |
TcSymbolRegistryCreatedEvent |
When the Symbol Map has been created |
destroyed |
TcSymbolRegistryDestroyedEvent |
When the Symbol Map has been destroyed | |
TcTypeRegistry |
created |
TcTypeRegistryCreatedEvent |
When the Type Map has been created |
destroyed |
TcTypeRegistryDestroyedEvent |
When the Type Map has been destroyed |
| Base | Component | Concrete |
|---|---|---|
TcEvent |
TcContextEvent |
TcContextReinitializedEvent |
TcContextKilledEvent |
||
TcSymbolEvent |
TcSymbolGetEvent |
|
TcSymbolSetEvent |
||
TcSymbolClearedEvent |
||
TcSymbolChangedEvent |
||
TcComEvent |
TcComConnectedEvent |
|
TcComDisconnectedEvent |
||
TcComSourceChangedEvent |
||
TcTypeRegistryEvent |
TcTypeRegistryCreatedEvent |
|
TcTypeRegistryDestroyedEvent |
||
TcSymbolRegistryEvent |
TcSymbolRegistryCreatedEvent |
|
TcSymbolRegistryDestroyedEvent |
const { TcContext } = require('tc-context')
TcContext.create().then(async context => {
//Attach listeners for kill event...
context.on('killed', () => console.log('context was killed'));
//TcContext manipulation code.....
//Explicit TcContext kill call
await context.kill();
//TcContext is no longer valid after this point, and
//any symbols produced by it should no longer be used...
})
/**
* Expected output:
*
* context was killed
*/
During normal operation, a TcContext
is valid from the moment it was created through TcContext.create()
call, upon until a call to TcContext.kill()
is made. When TcContext.kill()
completes its task, a killed
event of type TcContextKilledEvent
is emitted, which can be captured.
This holds true if no PLC-side code changes have been made from TcContext's
creation moment. If Activation of new configuration, or any Code Change takes place after TcContext.create()
was called, then that change in TcContext
will be detected. When detected, the TcContext
will invalidate all the created TcSymbols
, remove all subscriptions, clear Type and Symbol maps, kill itself, and then it will build a new map, based on the new symbols types and locations, which are present in the PLC.
MAIN(PRG)
PROGRAM MAIN
VAR
booleanValue : BOOL := TRUE;
END_VAR
index.js
const { TcContext } = require('tc-context')
TcContext.create().then(async context => {
//Attach listeners for kill and reinitialized events...
context.on('killed', () => console.log('context was killed'));
context.on('reinitialized', () => console.log('context was reinitialized'));
//Storing the symbol for use.
const booleanValue = context.$.MAIN.booleanValue;
/**
* Hypothetical scenario...
* Some code that uses the context cyclically for undefined period of time
* without yet reaching the .kill() call.
*
* During this hypothetical time - the PLC Configuration was changed
*/
//The line below is reached only after the PLC Configuration was changed
//in the above hypothetical scenario
await booleanValue.$set(true)
.catch(err => console.log(err));
/**
* Expected output:
*
* context was killed
* context was reinitialized
* TcBindingIsInvalidException :
* Attempting to read an Invalidated TcBinding[\<symbol name\>] ( \<symbol path\> )...
*/
})
As seen above, if after TcContext
was created, changes to code of the PLC have been made, the TcContext
will detect it, and perform the TcContext.kill()
operation automatically, upon completion of which, the killed
event of type TcContextKilledEvent
will be raised. Afterwards, the TcContext
will reconnect to the PLC, and a generation and build of new Type and Symbol Maps is executed. When completed the TcContext
will raise the reinitialized
event of type TcContextReinitializedEvent
. Any attempt to perform operations on any previously stored Invalidated TcSymbols
(such as booleanValue
in the example above), will result in an exception of type TcBindingIsInvalidException
These events can be used, as means of updating any dependencies, which utilize the created TcSymbols
by the TcContext
and thus ensuring stability.
NOTE: The reason for this approach, is because upon TcContext
creation, a scan is performed, which caches all the Types and Symbols, as well as their memory locations in the PLC. These memory locations are used as a means of reading, writing, clearing and subscribing to symbols, simple or complex. When an online change is performed, there are no guaranties that the location of the previously cached symbols is same, and even if an update to it can be made, the symbol itself could be of different type. All this would result in undefined behavior.
MAIN(PRG)
PROGRAM MAIN
VAR
booleanValue : BOOL := TRUE;
END_VAR
index.js
const { TcContext } = require('tc-context')
//Example of class which uses a symbol for its operation
class Foo {
//Bind the symbol at construction
constructor(symbol) { this.__symbol = symbol; }
//Means to update the binding of the symbol
bind(symbol) {
console.log('setting new symbol')
this.__symbol = symbol
}
//Method which uses the symbol
async printState() {
if (this.__symbol) {
const val = await this.__symbol.$get;
return (val) ? 'Symbol is on' : 'Symbol is off'
}
}
}
TcContext.create().then(async context => {
//Create the object which is dependent on the TcContext's symbol
const bar = new Foo(context.$.MAIN.booleanValue);
//When invalidated set the binding to null
context.$.MAIN.booleanValue.$onInvalidated(() => {
console.log('symbol was invalidated')
bar.bind(null);
})
context.on('reinitialized', () => {
console.log('context was reinitialized')
bar.bind(context.$.MAIN.booleanValue);
})
/**
* Hypothetical scenario...
* Some code that uses the context cyclically for undefined period of time
* without yet reaching the .kill() call.
*
* During this hypothetical time - the PLC Configuration was changed
*/
//The line below is reached only after the PLC Configuration was
//changed in the above hypothetical scenario
await bar.printState()
await context.kill()
})
/**
* Expected output:
*
* symbol was invalidated
* setting new symbol
* context was reinitialized
* setting new symbol
* Symbol is on
* symbol was invalidated
* setting new symbol
*/
The example above illustrates how any components, which depends on TcSymbol
, can use the emitted events to refresh its bindings, thus ensuring defined behavior.
| Base | Component | Concrete | Description |
|---|---|---|---|
TcException |
TcBindingException |
TcBindingIsInvalidException |
When operations are made on a TcSymbol of a killed TcContext |
TcBindingInvalidTypeException |
When input type does not match TcSymbol type |
||
TcBindingOutOfRangeException |
When input data length is outside the boundaries of TcSymbol |
||
TcBindingReadOnlyException |
When write commands are called on a Read-only TcSymbol |
||
TcComException |
TcComBusyException |
Connection to a PLC has already been made | |
TcComConnectException |
When an error occurred with establishing connection | ||
TcComIsInvalidException |
When operations are made on a TcCom of a killed TcContext |
||
TcComDisconnectException |
When an error occurred with disconnecting from Target | ||
TcComChangeDetectionException |
When an error occurred with establishing Code Change Monitoring | ||
TcComUnsubscribeException |
When an error occurred with unsubscribing from TwinCAT Symbol | ||
TcComFromRawException |
When an error occurred when transforming Data from Raw | ||
TcComToRawException |
When an error occurred when transforming Data to Raw | ||
TcComSubscribeException |
When an error occurred with subscribing to a TwinCAT Symbol | ||
TcComDataWriteException |
When an error occurred during data writing to TwinCAT | ||
TcComDataReadException |
When an error occurred during data reading from TwinCAT | ||
TcComTypeQueryException |
When an error occurred with querying Type Data from TwinCAT | ||
TcComSymbolQueryException |
When an error occurred with querying Symbol Data from TwinCAT | ||
TcComMethodCallException |
When an error occurred with invoking a RPC Method over ADS |
Detailed documentation of the TcContext
code base itself can be found under the /docs/ folder in this repository.
Licensed under MIT License.
Copyright (c) 2020 Dmitrij Trifanov<EMAIL_ADDRESS>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Generated using TypeDoc


|
|
|
|
|
|
|
|
BlastFile(iFileHandle,
multi=False,
**kw)
Factory function for Reader and Writer classes |
source code
|
|
|
|
getSequence(blastDb,
accession,
start=0,
end=0,
strand=' + ' ,
padding=0,
debug=False)
Load a sequence from a BLAST database. |
source code
|
|
|
|
genomeToBlock(chrom,
start,
end,
L=1000000000,
blockSize=5000000,
delimiter=' . ' )
Transform genomic to block coordinates. |
source code
|
|
|
|
genomeToBlocks(chrom,
start,
end,
L,
blockSize=5000000)
Transform genomic to block coordinates
(chrom.blockStart-blockEnd:start-end). |
source code
|
|
|
|
|


1 write to _isInitialized
Microsoft.PowerShell.Commands.Utility (1)
commands\utility\WebCmdlet\StreamHelper.cs (1)
218
_isInitialized
= true;
1 reference to _isInitialized
Microsoft.PowerShell.Commands.Utility (1)
commands\utility\WebCmdlet\StreamHelper.cs (1)
216
if (
_isInitialized
) { return; }


Deep Sea Mining, with Pliant Energy Systems
18 Jan 2021I followed up with Benjamin Pietro Filardo, founder of Pliant Energy Systems and NACROM, the North American Consortium for Responsible Ocean Mining. Pietro talks about the deep sea mining industry, an untapped market with a massive potential for growth. Pietro discusses the current proposed solutions for deep sea mining which are environmentally destructive, and he offers an alternative solution using swarm robots which could mine the depths of the ocean while creating minimal disturbance to this mysterious habitat.
What made me gravitate to doing a second interview with Pietro is that while talking to him about applications for his robot, I learned a lot about the potential for deep sea mining. The incredible amount of resources laying openly on the seafloor can fuel the battery powered selfless car revolution that is happening on the surface of the Earth. What is stopping us today is how extremely inaccesible this environment is to any type of operations, especially manned vehicles.
There is an industry that will open up around deep sea mining. It is not a question of “if”, but “when” technology can advance enough to do so.


|
|
From The September 1998 Issue of Nutrition Science News The Cholesterol ConundrumAnswers may lie in optimizing the LDL:HDL ratio. In the past decade, Americans have dramatically increased their cholesterol awareness. Most people would say a low-fat, low-cholesterol diet produces low circulating blood levels of cholesterol and lower cholesterol levels in turn reduce the risk of heart disease and atherosclerosis and therefore lengthen life span. In fact, scientific data do not always confirm these assumptions. First, a cholesterol primer: Cholesterol is a waxlike fatty substance produced in the liver. Cholesterol particles are lipoproteins--combinations of fats and proteins that travel in the blood and are essential for life. A cholesterol buildup on blood vessel walls is a known risk factor for coronary heart disease. Circulating levels of cholesterol are measured by its concentration in blood serum and expressed in milligrams per deciliter of blood serum (mg/dL). High-density lipoprotein (HDL) cholesterol, known as "good" or "reverse" cholesterol, carries cholesterol out of arterial walls. The prevailing opinion is that HDL levels should be 35 mg/dL minimum and preferably exceed 60 mg/dL. Low-density lipoprotein (LDL) cholesterol, known as "bad" cholesterol, carries cholesterol into arterial walls. Doctors like to keep LDL cholesterol levels below 130 mg/dL. Scientific evidence suggests LDL levels should not exceed HDL levels by more than four to five times. So if HDL levels are 60 mg/dL and LDL levels are 180 mg/dL, or three times greater than HDL, cholesterol is within the acceptable range. However, if LDL cholesterol is 180 mg/dL and HDL is 30 mg/dL, a ratio of 6-to-1 LDL to HDL, then there is cause for concern. Triglycerides are another circulating blood fat. Recent studies suggest triglyceride levels should not exceed 100 mg/dL. Total cholesterol is a broad measure including all the fractions of circulating blood fats. The prevailing opinion is that total cholesterol levels less than 200 mg/dL are desirable. What often goes unreported are the potential harmful effects that can result from a reduction of dietary cholesterol and fat. A study of 140 elderly subjects showed that the risk of clinical depression increased threefold with very low total cholesterol levels (less than 160 mg/dL).1 In another study, six men were referred to a clinic for cholesterol reduction; four experienced measurable increases in clinical depression.2 Along these lines, low or declining cholesterol profiles below a concentration of 4.78 micromoles per liter concentration in blood serum vs. 4.78 to 6.21 micromoles per liter are associated with 3.16 times the risk of suicide among working-age men.3 Attempts to reduce cholesterol usually revolve around dietary fat reduction, which may create a whole new set of problems. One study involving 20 healthy young adults who switched from a typical American 40 percent fat diet to a 25 percent fat diet resulted in depressive mood changes in just one month.4 Efforts to reduce dietary fat often result in low consumption of essential fats, in particular the omega-3 fatty acids required for nervous system maintenance. This may explain the mood changes involved with cholesterol reduction. Low Cholesterol and Mortality Because cholesterol, primarily LDL cholesterol, is the carrier of fat-soluble antioxidants such as vitamin E (alpha- and gamma-tocopherol) and carotenoid pigments such as beta-carotene, lutein and lycopene, any reduction in cholesterol results in reduced delivery of these antioxidants to tissues. It appears wise to increase consumption of antioxidants when attempting to reduce cholesterol. Cholesterol reduction seems even less prudent for older adults. Declining total cholesterol levels in nursing-home patients--losses greater than 45 mg/dL per year--increase the odds of mortality by more than six times.7 High total cholesterol or low HDL cholesterol have not been important risk factors for mortality from coronary heart disease or any other causes among people older than 70.8 Based on this information, there is no reason to believe cholesterol is a risk factor in patients older than 75, and cholesterol testing should be abandoned.9 Lowering cholesterol levels may reduce the risk of coronary heart disease, but do individuals then live longer? Researchers from the University of Arkansas in Little Rock performed a Medline search encompassing research published between 1966 and 1995. They found that reduced circulating blood cholesterol levels drop the coronary heart disease incidence but do not necessarily decrease mortality rates. Unfortunately, lowering cholesterol also appears to increase the risk for cancer, stroke and, oddly enough, coronary heart disease when certain medications are used. "The benefits may not outweigh the risks in all patients with elevated cholesterol," says researcher K.L. Geurian of the Medline study.10 Despite this evidence, the National Cholesterol Education Program wants to put 30 percent of Americans on cholesterol-lowering dietary and lifestyle programs and about 7 percent of the adult population on cholesterol-lowering drugs.11 Fortunately, most American physicians, apparently wary of the side effects caused by anticholesterol drugs and other undesirable outcomes associated with cholesterol reduction, are not likely to follow published guidelines for cholesterol control. One study shows that only 44 percent of primary care physicians would start a middle-age man with a total cholesterol of 276 mg/dL on cholesterol-lowering drugs.12 Raise HDL Instead The NIH recommendation to emphasize HDL levels is tardy given that it was known as early as 1985 that low HDL is the single strongest predictor of coronary heart disease.16 Researchers suggest HDL cholesterol is critical, particularly if the total cholesterol is less than 180 mg/dL. Total cholesterol is more predictive at levels greater than 250 mg/dL. Low HDL in the absence of elevated LDL is correlated with an increased risk of coronary heart disease.17 When the level of LDL cholesterol exceeds that of HDL cholesterol by more than five times, this ratio is a better predictor of a heart attack than total cholesterol, LDL or HDL cholesterol alone.18 The risk for coronary heart disease is 2.5 times greater when HDL cholesterol levels fall below 35 mg/dL.19 Current discussions regarding blood vessel disease now go beyond cholesterol and encompass other factors such as homocysteine, an undesirable blood protein that increases the risk of strokes and heart attacks. The antidote to homocysteine buildup is folic acid, with vitamins B6 and B12 playing secondary roles. [For more information, see related article on page 500.] Supplementary betaine may lower homocysteine by another metabolic pathway in a smaller group of individuals who are resistant to B-vitamin therapy.20 Hydrogenated fats, also called trans fats, that raise LDL cholesterol are also considered risk factors for blood vessel disease. Oxidation, or hardening, of LDL cholesterol on blood vessel walls is also a recognized factor. Inside the lining of blood vessels, a type of white blood cell called a monocyte converts into a lipid-ingesting macrophage cell, which, along with smooth muscle cells, accumulates lipids and becomes what is called a foam cell. As these lipid-laden cells accumulate inside blood vessel walls, they are visually observed as fatty streaks. Oxidized LDL cholesterol comprises most of the lipids attributed to these plaques, and using antioxidants such as vitamin E may prevent this buildup. Viruses, radiation or other inflammatory reactions may also hasten cholesterol plaque development.21 Antioxidants such as vitamin E, selenium, bioflavonoids and carotenoids all protect against cholesterol oxidation.22 Newly published data indicate that what once were considered normal triglyceride levels may increase coronary heart disease risk. Current guidelines published by the National Cholesterol Education Program call for triglyceride levels to be less than 200, but a research team from the University of Maryland in Baltimore now says the maximum triglyceride level should be half that amount. Triglyceride levels greater than 100 have been found to increase the risk for a new heart attack by 50 percent. Triglyceride levels can be reduced with exercise, low-saturated-fat diets, and foods and supplements rich in omega-3 fatty acids, such as flaxseed and fish oil.23 Optimizing HDL Cholesterol Low-fat diet: A recently published study suggests that severe dietary fat restriction doesn't appear to offer much more benefit than moderate dietary fat reduction with regard to protection from heart disease.  A moderate reduction in fat intake (from 36 to 28 percent of daily calories) has been found to produce maximum LDL reduction, but further fat restriction did not show any significant additional benefit. In fact, a very-low-fat diet resulted in increased triglycerides and reduced HDL levels, both of which are cardiovascular risk predictors.24 Monosaturated oils: While low-fat diets may undesirably reduce HDL levels, incorporating olive and avocado oils into the diet significantly reduces the HDL-lowering effect.25 This confirms the Mediterranean diet's benefits. Dietary cholesterol: Despite efforts to warn consumers away from cholesterol-rich foods such as eggs, there is little evidence that dietary sources of cholesterol have any significant effect on circulating blood fats.26 The liver appears to down-regulate its synthesis of cholesterol as more cholesterol is consumed in the diet. Young men who ate up to 14 eggs a week (one egg contains about 250 mg of cholesterol) did not experience a significant increase in cholesterol levels.27 Egg phobia appears to be unfounded. Soy: Researchers performed a meta- analysis of 38 controlled clinical trials and determined that soy consumption significantly reduces total cholesterol, LDL and triglycerides but only slightly raises HDL.28 Still, the LDL reduction improved the LDL-to-HDL cholesterol ratio.25 Traditional fermented soy products such as miso and tempeh have been shown to contain greater concentrations of genistein, one isoflavone active ingredient, compared with soybeans, soy nuts, soy sprouts, tofu, soy flour or soy milk.29 However, soy isoflavones can lower plasma cholesterol by elevating thyroid hormone levels. Regular soy users should be aware of its potential goitrogenic properties. At a concentration ranging from 1 to 10 micromoles in blood plasma, soy isoflavones may cause undesirable symptoms such as hair loss and fatigue, which are characteristic of thyroid disorders.30 Exercise, diet and weight loss: Probably the most natural and reliable way to elevate HDL cholesterol is through exercise and diet. In a small study of 32 people, exercise alone did not cause significant improvements in weight or cholesterol levels, but when subjects combined exercise with a low-saturated-fat diet, they were able to reduce total cholesterol and triglycerides.31 In another study, 42 obese males followed the American Heart Association (AHA) diet, and their total and LDL cholesterol as well as triglyceride levels dropped along with HDL levels (HDL by 17 percent). When the AHA diet was accompanied by weight loss, there were additional drops in cholesterol profiles but a desirable 15 percent increase in HDL.32 A recent study of 180 postmenopausal women and 197 middle-aged men revealed that only when aerobic exercise was added to the daily regimen did a low-fat diet produce a significant drop in LDL cholesterol (14.5 mg/dL in women, 20.0 mg/dL in men).33 Antioxidants: An LDL cholesterol particle contains about 1,200 molecules of polyunsaturated fatty acids that are vulnerable to oxidation but are protected by about five to 14 molecules of fat-soluble antioxidants such as Co-Q10, vitamins C and E and carotenoids.34 Since LDL carries cholesterol into the arterial wall and HDL carries it out, oxidation of HDL interferes with this reverse cholesterol transport. Fortunately, antioxidant vitamins protect against oxidation.35 So, for example, high plasma levels of vitamin C help maintain high HDL levels.36 Vitamin C also spares vitamin E from being oxidized in LDL cholesterol.37 Thus, antioxidant combinations may be more beneficial than single supplements. A study of young adults shows that intake of vitamins A, C, E and beta-carotene from food supplements, along with other lifestyle modifications such as exercise and smoking cessation, is associated with higher HDL cholesterol levels.38 Furthermore, high circulating antioxidant levels may produce health benefits that cholesterol-lowering drugs have been unable to provide, such as reduced mortality. Although circulating levels of antioxidants in middle-aged men in 16 European countries were found to vary by a factor of six, the vitamin E levels accounted for 62 percent of the future mortality predictability.39 Garlic: A recent study examined the combined effect of garlic and omega-3 fatty acids on cholesterol. Garlic powder that provides 3,0004,000 mcg allicin appeared to lower total cholesterol by about 9 to 12 percent, which is comparable to the effect of cholesterol-lowering drugs. Another recent and widely publicized study involving only 25 patients using 10 mg/day of garlic oil did not confirm the previously reported cholesterol-lowering properties of garlic.40 However, it is not the first time garlic has failed to reduce circulating blood fats. Despite a few reports that garlic is ineffective at lowering cholesterol, an analysis of all published studies suggests that garlic is an effective cholesterol-lowering agent.41 In a notable study, 8,000 Germans with high cholesterol were placed on a low-fat diet and experienced a mild reduction in blood fats. But the addition of garlic to the diet reduced cholesterol four times more than diet alone.42 In another study, healthy volunteers were given fresh juice squeezed from 50 g of garlic just prior to eating 100 g of butter, and there was no increase in serum cholesterol.43 Garlic and omega-3: Omega-3 fatty acids are effective at lowering triglycerides; however, omega-3 can raise the LDL cholesterol count. The LDL increase vanishes when omega-3 fats are combined with garlic. Garlic and omega-3 fish oils appear to work better in tandem than they do independently when it comes to optimizing the LDL-to-HDL ratio.44 Niacin: Doses of 1,500 mg/day are effective at reducing total cholesterol, LDL cholesterol and triglycerides while increasing HDL. The main drawbacks of high doses of niacin are flushing, palpitations and worsening of diabetes, gout, hepatitis and peptic ulcers.45 Doses greater than 1,500 mg/day may also result in ocular side effects (dry eyes, retinal swelling and loss of eyelashes) and should be avoided.46 Minerals: A magnesium deficiency increases LDL cholesterol buildup on blood vessel walls. Oral magnesium supplementation in the range of 411548 mg/day improves HDL levels.47 Low copper levels, which can be induced by taking more than 25 mg/day of zinc, may increase triglycerides and reduce HDL levels.48 Zinc/copper ratios should be monitored. The current science on cholesterol suggests that low-fat diets may be counterproductive, that plunging cholesterol levels may trade blood vessel disease for an array of mood and nervous disorders, and that it is best to raise HDL "good" cholesterol--because elevating this blood fat doesn't just reduce the risk of a heart attack, it reduces mortality rates. That's the bottom line when it comes to cholesterol control. References 1. Cadeddu G, Fioravanti P. Relationship between cholesterol levels and depression in the elderly. Minerva Med 1995;86:251--6. 2. Davidson KW, et al. Increases in depression after cholesterol-lowering drug treatment. Behav Med 1996;22:82--4. 3. Zureik M, Courbon D. Serum cholesterol concentration and death from suicide in men: Paris prospective study I. Brit Med J 1996; 313:649--51. 4. Wells AS, et al. Alterations in mood after changing to a low-fat diet. Br J Nut 1998;79:23--30. 5. Chang AK, Barrett-Conner E. Low plasma cholesterol predicts an increased risk of lung cancer in elderly women. Prev Med 1995; 24:557--62. 6. Behar S, et al. Low cholesterol is associated with high total mortality in patients with coronary heart disease. Eur Heart J 1997; 18:52--9. 7. Grant MD, et al. Declining cholesterol and mortality in a sample of older nursing home residents. J Am Ger Soc 1996;44:31--6. 8. Krumholz HM, Seeman TE. Lack of association between cholesterol and coronary heart disease mortality and morbidity and all-cause mortality in persons older than 70 years. J Am Med Assoc 1994; 272:1335--40. 9. Garber AM. Cholesterol screening should be targeted. Am J Med 1997;102:26--30. 10. Geurian KL. The cholesterol controversy. Ann Pharmacother 1996 May;30(5):495--500. 11. Gaziano JM, Hebert PR. Cholesterol reduction: weighing the benefits and risks. Ann Int Med 1996;124:914--8. 12. Hyman DJ, et al. Cholesterol treatment practices of primary care physicians. Pub Health Rep 1992;107:441--8. 13. Kaltenbach M. Is elevated cholesterol the cause of arteriosclerosis? Vericherungsmedizin 1995;47:112--6. 14. Yulsman T. An NIH panel reaches consensus on HDL screening for healthy Americans. Medical World News 1992 Mar:27--31. 15. Eiserich JP, et al. Dietary antioxidants and cigarette smoke-induced biomolecular damage: a complex interaction. Am J Clin Nut 1995;62:1490S--1500S. 16. Baker LL, Criqui MH. High- and low-density lipoprotein cholesterol: correlates in an older population. Prev Med 1985;14:155--64. 17. Gil VF, et al. Importance of HDL cholesterol determination in the evaluation of coronary risk in clinical practice. Aten Primaria 1995;16:254--60. 18. Kimata S, Hosoda S. An index predicting coronary heart disease: LDL/HDL x 5. Jpn Heart J 1997;38:1--9. 19. Corti MC, et al. HDL cholesterol predicts coronary heart disease mortality in older persons. J Am Med Assoc 1995;274:539--44. 20. Dudman NP, et al. Human homocysteine catabolism: three major pathways and their relevance to development of arterial occlusive disease. J Nutr 1996;126:1295S--1300S. 21. Hajjar DP, Nicholson AC. Atherosclerosis, American 22. Kwiterovich PO Jr. The effect of dietary fats, antioxidants, and pro-oxidants on blood lipids, lipoproteins, and atherosclerosis. J Am Diet Assoc 1997;97:S31--S41. 23. Miller M, et al. Normal triglyceride levels and coronary artery disease events: the Baltimore coronary observational long-term study. J Am Coll Cardiol 1998;31:1252--7. 24. Knopp RH, Walden CE. Long-term cholesterol-lowering effects of four fat-restricted diets in hypercholesterolemic and combined hyperlipidemic men. JAMA 1997;278:1509--15. 25. O'Dea K. A low-fat diet supplemented with monosaturated fat results in less HDL-C lowering than a very-low-fat diet. J Am Diet Assoc 1997;97:151--6. 26. McNamara DJ. Dietary cholesterol and the optimal diet for reducing risk of atherosclerosis. Can J Cardiol 1995;11:123G--126G. 27. Vorster HH, et al. Egg intake does not change plasma lipoprotein and coagulation profiles. Am J Clin Nut 1992;55:400--10. 28. Anderson JW, et al. Meta analysis of the effects of soy protein intake on serum lipids. New Eng J Med 1995;333:276--82. 29. Fukutake M, et al. Quantification of genistein and geinstin in soybeans and soybean products. Food Chem Toxicol 1996; 34:457--61. 30. Divi LD, et al. Anti-thyroid isoflavones from soybean. Biochem Pharmacol 1997;54:1087--96. 31. Ribiero JP, et al. The effectiveness of a low lipid diet and exercise in the management of coronary artery disease. Am Heart J 1984; 108:1183--9. 32. Dengel JL, et al. Effect of an American Heart Association diet, with or without weight loss, on lipids in obese middle-aged and older men. Am J Clin Nut 1995;62:715--21. 33. Stefanick ML, et al. Effects of diet and exercise in men and postmenopausal women with low levels of HDL cholesterol and high levels of LDL cholesterol. N Eng J Med 1998;339:12--20. 34. Dieber-Rotheneder M, et al. Effects of antioxidants on oxidative modification of LDL. In: Gotto, AM Jr, Smith LC, editors. Drugs affecting lipid metabolism. New York: Elsevier Science Publishers; 1991. p 287--91. 35. Rifici VA, Khachadurian AK. Effects of dietary vitamin C and E supplementation on the copper mediated oxidation of HDL and on HDL mediated cholesterol efflux. Atherosclerosis 1996; 127:19--26. 36. Hallfrisch J, et al. High plasma vitamin C associated with high plasma HDL and HDL2 cholesterol. Am J Clin Nut 1994; 60:100--5. 37. Retsky KL, Frei B. Vitamin C prevents metal ion-dependent initiation and propagation of lipid peroxidation in human low-density lipoprotein. Biochem Biophys Acta 1995;1257:279--87. 38. Slattery ML, et al. Dietary antioxidants and plasma lipids: the cardia study. J Am Coll Nut 1995;14:635--42. 39. Gey KF, Puska P. Inverse correlation between plasma vitamin E and mortality from ischemic heart disease in cross-cultural epidemiology. Am J Clin Nut 1991;53:325S--34S. 40. Berthold HK, et al. Effect of garlic oil preparation on serum lipoproteins and cholesterol metabolism. JAMA 1998; 279:1900--2. 41. Neil HA, et al. Garlic powder in the treatment of moderate hyperlipidemia: a controlled trial and meta-analysis. J Royal Coll Physicians London 1996;30:329--34. 42. Rassoul F, et al. Total and HDL cholesterol screening in the town of Leipzig. Eur J Clin Res 1992;3A:9. 43. Bordia A, Bansal HC. Essential oil of garlic in prevention of atherosclerosis. Lancet 1973;2:1491--2. 44. Adler AJ, Holub BJ. Effect of garlic and fish-oil supplementation on serum lipid and lipoprotein concentrations in hypercholesterolemic men. Am J Clin Nut 1997;65:445--50. 45. Crouse JR Jr. New developments in the use of niacin for treatment of hyperlipidemia: new considerations in the use of an old drug. Coronary Heart Dis 1996;7:321--6. 46. Fraunfelder FW, Fraunfelder FT. Adverse ocular effects associated with niacin therapy. Brit J Oph 1995;79:54--6. 47. Itoh K, et al. The effects of high oral magnesium supplementation on blood pressure, serum lipids and related variables in apparently healthy Japanese subjects. Br J Nut 1997;78:737--50. 48. McClain CJ, Stuart MA. Zinc metabolism in the elderly. In: Morley JE, editor. Geriatric Nutrition. New York: Raven Press; 1990. p 161--9. |
|
|
|
| terms of service penton privacy policy feedback | ||
|
CopyrightŠ 2001, Penton Media, Inc. |


We love getting to know and spend time with each and every one of our brides. Nothing makes us happier than hearing about your special day and knowing that we had a part in creating it. Here are some of our brides sharing their experiences with House of Bedi Design.
Heather P.
I had a very specific idea in mind for my dress, and I was worried that it would only live there and not be found anywhere. Working with House of Bedi design was amazing because they brought my dream dress to life.
Ameila A.
Thank you so much for making my wedding perfect! I got so many compliments on my dress - I just wish I could wear it all the time!
Brittny H.
I can't wait to work with you someday for my daughter's wedding!


Dear Users,
Thanks for using UCI Schedule Assistant. You have successfully login into the UCI Schedule Assistant. You may find and review the entire projects on our UCI-Schedule-Assistant github repository.
Several points to know about this application:
- First of all, this is a non profitable open source github project and it won't charge you anything in any means.
- This project is issued under the MIT LICENSE. You may review the details of the license on our github repository.
- Security Issue: This program will ask you to enter your gmail account and password. That piece of information is used solely for the purpose of contacting and connecting with the GMAIL SMTP server: smtp.gmail.com. This program will not distribute,store,use the account name and password you enter in any other ways and any other means. The security protocol used to establish the connection is either TLS(by default) or SSL. If you are still skeptical, you may review the code on our github repository to see all the details.
- The developers (specifically all the JiJiU33C43I Developers) are NOT at all LIABLE for ANY CLAIMS, DAMAGES, OR OTHER LIABILITY produced throughout the use of this software. You, the users, MUST be aware of the risks of using the software and MAY need to take the risks if there is any. This is legally licensed under the MIT LICENSE.
Once again, thanks for using our app and hope you like it.
Sincerely,
JiJiU33C43I Developers


Navigation in SwiftUI
This week I want to talk about Navigation in SwiftUI. SwiftUI provides a declarative way of implementing navigation in your apps. Today we will cover different navigation flows available in SwiftUI like Master-Detail and Presenting Modals.
Master-Detail flow
Assume that you are working on app which shows a list of some items and you want to move to details screen as soon as the user selects any item. For this type of navigation, SwiftUI provides NavigationView and NavigationLink components. Let’s check how we can use them.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct MasterView: View {
private let messages = [
"Hello", "How are you?"
]
var body: some View {
NavigationView {
List(messages, id: \.self) { message in
NavigationLink(destination: DetailsView(message: message)) {
Text(message)
}
}.navigationBarTitle("Messages")
}
}
}
1
2
3
4
5
6
7
8
9
10
struct DetailsView: View {
let message: String
var body: some View {
VStack {
Text(message)
.font(.largeTitle)
}
}
}
Here we have a list of messages, to make navigation possible we embed our List into a NavigationView, it adds standard NavigationBar on top of our List. We can also set text as a title of NavigationBar by adding navigationBarTitle modifier to a List. Please make sure that you add the navigationBarTitle modifier to a List component, and not to a NavigationView because multiple views can share same NavigationView and every view can have a different title.
Hidden gem: You can embed two views into NavigationView to achieve Split on iPadOS and MacOS
Next, we embed List rows into NavigationLink, while creating NavigationLink, we have to provide a destination view. SwiftUI presents a destination view when the user presses the List row. By wrapping List row into a NavigationLink, SwiftUI adds trailing arrow to the view which indicates that there is a details screen next to the view. And this is where the real power of declarative programming comes. List row starts appearing in another way only by embedding it into NavigationLink. To learn more about environment-based appearance in SwiftUI, you can check out “Building forms with SwiftUI” post.
Modals
Let’s change our navigation a little bit. Instead of showing our view as a child in the navigation hierarchy, I want to present it as a modal using new iOS 13 cart interface. To make it possible, all we need to do is embedding List row into PresentationLink instead of NavigationLink.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct MasterView: View {
private let messages = [
"Hello", "How are you?"
]
var body: some View {
NavigationView {
List(messages, id: \.self) { message in
PresentationLink(destination: DetailsView(message: message)) {
Text(message)
}
}.navigationBarTitle("Messages")
}
}
}
Now, the user gets an excellent card interface which we have in iOS 13. You can dismiss it by swiping down. But what if you want to add a button which dismisses the modal? To do that we have to use Environment Property Wrapper.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct DetailsView: View {
@Environment(\.isPresented) var isPresented
let message: String
var body: some View {
VStack {
Text(message)
.font(.largeTitle)
Button("Close") {
self.isPresented?.value = false
}
}
}
}
isPresented is an Environment binding to whether self is part of a hierarchy that is currently being presented. In other words, SwiftUI uses this binding to show/hide presented views. By setting the value of this binding to false, SwiftUI dismisses the modal. To learn more about Property Wrappers provided by SwiftUI and Environment values, you can check my “Understanding Property Wrappers in SwiftUI” post.
Conclusion
As you can see, there are no manual calls to push or present any views. All you need to do is wrapping your view into NavigationLink or PresentationLink, and as soon as the user presses it, SwiftUI moves it to next destination, and this is the really excellent declarative approach.


G
- topology typeT
- tuple typepublic class ConnectorStream<G extends Topology,T> extends AbstractTStream<G,T>
| Modifier | Constructor and Description |
|---|---|
protected |
ConnectorStream(G topology,
Connector<T> connector) |
| Modifier and Type | Method and Description |
|---|---|
TStream<T> |
alias(java.lang.String alias)
Set an alias for the stream.
|
protected <N extends Pipe<T,U>,U> |
connectPipe(N pipeOp) |
protected <U> ConnectorStream<G,U> |
derived(Connector<U> connector) |
<U> TStream<U> |
fanin(FanIn<T,U> fanin,
java.util.List<TStream<T>> others)
Declare a stream that contains the output of the specified
FanIn oplet applied to this stream and others . |
TStream<T> |
filter(Predicate<T> predicate)
Declare a new stream that filters tuples from this stream.
|
<U> TStream<U> |
flatMap(Function<T,java.lang.Iterable<U>> mapper)
Declare a new stream that maps tuples from this stream into one or
more (or zero) tuples of a different type
U . |
java.lang.String |
getAlias()
Returns the stream's alias if any.
|
java.util.Set<java.lang.String> |
getTags()
Returns the set of tags associated with this stream.
|
protected Graph |
graph() |
<J,U,K> TStream<J> |
join(Function<T,K> keyer,
TWindow<U,K> twindow,
BiFunction<T,java.util.List<U>,J> joiner)
Join this stream with a partitioned window of type
U with key type K . |
<J,U,K> TStream<J> |
joinLast(Function<T,K> keyer,
TStream<U> lastStream,
Function<U,K> lastStreamKeyer,
BiFunction<T,U,J> joiner)
Join this stream with the last tuple seen on a stream of type
U
with partitioning. |
<K> TWindow<T,K> |
last(int count,
Function<T,K> keyFunction)
Declare a partitioned window that continually represents the last
count
tuples on this stream for each partition. |
<K> TWindow<T,K> |
last(long time,
java.util.concurrent.TimeUnit unit,
Function<T,K> keyFunction)
Declare a partitioned window that continually represents the last
time seconds of
tuples on this stream for each partition. |
<U> TStream<U> |
map(Function<T,U> mapper)
Declare a new stream that maps (or transforms) each tuple from this stream into one
(or zero) tuple of a different type
U . |
TStream<T> |
peek(Consumer<T> peeker)
Declare a stream that contains the same contents as this stream while
peeking at each element using
peeker . |
<U> TStream<U> |
pipe(Pipe<T,U> pipe)
Declare a stream that contains the output of the specified
Pipe
oplet applied to this stream. |
TSink<T> |
sink(Sink<T> oplet)
Sink (terminate) this stream using a oplet.
|
<E extends java.lang.Enum<E>> |
split(java.lang.Class<E> enumClass,
Function<T,E> splitter)
Split a stream's tuples among
enumClass.size streams as specified by
splitter . |
java.util.List<TStream<T>> |
split(int n,
ToIntFunction<T> splitter)
Split a stream's tuples among
n streams as specified by
splitter . |
TStream<T> |
tag(java.lang.String... values)
Adds the specified tags to the stream.
|
java.lang.String |
toString()
Intended only as a debug aid and content is not guaranteed.
|
TStream<T> |
union(java.util.Set<TStream<T>> others)
Declare a stream that will contain all tuples from this stream and all the
streams in
others . |
protected <U> ConnectorStream<G,U> derived(Connector<U> connector)
protected Graph graph()
public TStream<T> filter(Predicate<T> predicate)
TStream
t
on this stream will appear in the returned stream if
filter.test(t)
returns true
. If
filter.test(t)
returns false
then then t
will not
appear in the returned stream.
Examples of filtering out all empty strings from stream s
of type
String
TStream<String> s = ...
TStream<String> filtered = s.filter(t -> !t.isEmpty());
predicate
- Filtering logic to be executed against each tuple.public <U> TStream<U> map(Function<T,U> mapper)
TStream
U
. For each tuple t
on this stream, the returned stream will contain a tuple that is the
result of mapper.apply(t)
when the return is not null
.
If mapper.apply(t)
returns null
then no tuple
is submitted to the returned stream for t
.
Examples of transforming a stream containing numeric values as
String
objects into a stream of Double
values.
// Using lambda expression
TStream<String> strings = ...
TStream<Double> doubles = strings.map(v -> Double.valueOf(v));
// Using method reference
TStream<String> strings = ...
TStream<Double> doubles = strings.map(Double::valueOf);
U
- Tuple type of output streammapper
- Mapping logic to be executed against each tuple.U
mapped from this
stream's tuples.public <U> TStream<U> flatMap(Function<T,java.lang.Iterable<U>> mapper)
TStream
U
. For each tuple
t
on this stream, the returned stream will contain all non-null tuples in
the Iterator<U>
that is the result of mapper.apply(t)
.
Tuples will be added to the returned stream in the order the iterator
returns them.
t
.
Examples of mapping a stream containing lines of text into a stream of words split out from each line. The order of the words in the stream will match the order of the words in the lines.
TStream<String> lines = ...
TStream<String> words = lines.flatMap(
line -> Arrays.asList(line.split(" ")));
U
- Type of mapped input tuples.mapper
- Mapper logic to be executed against each tuple.U
mapped and flattened from this
stream's tuples.public java.util.List<TStream<T>> split(int n, ToIntFunction<T> splitter)
TStream
n
streams as specified by
splitter
.
For each tuple on the stream, splitter.applyAsInt(tuple)
is
called. The return value r
determines the destination stream:
if r < 0 the tuple is discarded else it is sent to the stream at position (r % n) in the returned array.
Each split TStream
is exposed by the API. The user has full
control over the each stream's processing pipeline. Each stream's
pipeline must be declared explicitly. Each stream can have different
processing pipelines.
An N-way split()
is logically equivalent to a collection of N
filter()
invocations, each with a predicate to select the tuples
for its stream. split()
is more efficient. Each tuple is analyzed
only once by a single splitter
instance to identify the
destination stream. For example, these are logically equivalent:
List<TStream<String>> streams = stream.split(2, tuple -> tuple.length()); TStream<String> stream0 = stream.filter(tuple -> (tuple.length() % 2) == 0); TStream<String> stream1 = stream.filter(tuple -> (tuple.length() % 2) == 1);
Example of splitting a stream of log records by their level attribute:
TStream<LogRecord> lrs = ...
List<<TStream<LogRecord>> splits = lrr.split(3, lr -> {
if (SEVERE.equals(lr.getLevel()))
return 0;
else if (WARNING.equals(lr.getLevel()))
return 1;
else
return 2;
});
splits.get(0). ... // SEVERE log record processing pipeline
splits.get(1). ... // WARNING log record processing pipeline
splits.get(2). ... // "other" log record processing pipeline
n
- the number of output streamssplitter
- the splitter functionn
streamspublic <E extends java.lang.Enum<E>> java.util.EnumMap<E,TStream<T>> split(java.lang.Class<E> enumClass, Function<T,E> splitter)
TStream
enumClass.size
streams as specified by
splitter
.E
- Enum typeenumClass
- enum data to splitsplitter
- the splitter functionpublic TStream<T> peek(Consumer<T> peeker)
TStream
peeker
. t
on this stream, peeker.accept(t)
will be
called.peeker
- Function to be called for each tuple.this
public TSink<T> sink(Sink<T> oplet)
TStream
TStream.sink(Consumer)
with a full life-cycle of
the oplet as well as easy access to
runtime services
.oplet
- Oplet processes each tuple without producing output.public <U> TStream<U> pipe(Pipe<T,U> pipe)
TStream
Pipe
oplet applied to this stream.U
- Tuple type of the returned stream.pipe
- The Pipe
oplet.public <U> TStream<U> fanin(FanIn<T,U> fanin, java.util.List<TStream<T>> others)
TStream
FanIn
oplet applied to this stream and others
.U
- Tuple type of the returned streams.fanin
- The FanIn
oplet.others
- The other input streams.
Must not be empty or contain duplicates or this
TStream.union(Set)
,
TStream.pipe(Pipe)
,
TStream.sink(Sink)
public <K> TWindow<T,K> last(int count, Function<T,K> keyFunction)
TStream
count
tuples on this stream for each partition. Each partition independently maintains the last
count
tuples for each key seen on this stream.
If no tuples have been seen on the stream for a key then the corresponding partition will be empty.
keyFunction
.
For each tuple on the stream keyFunction.apply(tuple)
is called
and the returned value is the tuple's key. For any two tuples ta,tb
in a partition
keyFunction.apply(ta).equals(keyFunction.apply(tb))
is true.
equals()
and hashCode()
correctly.
To create a window partitioned using the tuple as the key use identity()
as the key function.
To create an unpartitioned window use a key function that returns a constant,
by convention unpartitioned()
is recommended.
K
- Key type.count
- Number of tuples to maintain in each partition.keyFunction
- Function that defines the key for each tuple.count
tuples for each partition.public <K> TWindow<T,K> last(long time, java.util.concurrent.TimeUnit unit, Function<T,K> keyFunction)
TStream
time
seconds of
tuples on this stream for each partition. If no tuples have been
seen on the stream for a key in the last time
seconds then the partition will be empty.
Each partition independently maintains the last
count
tuples for each key seen on this stream.
keyFunction
.
For each tuple on the stream keyFunction.apply(tuple)
is called
and the returned value is the tuple's key. For any two tuples ta,tb
in a partition
keyFunction.apply(ta).equals(keyFunction.apply(tb))
is true.
equals()
and hashCode()
correctly.
To create a window partitioned using the tuple as the key use identity()
as the key function.
To create an unpartitioned window use a key function that returns a constant,
by convention unpartitioned()
is recommended.
K
- Key type.time
- Time to retain a tuple in a partition.unit
- Unit for time
.keyFunction
- Function that defines the key for each tuple.count
tuple.public <J,U,K> TStream<J> join(Function<T,K> keyer, TWindow<U,K> twindow, BiFunction<T,java.util.List<U>,J> joiner)
TStream
U
with key type K
.
For each tuple on this stream, it is joined with the contents of window
for the key keyer.apply(tuple)
. Each tuple is
passed into joiner
and the return value is submitted to the
returned stream. If call returns null then no tuple is submitted.J
- Tuple type of result streamU
- Tuple type of window to join withK
- Key typekeyer
- Key function for this stream to match the window's key.twindow
- Keyed window to join this stream with.joiner
- Join function.window
.public <J,U,K> TStream<J> joinLast(Function<T,K> keyer, TStream<U> lastStream, Function<U,K> lastStreamKeyer, BiFunction<T,U,J> joiner)
TStream
U
with partitioning.
For each tuple on this
stream, it is joined with the last tuple seen on lastStream
with a matching key (of type K
).
t
on this stream will match the last tuple
u
on lastStream
if
keyer.apply(t).equals(lastStreamKeyer.apply(u))
is true.
equals
and
hashCode()
.
Each tuple is
passed into joiner
and the return value is submitted to the
returned stream. If call returns null then no tuple is submitted.
J
- Tuple type of result streamU
- Tuple type of stream to join withK
- Key typekeyer
- Key function for this streamlastStream
- Stream to join with.lastStreamKeyer
- Key function for lastStream
joiner
- Join function.lastStream
.public TStream<T> union(java.util.Set<TStream<T>> others)
TStream
others
. A stream cannot be unioned with itself, in
this case the union will only contain tuples from this stream once. If
others
is empty or only contains this
then this
is returned.others
- Stream to union with this stream.this
and others
.public TStream<T> tag(java.lang.String... values)
TStream
values
- Tag values.public java.util.Set<java.lang.String> getTags()
TStream
public TStream<T> alias(java.lang.String alias)
TStream
The alias must be unique within the topology. The alias may be used in various contexts:
alias
- an alias for the stream.ControlService
public java.lang.String getAlias()
TStream
public java.lang.String toString()
toString
in class java.lang.Object
Copyright © 2016 The Apache Software Foundation. All Rights Reserved - 73539fa-20161119-2136


--- layout: page title: Hi, I'm Jichang LI subtitle: Master student in Computer Vision use-site-title: true ---
I am Jichang LI(李继昌 in Chinese), currently a 3rd-year master student in Computer Science at South China University of Technology, advised by Prof. Zhiwen YU and Associate Prof. Si Wu. My current research interests are Computer Vision and Deep Learning, mainly including Semi-supervised Learning and Few-shot Learning.
Before that, I obtained my Bachelor Degree of Engineering at South China Normal University in 2017.
One paper is accepted by IEEE Transaction on Image Processing(TIP2019).
One paper is accepted by IEEE/ACM Transactions on Audio, Speech, and Language Processing(TASLP-2019).
Two papers are accepted by CVPR 2019.


Please return home and try again.


Many thanks to Peter Christian for alerting me to this magnificent letter.
A full transcript can be read at Letters Of Note, which looks like a blog well worth keeping an eye on.


How many people will live in the apartment ?
Smoker? Yes No
Any pets? Cat(s) Dog(s) Specify: No Pets
Size of apartment: Studio 3½ 4½ 5½ More than 5½
Price Range/month: <$500 $500-$700 $700-$1000 No Price Limit
Would be nice to have Fireplace Laundromat in building Indoor Parking Outdoor Parking Balcony


--- date: 2010-11-24T00:00:00Z published: true title: Top 48 ads that would never be allowed today url: /2010/11/24/top-48-ads-that-would-never-be-allowed-today/ ---
Oh my.


This course aims to build a bridge between the Data 8 curriculum and hands-on data science. Intended for students who have just completed Data 8 and plan to study fields that involve working with data, the course will translate knowledge of the datascience package to the pandas library, and give students supplemental technical skills to support their own data analysis projects. Data science methods such as finding robust datasets, dealing with missing values, and assessing the validity of the analysis will be taught. By the end of the semester, students will have carried out a start-to-finish analysis of data of their choosing. Upon completing this course, students will be able to use industry-grade data science tools to analyze data for projects in all fields.
Students should have completed Data 8 before taking this course. It is also recommended, but not required, that students have taken a linear algebra course such as Math 54.
Lectures will consist of two segments: a pre-recorded set of youtube videos to be watched at home with a follow-along Jupyter Notebook, and a live, synchronous lecture. They are designed to work hand in hand and one is not a substitute for the other. Live lectures will be during the scheduled class time of Mondays from 5:00 - 6:00 PM PST, and will occasionally feature guest lecturers from the data science industry. They will NOT be recorded and attendance will be taken.
The class will be run as much like a seminar as a regular class. Your participation is necessary to make this work. We will be expecting you to discuss during class, participate on Piazza, and come to Office Hours. You are required to check in with the course staff team for at least once during the semester. We also need your feedback on our materials in order to improve them!
If you are unable to attend class, email the instructor to have your absence excused 24 hours before the lecture. Students are allowed 2 excused absences during the semester. If you are unable to attend lecture without notice, please post privately on Piazza so that the course staff are aware.
Participation will be graded based on attendance and overall interaction with other students and course staff. As mentioned above, attendance will be taken during lecture, and students will be awarded full participation credit for attending at least 7/12 of live lectures and checking in with a course staff member before and after the final project. This will be worth 20% of the final course grade.
Labs are designed to help you gain familiarity and practice the new concepts taught in lecture. They are to be completed individually and then reviewed together as a group. There will be three labs throughout the course, and each will be worth 10% of the final course grade.
The final project will be a culmination of everything taught in the class, going through the entire data science lifecycle. It will be worth 50% of the final course grade.
For more information on the project, check out the prompt & rubric and example datasets & projects under the Resources tab of the navigation bar.
Grades will be assigned using the following weighted components:
| Activity | Grade |
|---|---|
| Participation | 20% |
| Labs | 30% |
| Project | 50% |
This class will only be offered for P/NP credit. To earn a passing grade, students must earn at least 70% of the total points offered.


Solo developer or serious team?
Get one user, and automated staging and production deployment workflows—for free!
Add more admins, operators and developers and get support from Convox engineers—from $500/mo.
---
title: Convox Pricing
layout: base
---
Plans & Pricing
Convox is a trusted name in cloud infrastructure.
We never charge for infrastructure services. Our mission is to reduce those bills.
Get one user, and automated staging and production deployment workflows—for free!
Add more admins, operators and developers and get support from Convox engineers—from $500/mo.
Convox is always available as free open-source software.
Join our contributors at github.com/convox/rack today.


--- title: Plymouth Hospitals NHS Trust layout: gb ---
| Resource | Fetch | Format | Columns | Data | Valid | Entries | Oldest | Latest | Total (GBP) | |
|---|---|---|---|---|---|---|---|---|---|---|
| Spend over £25,000 in Plymouth Hospitals NHS Trust | ||||||||||
| A link to the Plymouth Hospitals NHS Trust website | ✔ | ✘ | ✘ | ✘ | ✘ | 0 |
| Resource/Stage | Error | |
| A link to the Plymouth Hospitals NHS Trust website | ||
| extract | HTML file detected |


InitMovie()
语法
Result = InitMovie()概要
Initialize the movie environment for later use. You must call this function before any other functions in this library.
参数
无.
返回值
Returns nonzero if initialization was successful. If zero is returned then the movie library cannot be used.
备注
This function tries to open DirectX (v3.0 with NT4.0 compatibility or v7.0 else); if it fails then DirectX is either not available or is too old be used.
参阅
LoadMovie()
已支持操作系统
所有


We take pride in our work, and it shows. Every time you visit from us, we guarantee that it will be an experience worth having. from a basic wash to luxury spa treatments – We provide all kind of services and that too Ayurvedic. Our extensive range of hair care packages are designed to treat hair problems, while nourishing them from the root. Our hair staff tames unruly tresses and styles hair in a way that works well with its natural flow.Now, You can say goodbye to bad hair days!
Ensure your skin is detoxified, refreshed and most importantly pampered! Our natural skin care treatments provide you with healthy, glowing skin. For radiant, revitalised and refreshed skin naturally, Choose our Ayurvedic Skin Treatments.
Tiring week at work? De-stress and rejuvenate at Pisces! A pedicure is so much more than a cut and file for your nails. The fish therapy releases the tension from your feet, relieving stress from your entire body. Trust us to help you detox and rejuvenate!


This chapter describes commands that configure the EAP (Extensible Authentication Protocol). Mpd supports natively only the EAP-Type MD5; other EAP-Types may be used in conjunction with a RADIUS server. All of these commands apply to the currently active link.
set eap accept option ...
set eap deny option ...
set eap enable option ...
set eap disable option ...
set eap yes option ...
set eap no option ...
These commands configure various EAP options. Most options are bi-directional in that they can be independently enabled and disabled in each direction.
md5
EAP-Type MD5. It's the same as CHAP-MD5, except that is framed inside EAP packets.
The default is disable and accept.
radius-proxy
Causes Mpd to proxy all EAP requests to the RADIUS server.
The default is disable.


--- title: "Alpine.js Handbook Knowledge Base" ---
The Alpine.js Handbook is designed to be useful to both experienced JavaScript/Web developers and to developers whose expertise lies outside of that.
Through the book, you'll be faced with real-world scenarios and an explanation of different possible solutions. By the end of the Alpine.js Handbook, you'll be able to confidently build and deploy complex applications with Alpine.js.
Hugo is an Alpine.js contributor, author of The Jest Handbook and co-author of "Professional JavaScript" with Packt. He runs the Alpine.js Weekly newsletter and Code with Hugo website helping over 130k developers every month. He holds an MEng in Mathematical Computation from University College London (UCL) and has used JavaScript extensively to create scalable and performant platforms at companies such as Canon and Elsevier.