I think I'm pretty much ready to release this into the wild.
I've completely rewritten this code to be FULLY threaded and bidirectional.
This Beta version has a PLCBUS class which is being tested now. (all thumbs up so far!)
The PLCBUS class contains all the specific protocol conversions. It can be re-coded for support of another protocol while still maintaining threading and bidirectional support.
The bulk of the code is in #373 Private Method Listing and can be Copy/Pasted in from here: http://rafb.net/p/f80h1p14.html
The rest of the code is so small, I'm posting it here.
#351 Process IDLE
errorHandler()
#350 Process Incoming Data
### #350
while(true)
@buff = conn_.RecvDelimited(0x02.chr,100) #stx
if(@buff.length() == 0)
break
end
@buff2 = conn_.Recv(1,10) #Length of message
@buff +=@buff2
@length = @buff2[0]
@buff += conn_.Recv(@length + 1, 100)
debugin(@buff)
dataIn(@buff)
end
# this routine needs to be handled better.
# possibly with regular expressions.
#355 Process Initialize
#########################
# Written by Dan Damron #
#########################
initDevices()
#384 Process Receive Command for Child
### #484 Process Receive Command for Child
log('DCE Command Received')
dataIn(cmd)
Some of you sharper characters out there MAY notice that I treat
Process Receive Command for Child AND Process Incoming Data the same.
They BOTH call datain()
Maybe that might pique your interest a bit.. Maybe not..
For all the guts and glory, (and about 200 hours hard work) look at #373 http://rafb.net/p/f80h1p14.html
P.S.
The PLCBUS class has a TON of instructions on how to use it.
Regards,
Dan
Well done, mate!!!
Howdy,
I took a look at your code more as a curiosity of how ruby is being used in LinuxMCE than in actually using the feature. I thought I'd give you a few comments in a friendly code review style.
* you might consider using rdoc style comments. That will make publishing the API easier.
* naming choices are good, long names
* mixed constant naming, sometimes all uppercase, sometimes mixed (camel) case (ex: NotReady = 1). Convention is constant object references should be all uppercase with underscores. Camelcase is used for class and module names.
* a few methods are a little high on the line count, but mostly because of all the good logging calls.
* Your log() methods have me a little confused. Why all the putc calls? Wouldn't something like the following accomplish the same?
def log(line)
File.open("/var/log/pluto/" + device_.devid_.to_s + "_Generic_Serial_Device.log", "a") do |logfile|
logfile.puts '(***)' + line.to_s.gsub('<',' ')
end
end
* in several debug methods you use this construct:
work = label + ":"
for c in 1..text.length
work += padhex("%X" %text[c-1]) + ' '
end
string concatenations are usually costly. It might be better to append to an array then join to a string:
array = []
for c in 1..text.length
array << padhex("%X" %text[c-1])
end
work = label + ':' + array.join(' ')
but then again, these are in debug methods so performance is probably not an issue. :-)
* in gsdcommand() I'm not sure what the return value will be when the case statement hit's one of the when statements that don't have a block. For example, what will be returned when @gsdcommand is 'REPORT ONLY ON PULSE'? You might need an extra return statement at the bottom of the method.
* Sometimes the really long cases can be replaced with a hash lookup. For example, in gsdcommand, you may want to play with something like:
FUNCTIONS = {'ALL UNIT OFF' => Proc.new {@gsdparams[10] = '0';return 48},
'ALL LTS ON' => Proc.new {@gsdparams[10] = '1';return 48},
#...
}
def gsdcommand #returns DCE EVENT!! and compiles params
p = FUNCTIONS[CommandFunctions[@gsdcommand]]
p.call unless p.nil?
return 0
end
But then again, the big case statement is probably more readable. :-)
Overall, very nice looking code. :-)
Have fun,
Roy
Quote from: royw on January 27, 2008, 12:29:34 PM
Howdy,
I took a look at your code more as a curiosity of how ruby is being used in LinuxMCE than in actually using the feature. I thought I'd give you a few comments in a friendly code review style.
* you might consider using rdoc style comments. That will make publishing the API easier.
* naming choices are good, long names
* mixed constant naming, sometimes all uppercase, sometimes mixed (camel) case (ex: NotReady = 1). Convention is constant object references should be all uppercase with underscores. Camelcase is used for class and module names.
* a few methods are a little high on the line count, but mostly because of all the good logging calls.
* Your log() methods have me a little confused. Why all the putc calls? Wouldn't something like the following accomplish the same?
def log(line)
File.open("/var/log/pluto/" + device_.devid_.to_s + "_Generic_Serial_Device.log", "a") do |logfile|
logfile.puts '(***)' + line.to_s.gsub('<',' ')
end
end
* in several debug methods you use this construct:
work = label + ":"
for c in 1..text.length
work += padhex("%X" %text[c-1]) + ' '
end
string concatenations are usually costly. It might be better to append to an array then join to a string:
array = []
for c in 1..text.length
array << padhex("%X" %text[c-1])
end
work = label + ':' + array.join(' ')
but then again, these are in debug methods so performance is probably not an issue. :-)
* in gsdcommand() I'm not sure what the return value will be when the case statement hit's one of the when statements that don't have a block. For example, what will be returned when @gsdcommand is 'REPORT ONLY ON PULSE'? You might need an extra return statement at the bottom of the method.
* Sometimes the really long cases can be replaced with a hash lookup. For example, in gsdcommand, you may want to play with something like:
FUNCTIONS = {'ALL UNIT OFF' => Proc.new {@gsdparams[10] = '0';return 48},
'ALL LTS ON' => Proc.new {@gsdparams[10] = '1';return 48},
#...
}
def gsdcommand #returns DCE EVENT!! and compiles params
p = FUNCTIONS[CommandFunctions[@gsdcommand]]
p.call unless p.nil?
return 0
end
But then again, the big case statement is probably more readable. :-)
Overall, very nice looking code. :-)
Have fun,
Roy
Roy,
Hello and Thanks for the review!
This is my SECOND attempt in ERb and I'm still learning the language..
RDOC style Comments: Don't know that yet. will look into it
(I have a book on it, but haven't read that chapter yet)
Naming Conventions:
I wasn't aware of that: Ruby's naming conventions are based on the first character..
Yes, I know a few methods are a bit high on the line count ;)
half of those lines are logging and comments. (which can be taken out)
regarding my log():
ok, I originally wrote the log() routine when I started ruby..
I ran into problems displaying XML in the log.
Then, later, I ran into problems displaying Object.inspect in the log..
out of much frustration, that is what I ended up with, and as long as it worked (ie displayed what I needed it to) I left it.
ERb (at least in linuxmce) doesn't like displaying <object>. I tries to parse it.
Even in the code window, when you want to make a String='<?xml version="1.0"?>', when you save it, you get String=''
The debug calls are just them. debug. they can be removed.
That's an interesting way to use a hash! I like that!
As far as that long case statement.. Yeah, I should add a return at the end.
Most of those functions aren't needed.. but I copied them in from the specs, so they won't hurt.
I've been using Ruby for about 2-3 months now..
Nice to see some feedback!
Regards,
Dan
Ok, after a few hours of testing, with Hari (in Germany) and myself (in Canada)..
Added a $delayBetweenTransmit in the initDevices() to allow for a processing delay(of the interface)
Then, after even more testing, we realised a key problem:
PLCBUS (or at least this interface) isn't threaded! ie, I HAVE to wait for a response to a command.
AAAAAAAAAAAAAAAAAAAAAAARRRRRRRRRRRRRRRRGGGGGGGGGGGGGGHHHHHHHHHHHHHH!!!!
Ah well.. So, instead of throwing it all away, I've added a threaded flag to the command object.
If this flag is NOT set, it will force the routines to wait for a response to that command.
Interestingly enough, it wasn't that hard to do.. and as a side benifit, commands going the other way are not impeded.. ie, switching on a light switch will make it to DCE (and hence the floorplan) whether threading is suspended or not.
In other words, the Threading Suspension is only 1 way: DCE to GSD.
Still have a few things to iron out, but I will post new code soon.
Best Regards,
Dan
Quote from: ddamron on January 27, 2008, 03:44:47 PM
regarding my log():
ok, I originally wrote the log() routine when I started ruby..
I ran into problems displaying XML in the log.
Then, later, I ran into problems displaying Object.inspect in the log..
out of much frustration, that is what I ended up with, and as long as it worked (ie displayed what I needed it to) I left it.
ERb (at least in linuxmce) doesn't like displaying <object>. I tries to parse it.
Even in the code window, when you want to make a String='<?xml version="1.0"?>', when you save it, you get String=''
The debug calls are just them. debug. they can be removed.
You might want to try something like:
require 'cgi'
puts CGI.escapeHTML(line)
that will escape html characters ('<' => '<', '&' => '&',...)
Have fun,
Roy
Hi,
first great thanks for great work. I'm really interested in this one. I have something similar, but done as external driver in Perl, so I'm thinking about moving it to Ruby - but my Ruby knowledge is nearly nonexistent, so don't know if this migration would be easy.
- does anyone know of any source how to learn Ruby and your code efficiently ?
- do you have an option to respond to events also - since you use threads, does this mean you can start something that is continuosly running (I'm interested in this, cause I also try to implement other things for my home automation system - integration of scenes, speech announcements, etc..) ?
- under what licence do you plan to release the code ?
- what steps are needed to customize code to some other protocols (I need UDP connection to my system) ?
- this is really interesting idea - to have common skeleton on LMCE's side and just change modules for each separate protocol on top of that...
Thanks in advance for any opinions ,
regards,
Bulek.
Quote from: bulek on January 28, 2008, 10:42:35 AM
Hi,
first great thanks for great work. I'm really interested in this one. I have something similar, but done as external driver in Perl, so I'm thinking about moving it to Ruby - but my Ruby knowledge is nearly nonexistent, so don't know if this migration would be easy.
- does anyone know of any source how to learn Ruby and your code efficiently ?
- do you have an option to respond to events also - since you use threads, does this mean you can start something that is continuosly running (I'm interested in this, cause I also try to implement other things for my home automation system - integration of scenes, speech announcements, etc..) ?
- under what licence do you plan to release the code ?
- what steps are needed to customize code to some other protocols (I need UDP connection to my system) ?
- this is really interesting idea - to have common skeleton on LMCE's side and just change modules for each separate protocol on top of that...
Thanks in advance for any opinions ,
regards,
Bulek.
Bulek,
Thanks for the response! (I love hearing feedback)
Regarding your questions: (I'll try to answer them)
I just googled Ruby... learned by trial and error.. Because Ruby is an interpreted language, you can always test in irb.
I'm not sure I know what you mean by responding to events...
My code does NOT use threads (as they exist..) I basically created a 'kernel' that allows each 'command' object to be handled seperately from other 'command' objects.
That said, if you want to respond to a DCE Event, not a problem. an Event is a message, just like a command, just like gsd data coming in, just like gsd data going out..
I pass ALL data IN/OUT (to and from DCE and GSD) to my 'protocolObject' class..
It's up the that class to either ACCEPT the data or REJECT the data..
Take a closer look at the remarks in the PLCBUS class... you'll see what I mean.
Under what license do I plan to release the code!?? I firmly believe in GPL.
What steps are needed to customize the code: Create a new protocol object class, and implement the required methods. That's it.
It is designed to be DROP IN replaceable.
HTH,
Dan
Quote from: bulek on January 28, 2008, 10:42:35 AM
- does anyone know of any source how to learn Ruby and your code efficiently ?
The definitive and very readable book is "Programming Ruby, The Pragmatic Programmers' Guide" by Dave Thomas. The older release of the book is available online at http://www.ruby-doc.org/docs/ProgrammingRuby/ (http://www.ruby-doc.org/docs/ProgrammingRuby/)
The ruby language site is http://www.ruby-lang.org/en/ (http://www.ruby-lang.org/en/)
A nice quick reference (once you learn the language) is: http://www.zenspider.com/Languages/Ruby/QuickRef.html (http://www.zenspider.com/Languages/Ruby/QuickRef.html)
And finally the API docs are at: http://www.ruby-doc.org/ (http://www.ruby-doc.org/)
If you decide to look into Rails, then start with Dave Thomas' "Agile Web Development with Rails". Rails itself is an excellent example on how to use Ruby.
Have fun,
Roy
I develop on Perl more than 6 years. Recently I created a GSD interface with Ruby. So, I can say that Ruby is very close to Perl. Except Ruby is typified language. IMHO the porting application from Perl to Ruby shouldn't be a big deal. Except you use a lot of Perl modules :)
I've started a wiki for Threaded Ruby.
http://wiki.linuxmce.org/index.php/Category:ThreadedRuby
Anyone, please feel free to format it better...
Regards,
Dan