diff --git a/fhem/FHEM/10_FRM.pm b/fhem/FHEM/10_FRM.pm new file mode 100755 index 000000000..93ea096bb --- /dev/null +++ b/fhem/FHEM/10_FRM.pm @@ -0,0 +1,448 @@ +############################################## +package main; + +use strict; +use warnings; +use Device::Firmata::Constants qw/ :all /; +use Device::Firmata::IO; +use Device::Firmata::Protocol; +use Device::Firmata::Platform; + +sub FRM_Set($@); +sub FRM_Attr(@); +sub Log($$); + +##################################### +sub FRM_Initialize($) { + my ($hash) = @_; + + require "$main::attr{global}{modpath}/FHEM/DevIo.pm"; + + # Provider + $hash->{Clients} = + ":FRM_IN:FRM_OUT:FRM_AD:FRM_PWM:FRM_I2C:"; + $hash->{ReadyFn} = "FRM_Ready"; + $hash->{ReadFn} = "FRM_Read"; + + # Consumer + $hash->{DefFn} = "FRM_Define"; + $hash->{UndefFn} = "FRM_Undef"; + $hash->{GetFn} = "FRM_Get"; + $hash->{SetFn} = "FRM_Set"; + $hash->{AttrFn} = "FRM_Attr"; + + $hash->{AttrList} = "model:nano dummy:1,0 loglevel:0,1,2,3,4,5 sampling-interval i2c-config $main::readingFnAttributes"; +} + +##################################### +sub FRM_Define($$) { + my ( $hash, $def ) = @_; + my @a = split( "[ \t][ \t]*", $def ); + my $po; + + DevIo_CloseDev($hash); + + my $name = $a[0]; + my $dev = $a[2]; + + if ( $dev eq "none" ) { + Log 1, "FRM device is none, commands will be echoed only"; + $main::attr{$name}{dummy} = 1; + return undef; + } + $hash->{DeviceName} = $dev; + my $ret = DevIo_OpenDev($hash, 0, "FRM_DoInit"); + main::readingsSingleUpdate($hash,"state","initialized", 1); + return $ret; +} + +##################################### +sub FRM_Undef($) { + my $hash = @_; + FRM_forall_clients($hash,\&FRM_Client_Unassign,undef); + DevIo_CloseDev($hash); + my $device = $hash->{FirmataDevice}; + if (defined $device) { + if (defined $device->{io}) { + delete $hash->{FirmataDevice}->{io}->{handle} if defined $hash->{FirmataDevice}->{io}->{handle}; + delete $hash->{FirmataDevice}->{io}; + } + delete $device->{protocol} if defined $device->{protocol}; + delete $hash->{FirmataDevice}; + } + return undef; +} + +##################################### +sub FRM_Set($@) { + my ( $hash, @a ) = @_; + my $u1 = "Usage: set reset/reinit\n"; + + return $u1 if ( int(@a) < 2 ); + my $name = $hash->{DeviceName}; + + if ( $a[1] eq 'reset' ) { + DevIo_CloseDev($hash); + my $ret = DevIo_OpenDev($hash, 0, "FRM_DoInit"); + return $ret; + } elsif ( $a[1] eq 'reinit' ) { + FRM_forall_clients($hash,\&FRM_Init_Client,undef); + } else { + return "Unknown argument $a[1], supported arguments are 'reset', 'reinit'"; + } + return undef; +} + +##################################### +sub FRM_Get($@) { + my ( $hash, @a ) = @_; + return "\"get FRM\" needs only one parameter" if ( @a != 2 ); + shift @a; + my $spec = shift @a; + if ( $spec eq "firmware" ) { + if (defined $hash->{FirmataDevice}) { + return $hash->{FirmataDevice}->{metadata}->{firmware}; + } else { + return "not connected to FirmataDevice"; + } + } elsif ( $spec eq "version" ) { + if (defined $hash->{FirmataDevice}) { + return $hash->{FirmataDevice}->{metadata}->{firmware_version}; + } else { + return "not connected to FirmataDevice"; + } + } +} + +##################################### +# called from the global loop, when the select for hash->{FD} reports data +sub FRM_Read($) { + my ( $hash ) = @_; + my $device = $hash->{FirmataDevice} or return; + $device->poll(); +} + +sub FRM_Ready($) { + + my ($hash) = @_; + return DevIo_OpenDev($hash, 1, "FRM_DoInit") if($hash->{READINGS}{state} eq "disconnected"); +} + +sub FRM_Attr(@) { + my ($command,$name,$attribute,$value) = @_; + if ($command eq "set") { + $main::attr{$name}{$attribute}=$value; + if ($attribute eq "sampling-interval" + or $attribute eq "i2c-config" ) { + FRM_apply_attribute($main::defs{$name},$attribute); + } + } +} + +sub FRM_apply_attribute { + my ($hash,$attribute) = @_; + my $firmata = $hash->{FirmataDevice}; + my $name = $hash->{NAME}; + if (defined $firmata) { + if ($attribute eq "sampling-interval") { + $firmata->sampling_interval(main::AttrVal($name,$attribute,"1000")); + } elsif ($attribute eq "i2c-config") { + my $i2cattr = main::AttrVal($name,$attribute,undef); + if (defined $i2cattr) { + my @a = split(" ", $i2cattr); + my $i2cpins = $firmata->{metadata}{i2c_pins}; + if (defined $i2cpins and scalar @$i2cpins) { + foreach my $i2cpin (@$i2cpins) { + $firmata->pin_mode($i2cpin,PIN_I2C); + } + $firmata->i2c_config(@a); + $firmata->observe_i2c(\&FRM_i2c_observer,$hash); + } else { + Log 1,"Error, arduino doesn't support I2C"; + } + } + } + } +} + +sub FRM_DoInit($) { + + my ($hash) = @_; + + my $name = $hash->{NAME}; + $hash->{loglevel} = main::GetLogLevel($name); + + my $firmata_io = Firmata_IO->new($hash); + my $device = Device::Firmata::Platform->attach($firmata_io) or return 1; + + $hash->{FirmataDevice} = $device; + $device->observe_string(\&FRM_string_observer,$hash); + + my $found; # we cannot call $device->probe() here, as it doesn't select bevore read, so it would likely cause IODev to close the connection on the first attempt to read from empty stream + do { + $device->system_reset(); + $device->firmware_version_query(); + for (my $i=0;$i<50;$i++) { + my ($rout, $rin) = ('', ''); + vec($rin, $hash->{FD}, 1) = 1; + my $nfound = select($rout=$rin, undef, undef, 0.1); + my $mfound = vec($rout, $hash->{FD}, 1); + if($mfound) { + $device->poll(); + if ($device->{metadata}{firmware} && $device->{metadata}{firmware_version}){ + $device->{protocol}->{protocol_version} = $device->{metadata}{firmware_version}; + $main::defs{$name}{firmware} = $device->{metadata}{firmware}; + $main::defs{$name}{firmware_version} = $device->{metadata}{firmware_version}; + $device->analog_mapping_query(); + $device->capability_query(); + for (my $j=0;$j<100;$j++) { + my ($rout, $rin) = ('', ''); + vec($rin, $hash->{FD}, 1) = 1; + my $nfound = select($rout=$rin, undef, undef, 0.1); + my $mfound = vec($rout, $hash->{FD}, 1); + if ($mfound) { + $device->poll(); + if (($device->{metadata}{analog_mappings}) and ($device->{metadata}{capabilities})) { + my $inputpins = $device->{metadata}{input_pins}; + $main::defs{$name}{input_pins} = join(",", sort{$a<=>$b}(@$inputpins)); + my $outputpins = $device->{metadata}{output_pins}; + $main::defs{$name}{output_pins} = join(",", sort{$a<=>$b}(@$outputpins)); + my $analogpins = $device->{metadata}{analog_pins}; + $main::defs{$name}{analog_pins} = join(",", sort{$a<=>$b}(@$analogpins)); + my $i2cpins = $device->{metadata}{i2c_pins}; + $main::defs{$name}{i2c_pins} = join(",", sort{$a<=>$b}(@$i2cpins)); + my $onewirepins = $device->{metadata}{onewire_pins}; + $main::defs{$name}{onewire_pins} = join(",", sort{$a<=>$b}(@$onewirepins)); + $found = 1; + last; + } + } else { + select (undef,undef,undef,0.1); + } + } + $found = 1; + last; + } + } else { + select (undef,undef,undef,0.1); + } + } + } while (!$found); + + FRM_apply_attribute($hash,"sampling-interval"); + FRM_apply_attribute($hash,"i2c-config"); + FRM_forall_clients($hash,\&FRM_Init_Client,undef); + + return undef; +} + +sub +FRM_forall_clients($$$) +{ + my ($hash,$fn,$args) = @_; + foreach my $d ( sort keys %main::defs ) { + if ( defined( $main::defs{$d} ) + && defined( $main::defs{$d}{IODev} ) + && $main::defs{$d}{IODev} == $hash ) { + &$fn($main::defs{$d},$args); + } + } + return undef; +} + +sub +FRM_Init_Client($$) { + my ($hash,$args) = @_; + $hash->{loglevel} = main::GetLogLevel($hash->{NAME}); + main::CallFn($hash->{NAME},"InitFn",$hash,$args); +} + +sub +FRM_Init_Pin_Client($$) { + my ($hash,$args) = @_; + my $u = "wrong syntax: define FRM_XXX pin"; + return $u if(int(@$args) < 3); + $hash->{PIN} = @$args[2]; +} + +sub +FRM_Client_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + main::readingsSingleUpdate($hash,"state","defined",0); + + main::AssignIoPort($hash); + FRM_Init_Client($hash,\@a); + + return undef; +} + +sub +FRM_Client_Undef($$) +{ + my ($hash, $name) = @_; +} + +sub +FRM_Client_Unassign($) +{ + my ($dev) = @_; + delete $dev->{IODev} if defined $dev->{IODev}; + main::readingsSingleUpdate($dev,"state","defined",0); +} + +package Firmata_IO { + + sub new { + my ($class,$hash) = @_; + return bless { + hash => $hash, + }, $class; + } + + sub data_write { + my ( $self, $buf ) = @_; + main::Log 5, ">".join(",",map{sprintf"%02x",ord$_}split//,$buf); + main::DevIo_SimpleWrite($self->{hash},$buf); + } + + sub data_read { + my ( $self, $bytes ) = @_; + my $string = main::DevIo_SimpleRead($self->{hash}); + if (defined $string ) { + main::Log 5,"<".join(",",map{sprintf"%02x",ord$_}split//,$string); + } + return $string; + } +} + +sub +FRM_i2c_observer +{ + my ($data,$hash) = @_; + main::Log 5,"onI2CMessage address: '".$data->{address}."', register: '".$data->{register}."' data: '".$data->{data}."'"; + FRM_forall_clients($hash,\&FRM_i2c_update_device,$data); +} + +sub FRM_i2c_update_device +{ + my ($hash,$data) = @_; + if (defined $hash->{"i2c-address"} && $hash->{"i2c-address"}==$data->{address}) { + my $replydata = $data->{data}; + my @values = split(" ",main::ReadingsVal($hash->{NAME},"values","")); + splice(@values,$data->{register},@$replydata, @$replydata); + main::readingsBeginUpdate($hash); + main::readingsBulkUpdate($hash,"state","active",0); + main::readingsBulkUpdate($hash,"values",join (" ",@values),1); + main::readingsEndUpdate($hash,undef); + } +} + +sub FRM_string_observer +{ + my ($string,$hash) = @_; + main::Log 4, "received String_data: ".$string; + main::readingsSingleUpdate($hash,"error",$string,1); +} + +1; + +=pod +=begin html + + +

FRM

+ +
+ +=end html +=cut diff --git a/fhem/FHEM/20_FRM_AD.pm b/fhem/FHEM/20_FRM_AD.pm new file mode 100755 index 000000000..80e0f5518 --- /dev/null +++ b/fhem/FHEM/20_FRM_AD.pm @@ -0,0 +1,114 @@ +############################################# +package main; + +use strict; +use warnings; +use Device::Firmata; +use Device::Firmata::Constants qw/ :all /; + +##################################### +sub +FRM_AD_Initialize($) +{ + my ($hash) = @_; + + $hash->{GetFn} = "FRM_AD_Get"; + $hash->{DefFn} = "FRM_Client_Define"; + $hash->{InitFn} = "FRM_AD_Init"; + $hash->{UndefFn} = "FRM_AD_Undef"; + $hash->{AttrFn} = "FRM_Attr"; + + $hash->{AttrList} = "IODev loglevel:0,1,2,3,4,5 $main::readingFnAttributes"; +} + +sub +FRM_AD_Init($$) +{ + my ($hash,$args) = @_; + FRM_Init_Pin_Client($hash,$args); + if (defined $hash->{IODev}) { + my $firmata = $hash->{IODev}->{FirmataDevice}; + if (defined $firmata and defined $hash->{PIN}) { + $firmata->pin_mode($hash->{PIN},PIN_ANALOG); + $firmata->observe_analog($hash->{PIN},\&FRM_AD_observer,$hash); + main::readingsSingleUpdate($hash,"state","initialized",1); + return undef; + } + } + return 1; +} + +sub +FRM_AD_observer +{ + my ($pin,$old,$new,$hash) = @_; + main::Log(6,"onAnalogMessage for pin ".$pin.", old: ".(defined $old ? $old : "--").", new: ".(defined $new ? $new : "--")); + main::readingsSingleUpdate($hash,"state",$new, 1); +} + +sub +FRM_AD_Get($) +{ + my ($hash) = @_; + my $iodev = $hash->{IODev}; + if (defined $iodev and defined $iodev->{FirmataDevice} and defined $iodev->{FD}) { + my $ret = $iodev->{FirmataDevice}->analog_read($hash->{PIN}); + return $ret; + } else { + return $hash->{NAME}." no IODev assigned" if (!defined $iodev); + return $hash->{NAME}.", ".$iodev->{NAME}." is not connected"; + } +} + +sub +FRM_AD_Undef($$) +{ + my ($hash, $name) = @_; +} + +1; + +=pod +=begin html + + +

FRM_AD

+ +
+ +=end html +=cut diff --git a/fhem/FHEM/20_FRM_I2C.pm b/fhem/FHEM/20_FRM_I2C.pm new file mode 100755 index 000000000..c15581543 --- /dev/null +++ b/fhem/FHEM/20_FRM_I2C.pm @@ -0,0 +1,108 @@ +############################################# +package main; + +use strict; +use warnings; +use Device::Firmata; +use Device::Firmata::Constants qw/ :all /; + +##################################### +sub +FRM_I2C_Initialize($) +{ + my ($hash) = @_; + + $hash->{DefFn} = "FRM_Client_Define"; + $hash->{InitFn} = "FRM_I2C_Init"; + $hash->{UndefFn} = "FRM_I2C_Undef"; + $hash->{AttrFn} = "FRM_I2C_Attr"; + + $hash->{AttrList} = "IODev loglevel:0,1,2,3,4,5 $main::readingFnAttributes"; +} + +sub +FRM_I2C_Init($) +{ + my ($hash,$args) = @_; + my $u = "wrong syntax: define FRM_I2C address register numbytes"; + + return $u if(int(@$args) < 5); + + $hash->{"i2c-address"} = @$args[2]; + $hash->{"i2c-register"} = @$args[3]; + $hash->{"i2c-bytestoread"} = @$args[4]; + + if (defined $hash->{IODev}) { + my $firmata = $hash->{IODev}->{FirmataDevice}; + if (defined $firmata) { + $firmata->i2c_read(@$args[2],@$args[3],@$args[4]); + } + return undef; + } + return 1; +} + +sub FRM_I2C_Attr(@) { + my ($command,$name,$attribute,$value) = @_; + my $hash = $main::defs{$name}; + if ($command eq "set") { + $main::attr{$name}{$attribute}=$value; + } +} + +sub +FRM_I2C_Undef($$) +{ + my ($hash, $name) = @_; +} + +1; + +=pod +=begin html + + +

FRM_I2C

+
    + represents an integrated curcuit connected to the i2c-pins of an Arduino + running Firmata
    + Requires a defined FRM-device to work.
    + this FRM-device has to be configures for i2c by setting attr 'i2c-config' on the FRM-device
    + it reads out the ic-internal storage in intervals of 'sampling-interval' as set on the FRM-device

    + + + Define +
      + define <name> FRM_I2C <i2c-address> <register> <bytes-to-read>
      + Specifies the FRM_I2C device.
      +
    • i2c-address is the (device-specific) address of the ic on the i2c-bus
    • +
    • register is the (device-internal) address to start reading bytes from.
    • +
    • bytes-to-read is the number of bytes read from the ic
    • +
    + +
    + + Set
    +
      + N/A
      +
    + + Get
    +
      + N/A
      +

    + + Attributes
    + +
+
+ +=end html +=cut diff --git a/fhem/FHEM/20_FRM_IN.pm b/fhem/FHEM/20_FRM_IN.pm new file mode 100755 index 000000000..f824b3ecc --- /dev/null +++ b/fhem/FHEM/20_FRM_IN.pm @@ -0,0 +1,114 @@ +############################################# +package main; + +use strict; +use warnings; +use Device::Firmata; +use Device::Firmata::Constants qw/ :all /; + +##################################### +sub +FRM_IN_Initialize($) +{ + my ($hash) = @_; + + $hash->{GetFn} = "FRM_IN_Get"; + $hash->{DefFn} = "FRM_Client_Define"; + $hash->{InitFn} = "FRM_IN_Init"; + $hash->{UndefFn} = "FRM_IN_Undef"; + $hash->{AttrFn} = "FRM_Attr"; + + $hash->{AttrList} = "IODev loglevel:0,1,2,3,4,5 $main::readingFnAttributes"; +} + +sub +FRM_IN_Init($$) +{ + my ($hash,$args) = @_; + FRM_Init_Pin_Client($hash,$args); + if (defined $hash->{IODev}) { + my $firmata = $hash->{IODev}->{FirmataDevice}; + if (defined $firmata and defined $hash->{PIN}) { + $firmata->pin_mode($hash->{PIN},PIN_INPUT); + $firmata->observe_digital($hash->{PIN},\&FRM_IN_observer,$hash); + main::readingsSingleUpdate($hash,"state","initialized",1); + return undef; + } + } + return 1; +} + +sub +FRM_IN_observer +{ + my ($pin,$old,$new,$hash) = @_; + main::Log(6,"onDigitalMessage for pin ".$pin.", old: ".(defined $old ? $old : "--").", new: ".(defined $new ? $new : "--")); + main::readingsSingleUpdate($hash,"state",$new == PIN_HIGH ? "on" : "off", 1); +} + +sub +FRM_IN_Get($) +{ + my ($hash) = @_; + my $iodev = $hash->{IODev}; + if (defined $iodev and defined $iodev->{FirmataDevice} and defined $iodev->{FD}) { + my $ret = $iodev->{FirmataDevice}->digital_read($hash->{PIN}); + return $ret == PIN_HIGH ? "on" : "off"; + } else { + return $hash->{NAME}." no IODev assigned" if (!defined $iodev); + return $hash->{NAME}.", ".$iodev->{NAME}." is not connected"; + } +} + +sub +FRM_IN_Undef($$) +{ + my ($hash, $name) = @_; +} + +1; + +=pod +=begin html + + +

FRM_IN

+
    + represents a pin of an Arduino running Firmata + configured for digital input.
    + The current state of the arduino-pin is stored in reading 'state'. Values are 'on' and 'off'.
    + Requires a defined FRM-device to work.

    + + + Define +
      + define <name> FRM_IN <pin>
      + Specifies the FRM_IN device. +
    + +
    + + Set
    +
      + N/A
      +
    + + Get
    +
      + N/A
      +

    + + Attributes
    + +
+
+ +=end html +=cut diff --git a/fhem/FHEM/20_FRM_OUT.pm b/fhem/FHEM/20_FRM_OUT.pm new file mode 100755 index 000000000..35f716864 --- /dev/null +++ b/fhem/FHEM/20_FRM_OUT.pm @@ -0,0 +1,111 @@ +############################################# +package main; + +use strict; +use warnings; +use Device::Firmata; +use Device::Firmata::Constants qw/ :all /; + +##################################### +sub +FRM_OUT_Initialize($) +{ + my ($hash) = @_; + + $hash->{SetFn} = "FRM_OUT_Set"; + $hash->{DefFn} = "FRM_Client_Define"; + $hash->{InitFn} = "FRM_OUT_Init"; + $hash->{UndefFn} = "FRM_OUT_Undef"; + $hash->{AttrFn} = "FRM_Attr"; + + $hash->{AttrList} = "IODev loglevel:0,1,2,3,4,5 $main::readingFnAttributes"; +} + +sub +FRM_OUT_Init($$) +{ + my ($hash,$args) = @_; + FRM_Init_Pin_Client($hash,$args); + if (defined $hash->{IODev}) { + my $firmata = $hash->{IODev}->{FirmataDevice}; + if (defined $firmata and defined $hash->{PIN}) { + $firmata->pin_mode($hash->{PIN},PIN_OUTPUT); + main::readingsSingleUpdate($hash,"state","initialized",1); + } + } +} + +sub +FRM_OUT_Set($@) +{ + my ($hash, @a) = @_; + my $value; + if ($a[1] eq "on") { + $value=PIN_HIGH; + } elsif ($a[1] eq "off") { + $value=PIN_LOW; + } else { + return "illegal value '".$a[1]."', allowed are 'on' and 'off'"; + } + my $iodev = $hash->{IODev}; + if (defined $iodev and defined $iodev->{FirmataDevice} and defined $iodev->{FD}) { + $iodev->{FirmataDevice}->digital_write($hash->{PIN},$value); + main::readingsSingleUpdate($hash,"state",$a[1], 1); + } else { + return $hash->{NAME}." no IODev assigned" if (!defined $iodev); + return $hash->{NAME}.", ".$iodev->{NAME}." is not connected"; + } + return undef; +} + +sub +FRM_OUT_Undef($$) +{ + my ($hash, $name) = @_; +} + +1; + +=pod +=begin html + + +

FRM_OUT

+
    + represents a pin of an Arduino running Firmata + configured for digital input.
    + Requires a defined FRM-device to work.

    + + + Define +
      + define <name> FRM_OUT <pin>
      + Specifies the FRM_OUT device. +
    + +
    + + Set
    +
      + set <name> on|off

      +
    + + Get
    +
      + N/A +

    + + Attributes
    + +
+
+ +=end html +=cut diff --git a/fhem/FHEM/20_FRM_PWM.pm b/fhem/FHEM/20_FRM_PWM.pm new file mode 100755 index 000000000..bd29f820a --- /dev/null +++ b/fhem/FHEM/20_FRM_PWM.pm @@ -0,0 +1,105 @@ +############################################# +package main; + +use strict; +use warnings; +use Device::Firmata; +use Device::Firmata::Constants qw/ :all /; + +##################################### +sub +FRM_PWM_Initialize($) +{ + my ($hash) = @_; + + $hash->{SetFn} = "FRM_PWM_Set"; + $hash->{DefFn} = "FRM_Client_Define"; + $hash->{InitFn} = "FRM_PWM_Init"; + $hash->{UndefFn} = "FRM_PWM_Undef"; + $hash->{AttrFn} = "FIR_Attr"; + + $hash->{AttrList} = "IODev loglevel:0,1,2,3,4,5 $main::readingFnAttributes"; +} + +sub +FRM_PWM_Init($$) +{ + my ($hash,$args) = @_; + FRM_Init_Pin_Client($hash,$args); + if (defined $hash->{IODev}) { + my $firmata = $hash->{IODev}->{FirmataDevice}; + if (defined $firmata and defined $hash->{PIN}) { + $firmata->pin_mode($hash->{PIN},PIN_PWM); + main::readingsSingleUpdate($hash,"state","initialized",1); + } + } +} + +sub +FRM_PWM_Set($@) +{ + my ($hash, @a) = @_; + my $value = $a[1]; + my $iodev = $hash->{IODev}; + if (defined $iodev and defined $iodev->{FirmataDevice} and defined $iodev->{FD}) { + $iodev->{FirmataDevice}->analog_write($hash->{PIN},$value); + main::readingsSingleUpdate($hash,"state",$a[1], 1); + } else { + return $hash->{NAME}." no IODev assigned" if (!defined $iodev); + return $hash->{NAME}.", ".$iodev->{NAME}." is not connected"; + } + return undef; +} + +sub +FRM_PWM_Undef($$) +{ + my ($hash, $name) = @_; +} + +1; + +=pod +=begin html + + +

FRM_PWM

+
    + represents a pin of an Arduino running Firmata + configured for analog output.
    + The value set will be output by the specified pin as a pulse-width-modulated signal.
    + Requires a defined FRM-device to work.

    + + + Define +
      + define <name> FRM_PWM <pin>
      + Specifies the FRM_PWM device. +
    + +
    + + Set
    +
      + set <name> <value>

      +
    + + Get
    +
      + N/A +

    + + Attributes
    + +
+
+ +=end html +=cut