Mailing List Archive: 49091 messages
  • Home
  • Script library
  • AltME Archive
  • Mailing list
  • Articles Index
  • Site search
 

X.10 serial port control from Rebol

 [1/2] from: parki::whatevernot::com at: 6-Sep-2003 18:11


My first 'real' Rebol script - this is a certainly unfinished script which controls my X.10 lights in my house - with simple commands I can turn on and off lights on the X.10 circuit in my house. I have looked at this in C (working with a developer to port to Mac OS X) as well as Perl and Java, and can certainly say that the Rebol approach, which built in serial:// protocol, makes life a snap. This also contains some playing around with the notions of Singleton idiom and data hiding. It's likely overkill. I am posting it here as people might be interested, and I welcome any feedback on style and whatevernot. Cheers, parki... --- x8 snip REBOL [ Title: "CM11A X.10 serial controller" Author: "Brian Parkinson" Email: [parki--whatevernot--com] Date: 6-Sep-2003 File: %cm11a.r Version: 0.1.0 Purpose: "Support simple access to an X.10 controller to turn lights on and off." History: [ 0.1.0 [6-Sep-2003 "First cut."] ] Comment: { Example usage: x: cm11a ; Initialize the interface to the CM11A controller x/ex 3 'on ; Turn on light 3 in default house code #"A" x/houseCode #"B" ; Set the house code to #"B" x/ex 4 'off ; Turn off light 4 in house code #"B" x/deinit ; Deinitialize the controller when all are done with it Should be mentioned that at of this version (0.1.0) I have only really tested that lights on and off work - more features can be added. This is based around a singleton idiom that is somewhat nifty - data hiding is implemented. To make this work on a different machine, you will almost certainly have to change the set to system/ports/serial in the cm11aInit function. } ] ; The use construct allows us to hide a number of variable and functions and in so ; doing provide some nice data hiding ; use [ privateCtor instance_ cm11aInit cm11aSend serialPort_ HOUSE_CODES DEVICE_CODES FUNCTION_CODES ] [ ; The singleton instance which ensures that we only create one object ; instance_: false ; Define our constants based on the CM11A protocol ; HOUSE_CODES: [ 96 224 32 160 16 144 80 208 112 240 48 176 0 128 64 192 ] DEVICE_CODES: [ 6 14 2 10 1 9 5 13 7 15 3 11 0 8 4 12 ] FUNCTION_CODES: [ all-units-off all-lights-on on off dim bright all-lights-off extended hail-request hail-acknowledge preset-dim1 preset-dim2 extended-data status-on status-off status-request ] ; The function which returns the global singleton instance of the CM11A controller ; cm11a: func [] [ either instance_ [ instance_ ] [ instance_: privateCtor ] ] ; Private func inits serial port connection to the CM11A controller - called from ; the private constructor so we only init the connection when the cm11a object ; is constructed, and not at script load time ; cm11aInit: func [] [ ; /dev/cu.USA19QI14P1.1 is the serial device for X.10 on my machine ; See /dev on the machine for the name of this port ; system/ports/serial: [ cu.USA19QI14P1.1 ] ; The serial port contained by the singleton object ; Open the connection to the serial port serialPort_: open/binary/direct/no-wait serial://port1/4800/8/none/1 ; Turn off handshaking (required by CM11A controller) serialPort_/rts-cts: false update serialPort_ ] ; Private function to send a value to the CM11A controller ; Need to add in exception handling if fall through loop ; cm11aSend: func [ value /local reply ] [ insert serialPort_ value repeat count 100 [ wait 0.1 reply: copy serialPort_ if not reply = #{} [ return 0 ] ] -1 ] ; Private constructor which only gets called once upon first access ; privateCtor: func [] [ make object! [ ; The house code that this object will control - defaults to A but ; can be set with the houseCode function ; houseCode_: #"A" ; Initialize the CM11A controller ; cm11aInit ; When you are done with the serial port, you should call deinit to close ; the connection ; deinit: func [] [ close serialPort_ ] ; Set the current house code used by the controller ; houseCode: func [ houseCode [ char! ] "The house code character: A,...,P" ] [ houseCode_: houseCode ] ; The main entry point - ensure that the controller has been init'ed ; Example use: "ex 3 'on" ; ex: function [ deviceCode [ number! ] "Device code number: 1,..,16" funcWord [word!] "The function to run: 'on, 'off, etc" ] [ houseVal deviceVal functionVal addrHex funcHex ] [ houseVal: pick HOUSE_CODES (to-integer houseCode_) - (to-integer #"@") deviceVal: pick DEVICE_CODES deviceCode functionVal: (index? find FUNCTION_CODES funcWord) - 1 addrHex: debase/base (to-string copy/part at to-hex (1024 + houseVal + deviceVal) 5 4) 16 funcHex: debase/base (to-string copy/part at to-hex (1536 + houseVal + functionVal) 5 4) 16 ; Make the calls to the serial port ; cm11aSend addrHex cm11aSend #{00} cm11aSend funcHex cm11aSend #{00} ] ] ] ]

 [2/2] from: greggirwin:mindspring at: 7-Sep-2003 11:42


Hi Parki, Thanks for posting that. Good stuff! Makes me want to go out and find an X10 controller so I can play with things again. :) BP> I have looked BP> at this in C (working with a developer to port to Mac OS X) as well as BP> Perl and Java, and can certainly say that the Rebol approach, which BP> built in serial:// protocol, makes life a snap. Good to know. We all have different experience, so I always like hearing where REBOL works well and where it doesn't. BP> This also contains some playing around with the notions of Singleton BP> idiom and data hiding. It's likely overkill. BP> I am posting it here as people might be interested, and I welcome any BP> feedback on style and whatevernot. It could be overkill. One of the things about REBOL, or the approach *I* tend to use with REBOL, as compared to some other languages is that I don't try to exert too much control over how modules may be used. That is, REBOL is very flexible, so if I build a module that is locked down really tight in how it can be used, that makes more work for me, and more work for the person who wants to use it differently. It's sort of an accountability thing, and it's a fine line. On one hand, I don't want to write things so knowledge of internals is required for people to use it (some VID styles suffer from this IMO), but on the other hand, in REBOL people can find a way around almost any blockades you try to put up to protect them. In this case, I'd weigh the pros and cons of the singleton stuff versus just using an object. What happens if they create more than one? Would that ever be useful? Stuff like that. Now, I can make some comments on your code (already very good) which are related more to REBOL style and idioms than correct code. First, your posting line-wrapped heavily here. REBOL is free form, so you can often adjust the formatting to suit shorter lines if you want. Second, great comments! Don't forget the COMMENT function for long comments that may wrap. Also, glad to see you used doc attributes on your ex function parameters! Where you use: repeat count 100 [ You're not using 'count, so you could use LOOP instead. loop 100 [ Where you use: if not reply = #{} [ return 0 ] Consider: if not empty? reply [ return 0 ] EMPTY? is often clearer, as to the intent of what you're checking, than comparing equality against specific values. Just as FUNC is a shortcut for FUNCTION, DOES and HAS are shortcuts for functions with no params or those with only local var decls, respectively. Don't be afraid of one-liner functions. deinit: func [] [ close serialPort_ ] Could be: deinit: does [close serialPort_] Standard REBOL style doesn't use CamelCase for words. It uses all lowercase, with hyphens, and hyphens are used in place of underscores in most cases. Also, all-caps aren't used for constants, maybe because, well, there really *aren't* any constants in REBOL. :) ex: function [ deviceCode [ number! ] "Device code number: 1,..,16" funcWord [word!] "The function to run: 'on, 'off, etc" ] [ houseVal deviceVal functionVal addrHex funcHex ... Vs: ex: function [ device-code [ number! ] "Device code number: 1,..,16" func-word [word!] "The function to run: 'on, 'off, etc" ] [ house-val device-val function-val addr-hex func-hex ... You've made good use of data, but consider how else you might express your data, and how that would affect the code. e.g. house-codes: [ #"A" 96 #"B" 224 #"C" 32 #"D" 160 #"E" 16 #"F" 144 #"G" 80 #"H" 208 #"I" 112 #"J" 240 #"K" 48 #"L" 176 #"M" 0 #"N" 128 #"O" 64 #"P" 192 ] ... house-code_: #"A" ... house-val: house-codes/:house-code_ You could even validate house codes valid-house-codes: charset [#"A" - #"P"] ; or, with the above house-codes definition ;valid-house-codes: charset extract house-codes 2 valid-house-code?: func [house-code [char!]] [ found? find valid-house-codes house-code ] You could also use a function for your data conversion lines on the address and function values. You could even define it locally in EX. I'm sure you would have done this in your next cut anyway as it's not really a REBOL specific thing. I do this, sometimes, so I can take the function and play with it out of context--often in the console. X10ify: func [val] [ debase/base (to-string copy/part at to-hex val 5 4) 16 ;?? will this work here instead? ;at debase/base to-hex val 16 3 ] ... addr-hex: X10ify 1024 + house-val + device-val func-hex: X10ify 1536 + house-val + function-val Another handy idiom is to write your function, or a wrapper for it, to allow multiple values to be iterated over, rather than calling the function manually with one argument, or arg list, multiple times. cm11aSend addrHex cm11aSend #{00} cm11aSend funcHex cm11aSend #{00} Versus: send-cmds: func [cmds [block!] [ foreach cmd cmds [cm11aSend cmd] ] send-cmds reduce [addrHex #{00} funcHex #{00}] If the #{00} command has a special meaning, or is used after every command, you could give it a special name or just send it silently in 'cm11aSend, maybe based on a refinement. SEND-CMDS could even treat the block as a simple dialect so you could include WAIT commands and such. It's good stuff in any case! I hope my comments give you some thoughts and ideas. There's certainly nothing wrong with your code as it stands now. -- Gregg