MODULE_NAME='BaytechRPC' (dev rpcDevice, char rpcDeviceIP[], dev vdev_rpc, dev vdSyslog) (* ****************************************************************************************** Ethernet control of Baytech RPC series remote power control units Code copyright 2005 Ryan P. Wright (http://www.ryanwright.com) VERSION 1.0 LAST MODIFIED: 2/24/2005 The latest version of this code is always available at: http://www.ryanwright.com/automation/ ********************************************************************************************* LICENSE: This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License (included, gpl.txt) for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA02110-1301USA ********************************************************************************************* REQUIREMENTS: This module was written for the Baytech RPC-3 Remote Power Control unit. It should be useful for other Baytech RPC products, however, I have only tested with the RPC-3 (8 outlet unit) and cannot guarantee compatibility with others. Maximum of 12 ports per device (per instance of this module) can be controlled. The command queue is setup to hold roughly 30-40 commands at a time by default. This is about 20-30 more than anyone should ever need. If you're doing something weird and find you need a larger queue, increase the value of "char cmdQueue[200]" under define_variable. ********************************************************************************************* CONFIGURATION: ++ You MUST have syslogmod setup and working. If you are not using it, you are missing out on one of my favorite capabilities of Netlinx. Get it from: http://cvs.sourceforge.net/viewcvs.py/netlinx-modules/NetLinx-Modules/SyslogMod/ If you do not wish to configure syslogmod, you must go through this module and comment out every line containing the term "syslog". You must also remove "dev vdSyslog" from the module parameters at the top of this file and from the define_module section in your main code where this module is called. Note that you do not have to enable syslogging, you can set "loglevel = 0" in the config options below to turn it off, but syslogmod must be configured for this module to compile without the above modifications. ++ Each instance of this module needs the following defined in mainline: DEFINE_DEVICE vdev_BayTechRPC3_1 = 32772:1:0 // virtual device for control / status feedback vdSyslog = 33001:1:0 // virtual device for syslogmod (if using) net_BayTechRPC3_1 = 0:2:0; // local network port for your RPC device DEFINE_VARIABLE volatile char BayTechRPC3_1ip[] = '192.168.1.100'; // IP of your RPC unit DEFINE_LATCHING [vdev_BayTechRPC3_1,1]..[vdev_BayTechRPC3_1,12] DEFINE_START on[vdev_BayTechRPC3_1,99] // Match channel status to RPC's current status DEFINE_MODULE 'BaytechRPC' modBaytechRPC3_1(net_BayTechRPC3_1,BayTechRPC3_1ip,vdev_BayTechRPC3_1,vdSyslog); ++ Configure the options (under DEFINE_CONSTANT, below) to your environment. ++ You must configure your RPC unit to disable command confirmation. This configuration option is located, from the main menu: 3, Configuration; 6, Outlets; 2, Command Confirmation. Make sure your changes are saved (hit enter a few times to get out of the config menu, and you will be prompted). ********************************************************************************************* USAGE: Usage is very straightforward. Your virtual device (in the configuration example above, it is "vdev_BayTechRPC3_1") has control/feedback on the first dozen channels. So, to turn outlet 1 on, you would use "on[vdev_BayTechRPC3_1,1]". Outlet 2 is controlled by turning channel 2 on or off, outlet 3 with channel 3, etc. You just treat it like any other device. Special commands: To refresh status, turn channel 99 on: "on[vdev_BayTechRPC3_1,99]" Using the above command, turning the channel on is only a trigger. The channel will be immediately turned back off and the command will be executed. ********************************************************************************************* HOW IT ALL WORKS: The control code is very straightforward. If a channel event is detected, we hop onto the RPC device and execute that command. We then sit and wait for 10 seconds in case other commands are in the queue; if anything comes in, we start all over again. If not, we disconnect. This is all good and well, but if you reset your Netlinx master, the RPC device doesn't know this. The result is a channel mixup. Say channel 4 was on when you reset the master; channel 4 will now be off on your virtual device when the master comes back up, but still on at the RPC unit. Now when you call an "off" command later, Netlinx thinks the channel is already off and doesn't execute the off code. This little issue is solved by executing a status update (turning channel 99 on the virtual device on) in define_start in mainline. This update code is quite a hack, as I wrote it with a 100+ degree temperature while high on aspirin. Besides that, I don't claim to be the best Netlinx programmer, I just hack the code until it works reliably and leave it at that. It may not be pretty but it works and that's what matters to me. One note, there is no checking of status involved. If your RPC device malfunctions or for whatever reason doesn't receive a command in the middle of processing, the status of your virtual device channels may be incorrect. Also, if for some reason we can't connect to the RPC device, we bail out and turn all channels off. The only way to correct this situation is to bring the device back online and initiate a refresh by turning channel 99 on. ******************************************************************************************* *) #include 'SyslogMod.axi'; // if you are not using SyslogMod, comment this out DEFINE_CONSTANT (* ****************************************************************************************** CONFIGURATION OPTIONS ******************************************************************************************* *) char outlets = 8 // number of outlets on your RPC unit. max = 12. // if you are running multiple RPC units with differing // numbers of outlets, you will need multiple copies of // this module (vs calling the same copy multiple times). // actually this isn't completely true and I could probably // lose this setting alltogether (or auto-detect it), but // this is the first release and right now I really don't care. char loglevel = 1 // set to 0 to disable logging via syslog // set to 1 to enable logging via syslog // set to 2 to enable debugging (highly detailed logging via syslog) (* **************************************************************************************** *) // DO NOT MODIFY THE CODE BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING // (* **************************************************************************************** *) // other constants char Info = 6; // Informational: informational messages DEFINE_VARIABLE sinteger result char resultstring[50] integer rpconline integer rpconlineattempt integer executingCmd integer waitingForOffline integer menuLevel integer updatingStatus char rpcbuffer[1000] char cmdQueue[200] char cmd[10] (* **************************************************************************************** *) define_call 'RpcConnect' { rpconlineattempt = rpconlineattempt + 1 if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', "'Attemping to open socket to IP: ',rpcDeviceIP") } result = ip_client_open (rpcDevice.Port,rpcDeviceIP,23,1) if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', "'Result code = ',itoa(result)") } // spit out an error & clear the queue if we didn't make the connection if (result <> 0) { cmdQueue = "" // clear queue rpconlineattempt = 0 // clear attempt counter call 'RpcCleanup' // cleanup due to error switch (result) { case 1: { resultstring = 'Invalid server address' } case 2: { resultstring = 'Invalid server port' } case 3: { resultstring = 'Invalid value for protocol' } case 4: { resultstring = 'Unable to open communication port with server' } case 6: { resultstring = 'Connection refused' } case 7: { resultstring = 'Connection timed out' } case 8: { resultstring = 'Unknown connection error' } case 9: { resultstring = 'Already closed' } case 10: { resultstring = 'Binding error' } case 11: { resultstring = 'Listening error' } case 14: { resultstring = 'Local port already used' } case 15: { resultstring = 'UDP socket already listening' } case 16: { resultstring = 'Too many open sockets' } default : { resultstring = "'Could not determine; result returned: ',itoa(result)" } } if (loglevel > 0) { syslog(Info, 'BaytechRPCModule', "'Error connecting: ',resultstring") } } // netlinx sometimes returns 0 even if we didn't get online (?) // this causes an infinite loop and will make things very unhappy // so after 3 tries to get online, we're going to give up if (rpconlineattempt > 2) { cmdQueue = "" // clear queue rpconlineattempt = 0 // clear attempt counter call 'RpcCleanup' // cleanup due to error ip_client_close (rpcDevice.port) // close the port, just in case it's really opened if (loglevel > 0) { syslog(Info, 'BaytechRPCModule', 'Error connecting, but Netlinx lied. Runaway cease-fire invoked.') } } } define_call 'RpcCleanup' { if (loglevel > 0) { syslog(Info, 'BaytechRPCModule', 'Turned off all channels due to connection error') } off [vdev_rpc,1] off [vdev_rpc,2] off [vdev_rpc,3] off [vdev_rpc,4] off [vdev_rpc,5] off [vdev_rpc,6] off [vdev_rpc,7] off [vdev_rpc,8] off [vdev_rpc,9] off [vdev_rpc,10] off [vdev_rpc,11] off [vdev_rpc,12] } define_call 'RpcDisconnect' { ip_client_close (rpcDevice.port) if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'Closed connection') } clear_buffer rpcbuffer } define_call 'RpcCommand' (cmd[10]) { // execute outlet control command executingCmd = 1 send_string rpcDevice, "cmd,13" if (loglevel > 0) { syslog(Info, 'BaytechRPCModule', "'Switched "',cmd,'" at ',rpcDeviceIP") } executingCmd = 0 } (* **************************************************************************************** *) DEFINE_START rpconline = 0 rpconlineattempt = 0 executingCmd = 0 waitingForOffline = 0 menuLevel = 0 updatingStatus = 0 create_buffer rpcDevice, rpcbuffer (* **************************************************************************************** *) DEFINE_EVENT channel_event [vdev_rpc, 1] { on: { if (outlets >= 1) { cmdQueue = "cmdQueue,'on 1!'" } } off: { if (outlets >= 1) { cmdQueue = "cmdQueue,'off 1!'" } } } channel_event [vdev_rpc, 2] { on: { if (outlets >= 2) { cmdQueue = "cmdQueue,'on 2!'" } } off: { if (outlets >= 2) { cmdQueue = "cmdQueue,'off 2!'" } } } channel_event [vdev_rpc, 3] { on: { if (outlets >= 3) { cmdQueue = "cmdQueue,'on 3!'" } } off: { if (outlets >= 3) { cmdQueue = "cmdQueue,'off 3!'" } } } channel_event [vdev_rpc, 4] { on: { if (outlets >= 4) { cmdQueue = "cmdQueue,'on 4!'" } } off: { if (outlets >= 4) { cmdQueue = "cmdQueue,'off 4!'" } } } channel_event [vdev_rpc, 5] { on: { if (outlets >= 5) { cmdQueue = "cmdQueue,'on 5!'" } } off: { if (outlets >= 5) { cmdQueue = "cmdQueue,'off 5!'" } } } channel_event [vdev_rpc, 6] { on: { if (outlets >= 6) { cmdQueue = "cmdQueue,'on 6!'" } } off: { if (outlets >= 6) { cmdQueue = "cmdQueue,'off 6!'" } } } channel_event [vdev_rpc, 7] { on: { if (outlets >= 7) { cmdQueue = "cmdQueue,'on 7!'" } } off: { if (outlets >= 7) { cmdQueue = "cmdQueue,'off 7!'" } } } channel_event [vdev_rpc, 8] { on: { if (outlets >= 8) { cmdQueue = "cmdQueue,'on 8!'" } } off: { if (outlets >= 8) { cmdQueue = "cmdQueue,'off 8!'" } } } channel_event [vdev_rpc, 9] { on: { if (outlets >= 9) { cmdQueue = "cmdQueue,'on 9!'" } } off: { if (outlets >= 9) { cmdQueue = "cmdQueue,'off 9!'" } } } channel_event [vdev_rpc, 10] { on: { if (outlets >= 10) { cmdQueue = "cmdQueue,'on 10!'" } } off: { if (outlets >= 10) { cmdQueue = "cmdQueue,'off 10!'" } } } channel_event [vdev_rpc, 11] { on: { if (outlets >= 11) { cmdQueue = "cmdQueue,'on 11!'" } } off: { if (outlets >= 11) { cmdQueue = "cmdQueue,'off 11!'" } } } channel_event [vdev_rpc, 12] { on: { if (outlets >= 12) { cmdQueue = "cmdQueue,'on 12!'" } } off: { if (outlets >= 12) { cmdQueue = "cmdQueue,'off 12!'" } } } channel_event [vdev_rpc, 99] { on: { cmdQueue = "cmdQueue,'refresh!'"; off [vdev_rpc, 99] } } data_event [rpcDevice] { online: { rpconline = 1 if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'Device reported online') } } offline: { // reset flags rpconline = 0 rpconlineattempt = 0 executingCmd = 0 waitingForOffline = 0 menuLevel = 0 updatingStatus = 0 if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'Device reported offline') } } string: { // if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', "'Data: ',rpcbuffer") } // if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'Outlet status: ') } if (find_string(rpcbuffer, 'Selection>',1)) { // at main menu, so switch to outlet control clear_buffer rpcbuffer send_string rpcDevice, "'1',13" menuLevel = 1 if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'Switched to menulevel 1 (outlet control)') } } // at second menu, update channels from rpc data once queue has been flushed if ((menulevel = 1) && (find_string(rpcbuffer, '>',1)) && (updatingStatus)) { if (find_string(rpcbuffer, '1 On',1)) { on[vdev_rpc,1] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'ON 1')} } if (find_string(rpcbuffer, '1 Off',1)){ off[vdev_rpc,1] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'OFF 1')} } if (find_string(rpcbuffer, '2 On',1)) { on[vdev_rpc,2] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'ON 2')} } if (find_string(rpcbuffer, '2 Off',1)){ off[vdev_rpc,2] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'OFF 2')} } if (find_string(rpcbuffer, '3 On',1)) { on[vdev_rpc,3] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'ON 3')} } if (find_string(rpcbuffer, '3 Off',1)){ off[vdev_rpc,3] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'OFF 3')} } if (find_string(rpcbuffer, '4 On',1)) { on[vdev_rpc,4] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'ON 4')} } if (find_string(rpcbuffer, '4 Off',1)){ off[vdev_rpc,4] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'OFF 4')} } if (find_string(rpcbuffer, '5 On',1)) { on[vdev_rpc,5] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'ON 5')} } if (find_string(rpcbuffer, '5 Off',1)){ off[vdev_rpc,5] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'OFF 5')} } if (find_string(rpcbuffer, '6 On',1)) { on[vdev_rpc,6] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'ON 6')} } if (find_string(rpcbuffer, '6 Off',1)){ off[vdev_rpc,6] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'OFF 6')} } if (find_string(rpcbuffer, '7 On',1)) { on[vdev_rpc,7] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'ON 7')} } if (find_string(rpcbuffer, '7 Off',1)){ off[vdev_rpc,7] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'OFF 7')} } if (find_string(rpcbuffer, '8 On',1)) { on[vdev_rpc,8] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'ON 8')} } if (find_string(rpcbuffer, '8 Off',1)){ off[vdev_rpc,8] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'OFF 8')} } if (find_string(rpcbuffer, '9 On',1)) { on[vdev_rpc,9] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'ON 9')} } if (find_string(rpcbuffer, '9 Off',1)){ off[vdev_rpc,9] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'OFF 9')} } if (find_string(rpcbuffer, '10 On',1)) { on[vdev_rpc,10] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'ON 10')} } if (find_string(rpcbuffer, '10 Off',1)){ off[vdev_rpc,10] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'OFF 10')} } if (find_string(rpcbuffer, '11 On',1)) { on[vdev_rpc,11] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'ON 11')} } if (find_string(rpcbuffer, '11 Off',1)){ off[vdev_rpc,11] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'OFF 11')} } if (find_string(rpcbuffer, '12 On',1)) { on[vdev_rpc,12] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'ON 12')} } if (find_string(rpcbuffer, '12 Off',1)){ off[vdev_rpc,12] if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'OFF 12')} } clear_buffer rpcbuffer updatingStatus = 0 } } } (* **************************************************************************************** *) DEFINE_PROGRAM if ((length_string(cmdQueue) > 0) && (! executingCmd) && (! updatingStatus)) { // we have something to process; cancel disconnect timer cancel_wait 'disconnect' waitingForOffline = 0 if ((rpconline) && (menuLevel = 1)) { // we're online, so do something if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'Processing queue') } // extract next item in queue cmd = remove_string(cmdQueue, '!', 1) cmd = left_string(cmd, length_string(cmd)-1) // execute select { active (cmd = 'refresh'): { wait 1 { clear_buffer rpcbuffer updatingStatus = 1 send_string rpcDevice, "13" } } active (1): call 'RpcCommand' (cmd) } } if (! rpconline) { call 'RpcConnect' } // initiate connection } if ((length_string(cmdQueue) = 0) && (rpconline) && (! executingCmd) && (! waitingForOffline)) { // we're online, but have nothing to process waitingForOffline = 1 if (loglevel = 2) { syslog(Info, 'BaytechRPCModule', 'Nothing left to process; closing connection in 10 seconds') } wait 100 'disconnect' { // wait 10 seconds if ((length_string(cmdQueue) = 0) && (rpconline)) { // still nothing to process, so disconnect call 'RpcDisconnect' } } }