├── .gitignore ├── LICENSE ├── README.md ├── assets ├── APC_old_Protected_Object.png └── APC_rj12.jpg ├── pdu-commander.py ├── pduapc.py ├── pdulog.py ├── sample_sequence.py └── test_pdu.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | #vsCode 31 | *.code-workspace 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 ThriveAudio LLC, MyElectrons.com/.ru 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Command Line Interface (CLI) and Sequencer with Python (bash, sh, other scripts) integration for APC ap7900 and other compatible Power Distribution Units (PDU) 2 | 3 | While AP79xx(b) are versatile PDU devices that provide a variety of access and control options, somehow there was no ready to use **CLI tool** available that could be **called from the command line or a script** and told what sockets to switch on or off on PDU(s), or what to measure. 4 | 5 | There were several sketches I found on the Net, these would potentially make PDU's controllable from scripts, mostly using telnet in conjunction with pexpect, as well as SNMP. However, those were only sketches and still required a lot of tinkering; none of the predecessor projects that I've seen provided even a rudimentary error handling. 6 | 7 | This little project covers the power control needs I had. Thanks to it I can now run a chron-job that will: 8 | 1) **Power up** the backup rig (and *do it in a properly staged way*), 9 | 2) Perform backups (this part is obviously outside of the scope of this project), 10 | 3) **Power it down** completely (*eliminating wasting of dozens of Watts by standby PSUs power draw*). 11 | 12 | All the steps had to be done without any human interactions required - simply because I'd forget about it after a while. 13 | 14 | This project can also be used for **power monitoring** purposes. Just keep in mind that the precision of current and power measurements of the AP79xx series is somewhat low (to my taste, Ok?) 15 | 16 | I'd be happy to modify and adapt this project for integrating other models (including other brands) and/or measured PDU(s), UPS, etc. Should you need it implemented fast - *just send a unit that needs to be supported my way*. :wink: 17 | 18 | ## Prerequisites 19 | **python3** version 3.7 and up 20 | 21 | ## Usage Examples 22 | 23 | ### All defaults 24 | Give it no parameters - it will go to "ap7900" (it should be a DNS-resolvable name) and retrieve its status: 25 |
26 | ./pdu-commander.py 27 |28 | ``` 29 | Address: ap7900 30 | Unit ID: ap7900 31 | Outlets: 8 32 | ('get', 'status') 33 | 1: ON : Outlet 1 34 | 2: ON : Outlet 2 35 | 3: ON : Outlet 3 36 | 4: ON : Outlet 4 37 | 5: ON : Outlet 5 38 | 6: ON : Outlet 6 39 | 7: ON : Outlet 7 40 | 8: ON : Outlet 8 41 | ``` 42 | It will use the default APC username and password. See `./pdu-commander.py -h` for defaults. 43 | 44 | ### Turn one outlet ON 45 | Actionable commands consist of a command and its argument(s), divided by a column ':' 46 |
47 | ./pdu-commander.py on:1 48 |49 | ``` 50 | Address: ap7900 51 | Unit ID: ap7900 52 | Outlets: 8 53 | ('on', '1') 54 | 1: Outlet 1 : Outlet Turned On 55 | ``` 56 | 57 | ### A sequence of ON and OFF commands with a 3 seconds delay between them 58 |
59 | ./pdu-commander.py on:1-3,8 delay:3 off:all 60 |61 | ``` 62 | Address: ap7900 63 | Unit ID: ap7900 64 | Outlets: 8 65 | ('on', '1-3,8') 66 | 1: Outlet 1 : Outlet Turned On 67 | 2: Outlet 2 : Outlet Turned On 68 | 3: Outlet 3 : Outlet Turned On 69 | 8: Outlet 8 : Outlet Turned On 70 | ('delay', '3') 71 | ('off', 'all') 72 | 1: Outlet 1 : Outlet Turned Off 73 | 2: Outlet 2 : Outlet Turned Off 74 | 3: Outlet 3 : Outlet Turned Off 75 | 4: Outlet 4 : Outlet Turned Off 76 | 5: Outlet 5 : Outlet Turned Off 77 | 6: Outlet 6 : Outlet Turned Off 78 | 7: Outlet 7 : Outlet Turned Off 79 | 8: Outlet 8 : Outlet Turned Off 80 | ``` 81 | There could be as many commands called in one sequence as needed. 82 | 83 | ### Connection details can be specified 84 |
85 | ./pdu-commander.py -a 192.168.7.242 -u device -p your_password "on: 1-2, 8" "delay: 3" "off: 2" 86 |87 | ``` 88 | Address: 192.168.7.242 89 | Unit ID: ap7902 90 | Outlets: 16 91 | ('on', '1-2,8') 92 | 1: Outlet 1 : Outlet Turned On 93 | 2: Outlet 2 : Outlet Turned On 94 | 8: Outlet 8 : Outlet Turned On 95 | ('delay', '3') 96 | ('off', '2') 97 | 2: Outlet 2 : Outlet Turned Off 98 | ``` 99 | Note that the `command:argument` parameters must be passed as a single command line argument - hence if you'd like or need to use spaces in them, please remember to put quotes around each `"command : argument"` pair. 100 | 101 | ### Read current and power, and be quiet 102 |
103 | ./pdu-commander.py -q -a 192.168.7.242 get:current get:power 104 |105 | ``` 106 | Current on bank 1 is 1.4A 107 | Current on bank 2 is 0.0A 108 | Current on total is 1.4A 109 | 168 VA 110 | 168 Watts 111 | ``` 112 | 113 | ### There's HELP, when needed 114 |
115 | ./pdu-commander.py -h 116 |117 | ``` 118 | usage: pdu-commander.py [-h] [-a ADDR] [-u USER] [-p PSWD] [-P PORT] [-t TOUT] [-d] [-v | -q] [command:argument [command:argument ...]] 119 | 120 | Supported commands: 121 | on | off - control outlets on PDU's that support individual outlet switching 122 | get - read PDU data, including: status, current, power 123 | delay - pause for a number of seconds before the next command gets executed 124 | 125 | positional arguments: 126 | command:argument commands to be executed 127 | 128 | optional arguments: 129 | -h, --help show this help message and exit 130 | -a ADDR, --addr ADDR IP address or DNS-resolvable host name of PDU, default="ap7900" 131 | -u USER, --user USER username, default="device" 132 | -p PSWD, --pswd PSWD password, default="apc" 133 | -P PORT, --port PORT Telnet port, default=23 134 | -t TOUT, --tout TOUT Telnet timeout, default=7 (seconds) 135 | -d, --debug log debug messages in addition to info and errors 136 | -v, --verbosity increase output verbosity on stdout: -v or -vv 137 | -q, --quiet only output responses to "get" requests 138 | 139 | If called without command:argument parameters the script will execute "get:status" 140 | 141 | Copyright (c) MyElectrons.com, 2022; https://github.com/MyElectrons/PDU-Commander 142 | ``` 143 | 144 | ## Things to remember 145 | 146 | ### Maximum delays 147 | Each `pdu-commander.py` script invocation opens a single telnet session and executes all commands within that session. That said - the delays cannot be longer than the telnet session timeout (as configured in device settings). 148 | 149 | Delays shorter than 60 seconds are usually safe to use. 150 | 151 | Should you need a longer pause between certain actions - it's better to call the `pdu-commander.py` script again later, after that long delay has passed. 152 | 153 | ### Logging 154 | The script will record all its actions into a log file called `pdu-log.log`, that will be stored in the current directory. The file gets appended with every script invocation. Should you need to use another logging facility and/or functionality - just let me know, or code it away in the `pdulog.py` module. 155 | 156 | ### Concurrent sessions 157 | The PDU series that I tested didn't seem to like any concurrency of control sessions. Therefore in order to use this project successfully please make sure all the configuration of the unit(s) is done, completed, and sessions are closed or logged-out before the `pdu-commander.py` script is called. 158 | 159 | ### Give the PDU time to think 160 | From my experience the AP7000 series PDUs require quite some time for repeatable current and power measurements. 161 | 162 | Personally, I'd always give at least 4+ seconds to 8-outlet units and 7+ seconds to 16-outlet ones before reading any measured values. 163 | 164 | One can simply inject a `delay:7` command after all the necessary on/off commands were executed and before calling any of `get:current` or `get:power`. 165 | 166 | ## Tested thoroughly with 167 | - ap7900 - :thumbsup: 168 | - ap7901 - :thumbsup: 169 | - ap7902 - :thumbsup: 170 | 171 | ## Management nuggets for APC AP79xx PDU series 172 | 173 | ### Reset AP7900 to manufacturer defaults 174 | Source: [User Manual for APC Switched Rack Power Distribution Unit](https://download.schneider-electric.com/files?p_enDocType=User+guide&p_File_Name=ASTE-6Z6KAM_R0_EN.pdf&p_Doc_Ref=SPD_ASTE-6Z6KAM_EN) 175 | 176 | 1. Connect the serial cable (APC part number 940-0144, see pinout below) 177 | 2. Set serial communication parameters as follows: **9600 8N1, no flow control** 178 | 3. In the serial terminal program check the serial communication by pressing ENTER several times - the "User Name" prompt should appear 179 | 4. Press the RESET button (for example using a paper clip) 180 | 5. The status light should start blinking green/orange 181 | 6. Press the RESET button again 182 | 7. Press ENTER repeatedly till the "User Name" prompt appears 183 | 8. Use temporary default "apc" as both username and password 184 | 9. System --> User Manager --> Administrator --> set **User Name** and **Password** 185 | 186 | Note: if it took longer than 30 seconds to login, the procedure will need to be repeated from the step 4 above. 187 | 188 | ### Serial cable pinout for APC AP7000 (and other) PDU series 189 | The APC spare part number: 0J-940-0144A 190 | 191 | | APC RJ12 Pin Number | PC DB9 Pin Number | 192 | |---|---| 193 | | 2 | 5 (GND) | 194 | | 3 | 2 (Rx) | 195 | | 4 | 3 (Tx) | 196 | | 5 | 5 (GND) | 197 | 198 | If you look straigh at the APC PDU, pin #1 in RJ12 female connector will be at the bottom: 199 |
200 |
201 | Once ubiquitos "telephone cables" with four wires and RJ11 connectors work perfectly for making this cable: just cut one end and solder wires into DB9.
202 |
203 | ### Notes on network configuration of APC AP79xx PDU
204 |
205 | Older devices, those without the letter 'B' at the end, have an outdated "netowrk card" that does not support today's level of secure authentication algorythms.
206 |
207 | I had success with **non-B** DPUs using **Telnet and HTTP only**.
208 |
209 | In order to use HTTP one must explicitely disable the device to use HTTPS. In terminal it should look like:
210 | ```
211 | ------- Web/SSL/TLS -----------------------------------------------------------
212 |
213 | 1- Access : Enabled
214 | 2- SSL/TLS : Disabled
215 | ```
216 | Otherwise, when trying to access it via HTTP, the device will inform that it is a:
217 | ```
218 | Protected Object.
219 | This object on the APC Management Web Server is protected and requires a secure socket connection.
220 | ```
221 |
222 |
223 | But when you click there to go to the "secure connection", browsers will refuse to connect with varios messages, depending on the browser:
224 | - Secure Connection Failed
225 | - This site can’t provide a secure connection
226 | - XX.YY.ZZ.LL uses an unsupported protocol
227 |
228 | To summarise, the solution is to go through the terminal and configure:
229 | ```
230 | (2)-Network --> (6)-Web/SSL/TLS --> (2)-SSL/TLS --> (1)-Disabled --> (6)-Accept Changes --> ESC
231 |
232 | (5)-Telnet/SSH --> (2)-Protocol Mode --> (1)-Telnet --> (6)-Accept Changes --> ESC
233 | ```
234 |
235 | ### Power consumption of APC ap79xx PDU
236 | Even though these devices are all about controlling the power, their "Product Datasheet" (e.g.: for [ap7900B](https://www.apc.com/us/en/product/AP7900B/rack-pdu-switched-1u-15a-100-120v-8515/)) tells us nothing about how much the device itself consumes. This data might be of not much relevance to big datacenters where it will be negligeable compared to other loads, but for home lab builders that can make quite a difference: for example in the area where we live each 10 Watts of electical power cunsumed non-stop cost us roughly one US dollar per month (10 Watt ~~ $1 USD / month).
237 |
238 | Below is the data I measured with the devices at hand, using a certified bench-top power meter. No load connected to the outlets.
239 |
240 | | Device | All outlets OFF | 8 outlets ON | 16 outlets ON |
241 | |---|---|---|---|
242 | | ap7900 | 2.8 W | 7.6 W | |
243 | | ap7901 | 2.7 W | 7.5 W | |
244 | | ap7902 | 2.8 W | 7.6 W | 12.5 W |
245 |
246 | With less than 3W for the controller and approximately 600mW per relay I'd say it's really good for home (or garage) type of applications.
247 |
248 | .
249 |
250 | *Happy controlling!*
251 |
252 | -- :heart: -- Serge.
253 |
--------------------------------------------------------------------------------
/assets/APC_old_Protected_Object.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyElectrons/PDU-Commander/725023a746183a119e961b594f90306546c5a75f/assets/APC_old_Protected_Object.png
--------------------------------------------------------------------------------
/assets/APC_rj12.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyElectrons/PDU-Commander/725023a746183a119e961b594f90306546c5a75f/assets/APC_rj12.jpg
--------------------------------------------------------------------------------
/pdu-commander.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | # PDU CLI Commander
4 | # A Command Line Interface wrapper for PDU sequencer module(s)
5 | #
6 | # This code is published under the MIT License
7 | # When forking, reusing and/or modifying, please retain the following line:
8 | # Copyright (c) MyElectrons.com; https://github.com/MyElectrons/PDU-Commander
9 |
10 |
11 | import pduapc
12 | from pdulog import Debg, Info, Err
13 | import pdulog
14 |
15 | import argparse
16 | import textwrap
17 | import re
18 |
19 | parser = argparse.ArgumentParser(
20 | formatter_class=argparse.RawDescriptionHelpFormatter,
21 | description=textwrap.dedent('''\
22 | Supported commands:
23 | on | off - control outlets on PDU's that support individual outlet switching
24 | get - read PDU data, including: status, current, power
25 | delay - pause for a number of seconds before the next command gets executed
26 | '''),
27 | epilog=textwrap.dedent('''\
28 |
29 | If called without command:argument parameters the script will execute "get:status"
30 |
31 | Copyright (c) MyElectrons.com, 2022; https://github.com/MyElectrons/PDU-Commander
32 | ''')
33 | )
34 | parser.add_argument('-a', '--addr', help='IP address or DNS-resolvable host name of PDU, default="ap7900"')
35 | parser.add_argument('-u', '--user', type=str, help='username, default="device"')
36 | parser.add_argument('-p', '--pswd', type=str, help='password, default="apc"')
37 | parser.add_argument('-P', '--port', type=int, help='Telnet port, default=23')
38 | parser.add_argument('-t', '--tout', type=int, help='Telnet timeout, default=7 (seconds)')
39 | parser.add_argument("-d", "--debug", action="store_true", help="log debug messages in addition to info and errors")
40 | vq_grp = parser.add_mutually_exclusive_group()
41 | vq_grp.add_argument("-v", "--verbosity", action="count", default=0, help="increase output verbosity on stdout: -v or -vv")
42 | vq_grp.add_argument("-q", "--quiet", action="store_true", help="only output responses to \"get\" requests")
43 | parser.add_argument('commands', metavar='command:argument', type=str, nargs='*', #action='append',
44 | help='commands to be executed')
45 | args = parser.parse_args()
46 |
47 | cfg = {}
48 | if args.addr:
49 | cfg['host'] = args.addr
50 | if args.user:
51 | cfg['user'] = args.user
52 | if args.pswd:
53 | cfg['pswd'] = args.pswd
54 | if args.port:
55 | cfg['port'] = args.port
56 | if args.debug:
57 | pdulog.DebugOn()
58 | if args.verbosity:
59 | pdulog.verbose = args.verbosity
60 | if args.quiet:
61 | pdulog.quiet = args.quiet
62 |
63 | seq = []
64 | if args.commands:
65 | for c in args.commands:
66 | cmd_arg_ok = False
67 | text = re.sub(r'[\s]+', '', c)
68 | m = re.search('(.+):(.+)', text)
69 | if m:
70 | k = m.group(1)
71 | v = m.group(2)
72 | if k != '' and v != '':
73 | cmd_arg_ok = True
74 | seq.append((k,v))
75 | if not cmd_arg_ok:
76 | Err('Unable to parse "%s" as a command:argument pair. Bailing out.' % (c))
77 | exit(1)
78 |
79 | pduapc.apc_pdu_sequencer(cfg, seq)
80 |
--------------------------------------------------------------------------------
/pduapc.py:
--------------------------------------------------------------------------------
1 | # module pduapc
2 | #
3 | # Interfacing APC PDUs via telnet "CLI"
4 | #
5 | # This code is published under the MIT License
6 | # When forking, reusing and/or modifying, please retain the following line:
7 | # Copyright (c) MyElectrons.com; https://github.com/MyElectrons/PDU-Commander
8 | #
9 | # Tested with AP7900, AP7901, and AP7902
10 |
11 |
12 | from pdulog import Debg, Info, Warn, Err
13 | import pdulog
14 |
15 | import telnetlib
16 | import time
17 | import re
18 |
19 | def_host = 'ap7900' # hostname or IP address
20 | def_user = 'device'
21 | def_pswd = 'apc'
22 |
23 | def_port = 23
24 | def_timeout = 7
25 |
26 | beol = b'\r\n' # binary end of line
27 |
28 |
29 | def apc_pdu_sequencer(cfg=None, seq=None):
30 |
31 | def Display_Intro(mlines):
32 | Debg(mlines)
33 | if not pdulog.quiet:
34 | print('Address:', cfg['host'])
35 | for st in mlines.splitlines():
36 | if 'Unit ID:' in st:
37 | unit_id = st
38 | if 'Outlets:' in st:
39 | outlets = st
40 | if 'unit_id' in vars() and unit_id:
41 | print(unit_id)
42 | if 'outlets' in vars() and outlets:
43 | print(outlets)
44 |
45 | def Display_Step(ln):
46 | Info(ln)
47 | if not pdulog.quiet:
48 | print(ln)
49 |
50 | def Display_Result(mlines, getcmd=False, opcode=''):
51 | mres=''
52 | for st in mlines.splitlines():
53 | if re.search(r'E[0-9]{3}', st):
54 | err_st = st
55 | if not re.search(r'OK|APC>|E[0-9]{3}|%s' % (opcode), st):
56 | mres += st + '\n'
57 | mres = mres.rstrip()
58 | if mres:
59 | if getcmd or not pdulog.quiet:
60 | print(mres)
61 | if getcmd:
62 | Info('\n' + mres)
63 | if 'err_st' in vars() and err_st:
64 | Warn('Command failed to execute: ' + err_st)
65 |
66 |
67 | if cfg is None:
68 | cfg = {}
69 | cfg.setdefault('host', def_host)
70 | cfg.setdefault('user', def_user)
71 | cfg.setdefault('pswd', def_pswd)
72 | cfg.setdefault('port', def_port)
73 | cfg.setdefault('timeout', def_timeout)
74 |
75 | if seq is None or seq == []:
76 | seq = [('get', 'status')]
77 |
78 | Debg('cfg = ' + str(cfg))
79 | Debg('seq = ' + str(seq))
80 |
81 | tn = telnetlib.Telnet(cfg['host'], port = cfg['port'], timeout = cfg['timeout'])
82 | tnto = cfg['timeout']
83 | try:
84 | rd_uname = tn.read_until(b'User Name :', tnto).decode('ascii')
85 | tn.write(cfg['user'].encode('ascii') + beol)
86 | rd_paswd = tn.read_until(b'Password :', tnto).decode('ascii')
87 | tn.write(cfg['pswd'].encode('ascii') + b' -c' + beol)
88 | rd_1stAPC = tn.read_until(b'APC>', tnto).decode('ascii')
89 | Display_Intro(rd_1stAPC)
90 | if 'User Name :' in rd_1stAPC:
91 | raise Exception('Wrong ussername/password. Bailing out.')
92 | for tt in seq: # process command:argument two-tuple
93 | Display_Step(str(tt))
94 | if 'on' == tt[0] or 'off' == tt[0]:
95 | cmd = tt[0] + ' ' + str(tt[1])
96 | tn.write(cmd.encode('ascii') + beol)
97 | res = tn.read_until(b'APC>', tnto).decode('ascii')
98 | Display_Result(res, opcode=tt[0])
99 | elif 'get' == tt[0]:
100 | cmd = str(tt[1])
101 | tn.write(cmd.encode('ascii') + beol)
102 | res = tn.read_until(b'APC>', tnto).decode('ascii')
103 | Display_Result(res, True, opcode=tt[1])
104 | elif 'delay' == tt[0]:
105 | time.sleep(int(tt[1]))
106 | else:
107 | Warn('Unrecognized command "%s" - skipped.' % (tt[0]))
108 | except Exception as e:
109 | Err('Exception while in Telnet session: "%s"' % (str(e)))
110 | finally:
111 | tn.write(b'bye' + beol)
112 | tn.close()
113 |
114 |
--------------------------------------------------------------------------------
/pdulog.py:
--------------------------------------------------------------------------------
1 | # module: pdulog.py
2 |
3 | import logging
4 |
5 | verbose = 0
6 | debug = False
7 | quiet = False
8 |
9 |
10 | logging.basicConfig(filename='pdu-log.log',
11 | filemode='a',
12 | format='%(asctime)s,%(msecs)d %(levelname)s %(message)s',
13 | datefmt='%Y-%m-%d %H:%M:%S',
14 | level=logging.INFO)
15 |
16 |
17 | logging.debug("===== Running PDU-Commander CLI =====")
18 |
19 | logger = logging.getLogger('PDU-CLI')
20 |
21 |
22 | def DebugOn():
23 | logger.level = logging.DEBUG
24 | debug = True
25 |
26 |
27 | def Debg(_debg_msg):
28 | logger.debug(_debg_msg)
29 | if 2 <= verbose and not quiet:
30 | print('DEBUG:', _debg_msg)
31 |
32 | def Info(_info_msg):
33 | logger.info(_info_msg)
34 | if 1<= verbose and not quiet:
35 | print('INFO:', _info_msg)
36 |
37 | def Warn(_warn_msg):
38 | logger.warning(_warn_msg)
39 | if not quiet:
40 | print('WARNING:', _warn_msg)
41 |
42 | def Err(_err_msg):
43 | logger.error(_err_msg)
44 | if not quiet:
45 | print('ERROR:', _err_msg)
46 |
47 |
--------------------------------------------------------------------------------
/sample_sequence.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pduapc
4 |
5 |
6 | s = [
7 | ('get', 'status'),
8 | ('off', 'all'),
9 | ('delay', '1'),
10 | ('on', 1),
11 | ('delay', '1'),
12 | ('on', '2'),
13 | ('delay', 1),
14 | ('on', '8'),
15 | ('delay', 6),
16 | ('get', 'current'),
17 | ('get', 'power'),
18 | ('off', '1,2'),
19 | ('delay', 1),
20 | ('off', 'all'),
21 | ('get', 'status'),
22 | ]
23 |
24 | pduapc.apc_pdu_sequencer(seq = s)
25 |
--------------------------------------------------------------------------------
/test_pdu.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | for i in {1..5}
4 | do
5 | ./pdu-commander.py off:all delay:1 on:1 delay:1 on:2 delay:1 on:3 delay:1 on:4 delay:1 on:5 delay:1 on:6 delay:1 on:7 delay:1 on:8
6 | ./pdu-commander.py delay:1 off:1 delay:1 off:2 delay:1 off:3 delay:1 off:4 delay:1 off:5 delay:1 off:6 delay:1 off:7 delay:1 off:8
7 | done
8 |
9 |
--------------------------------------------------------------------------------