{"id":3294,"date":"2025-12-17T15:08:59","date_gmt":"2025-12-17T20:08:59","guid":{"rendered":"https:\/\/blog.lufamily.ca\/kang\/?p=3294"},"modified":"2025-12-17T15:09:01","modified_gmt":"2025-12-17T20:09:01","slug":"ups-monitoring","status":"publish","type":"post","link":"https:\/\/blog.lufamily.ca\/kang\/2025\/12\/17\/ups-monitoring\/","title":{"rendered":"UPS Monitoring"},"content":{"rendered":"\n<p>We have several UPS (Uninterruptible Power Supply) units around the house. They are there to prevent power interruptions to networking equipment and computer servers. When you are into home automation, keeping these services up and running is almost essential.<\/p>\n\n\n\n<p>Previously this year, I noticed one of the UPS unit keeps on chirping and the body of the UPS unit was warm to the touch. I noticed on the LED display, that its battery is due to be replaced. This was not an issue. However I treated this as a cautionary tale because some of my UPS units were situated within the house that I rarely visit, so I may not hear the beeping alerts and may end up being a potential fire hazard should the battery misbehave. I decided that I needed to monitor my UPS units more frequently.<\/p>\n\n\n\n<p>I started to learn about NUT (<a href=\"https:\/\/networkupstools.org\" target=\"_blank\" rel=\"noreferrer noopener\">Network UPS Tools<\/a>), and went on a mission to deploy this solution so that I can centrally monitored all of my UPS on a single web site. The first step is to ensure that all of my UPS can physically communicate its status to a computer host. This means they all have to be connected with a USB port.<\/p>\n\n\n\n<p>Once communication is established, I then had to install NUT software on each of the computer hosts. My UPS units were installed on different hosts consisting of Raspberry PI, Ubuntu Linux, and Mac, so I had to configure each properly. Below is a summary of the configuration steps.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Linux &amp; Raspberry Pi configuration:<\/h4>\n\n\n\n<p>First install the NUT software<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># apt update\n# apt install nut nut-server nut-client\n# apt install libusb-1.0-0-dev<\/code><\/pre>\n\n\n\n<p>Ensure the UPS is connected to with USB.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># lsusb\nBus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub\nBus 001 Device 002: ID 0764:0601 Cyber Power System, Inc. PR1500LCDRT2U UPS\nBus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub<\/code><\/pre>\n\n\n\n<p>Perform a scan.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># nut-scanner -U\n&#91;ups-name]\n        driver = \"usbhid-ups\"\n        port = \"auto\"\n        vendorid = \"0764\"\n        productid = \"0601\"\n        product = \"CP1500AVRLCD3\"\n        serial = \"BHPPO7007168\"\n        vendor = \"CPS\"\n        bus = \"001\"\n        device = \"002\"\n        busport = \"008\"\n        ###NOTMATCHED-YET###bcdDevice = \"0200\"<\/code><\/pre>\n\n\n\n<p>Modify the following files in <code>\/etc\/nut<\/code>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>nut.conf<\/code><\/li>\n\n\n\n<li><code>ups.conf<\/code><\/li>\n\n\n\n<li><code>upsmon.conf<\/code><\/li>\n\n\n\n<li><code>upsd.conf<\/code><\/li>\n\n\n\n<li><code>upsd.users<\/code><\/li>\n<\/ul>\n\n\n\n<p>Inside <code>nut.conf<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>MODE=netserver<\/code><\/pre>\n\n\n\n<p>Inside <code>ups.conf<\/code>: Copy the above output from <code>nut-scanner<\/code> into end of the file and be sure to <span style=\"text-decoration: underline;\">change the <code>ups-name<\/code> <\/span>into something that is unique.<\/p>\n\n\n\n<p>Inside <code>upsmon.conf<\/code>: Remember to replace <code>ups-name<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>MONITOR ups-name 1 upsmon secret primary<\/code><\/pre>\n\n\n\n<p>Inside <code>upsd.conf<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>LISTEN 0.0.0.0 3493\nLISTEN ::1 3493<\/code><\/pre>\n\n\n\n<p>Inside <code>upsd.users<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;upsmon]\n        password = secret\n        actions = SET\n        instcmds = ALL\n        upsmon primary<\/code><\/pre>\n\n\n\n<p>Finally we need add a file in <code>\/etc\/udev\/rules.d<\/code> which governs whether we can send commands to the UPS. We need to create a file called <code>99-nut-ups.rules<\/code> and that file should contain the following content:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"0764\", ATTRS{idProduct}==\"0601\", MODE=\"0660\", GROUP=\"nut\", OWNER=\"nut\"<\/code><\/pre>\n\n\n\n<p>Note that <code>idVendor<\/code> and <code>idProduct<\/code> should be replaced with the appropriate data from the <code>nut-scanner<\/code>.<\/p>\n\n\n\n<p>Once all of this is completed, we have to restart all the relevant services and driver.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>udevadm control --reload-rules\nudevadm trigger\nsystemctl restart nut-server.service\nsystemctl restart nut-client.service\nsystemctl restart nut-monitor.service\nsystemctl restart nut-driver@ups-name.service\nupsdrvctl stop\nupsdrvctl start<\/code><\/pre>\n\n\n\n<p>You can test it now by reading the current UPS status information. Below is an example with <code>ups-computer-room<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># upsc ups-computer-room@localhost\nInit SSL without certificate database\nbattery.charge: 100\nbattery.charge.low: 10\nbattery.charge.warning: 20\nbattery.mfr.date: CPS\nbattery.runtime: 2725\nbattery.runtime.low: 300\nbattery.type: PbAcid\nbattery.voltage: 26.0\nbattery.voltage.nominal: 24\ndevice.mfr: CPS\ndevice.model: CP1500AVRLCD3\ndevice.serial: BHPPO7007168\ndevice.type: ups\ndriver.debug: 0\ndriver.flag.allow_killpower: 0\ndriver.name: usbhid-ups\ndriver.parameter.bus: 001\ndriver.parameter.busport: 008\ndriver.parameter.device: 002\ndriver.parameter.pollfreq: 30\ndriver.parameter.pollinterval: 2\ndriver.parameter.port: auto\ndriver.parameter.product: CP1500AVRLCD3\ndriver.parameter.productid: 0601\ndriver.parameter.serial: BHPPO7007168\ndriver.parameter.synchronous: auto\ndriver.parameter.vendor: CPS\ndriver.parameter.vendorid: 0764\ndriver.state: updateinfo\ndriver.version: 2.8.1\ndriver.version.data: CyberPower HID 0.8\ndriver.version.internal: 0.52\ndriver.version.usb: libusb-1.0.27 (API: 0x100010a)\ninput.voltage: 122.0\ninput.voltage.nominal: 120\noutput.voltage: 122.0\nups.beeper.status: enabled\nups.delay.shutdown: 20\nups.delay.start: 30\nups.load: 21\nups.mfr: CPS\nups.model: CP1500AVRLCD3\nups.productid: 0601\nups.realpower.nominal: 900\nups.serial: BHPPO7007168\nups.status: OL\nups.test.result: Done and passed\nups.timer.shutdown: -60\nups.timer.start: -60\nups.vendorid: 0764<\/code><\/pre>\n\n\n\n<p>You can also perform actions on the UPS unit. First we can query a list of commands that we can execute on the UPS. Note that with this example the UPS is being queried by a remote host hence the usage of <code>avs.localdomain<\/code> instead of <code>localhost<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># upscmd -l ups-computer-room@avs.localdomain\nInstant commands supported on UPS &#91;ups-computer-room]:\n\nbeeper.disable - Disable the UPS beeper\nbeeper.enable - Enable the UPS beeper\nbeeper.mute - Temporarily mute the UPS beeper\nbeeper.off - Obsolete (use beeper.disable or beeper.mute)\nbeeper.on - Obsolete (use beeper.enable)\nload.off - Turn off the load immediately\nload.off.delay - Turn off the load with a delay (seconds)\nshutdown.reboot - Shut down the load briefly while rebooting the UPS\nshutdown.stop - Stop a shutdown in progress\ntest.battery.start.deep - Start a deep battery test\ntest.battery.start.quick - Start a quick battery test\ntest.battery.stop - Stop the battery test\ntest.panel.start - Start testing the UPS panel\ntest.panel.stop - Stop a UPS panel test<\/code><\/pre>\n\n\n\n<p>Reading the above, we see that we can perform a quick battery test by sending the command <code>test.battery.start.quick<\/code>. We do this with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>upscmd -u upsmon -p secret ups-computer-room@avs.localdomain test.battery.start.quick<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">MaC configuration:<\/h4>\n\n\n\n<p>We install NUT with <code>brew<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>brew install nut<\/code><\/pre>\n\n\n\n<p>The configuration files are stored in <code>\/usr\/local\/etc\/nut<\/code>.<\/p>\n\n\n\n<p>Since there is no <code>lsusb<\/code> or <code>nut-scanner<\/code> on the Mac, you can use the following command to see if the UPS is connected with USB or not.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>system_profiler SPUSBHostDataType<\/code><\/pre>\n\n\n\n<p>You can also use:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pmset -g ps<\/code><\/pre>\n\n\n\n<p>The <code>ups.conf<\/code> file is more simple, because you don&#8217;t need the other details:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;ups-dining-room]\n  driver = macosx-ups\n  port = auto\n  desc = \"APC Back-UPS ES 550\"<\/code><\/pre>\n\n\n\n<p>All other configuration files is the same and there is no need to create the <code>\/etc\/udev\/rules.d<\/code> file.<\/p>\n\n\n\n<p>I need to start nut when the Mac reboots, so I need to configure <code>launchd<\/code> for this. First I created two scripts, <code>start.sh<\/code> and <code>stop.sh<\/code> in the <code>~\/Applications\/nut<\/code>. Below are their respective contents:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u276f cat start.sh\n#!\/usr\/bin\/env zsh\n\n\/usr\/local\/sbin\/upsdrvctl start\n\/usr\/local\/sbin\/upsd\n\/usr\/local\/sbin\/upsmon\n\n\u276f cat stop.sh\n#!\/usr\/bin\/env zsh\n\n\/usr\/local\/sbin\/upsmon -c stop\n\/usr\/local\/sbin\/upsd -c stop\n\/usr\/local\/sbin\/upsdrvctl stop<\/code><\/pre>\n\n\n\n<p>Next, we have to create a home.nut.custom.plist file in \/Library\/LaunchDaemons. It has the following contents:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?>\n&lt;!DOCTYPE plist PUBLIC \"-\/\/Apple\/\/DTD PLIST 1.0\/\/EN\" \"http:\/\/www.apple.com\/DTDs\/PropertyList-1.0.dtd\">\n&lt;plist version=\"1.0\">\n  &lt;dict>\n    &lt;key>Label&lt;\/key>\n    &lt;string>home.nut.custom&lt;\/string>\n    &lt;!-- Start script -->\n    &lt;key>ProgramArguments&lt;\/key>\n    &lt;array>\n      &lt;string>\/bin\/zsh&lt;\/string>\n      &lt;string>\/Users\/kang\/Applications\/nut\/start.sh&lt;\/string>\n    &lt;\/array>\n    &lt;!-- Stop script -->\n    &lt;key>StopProgram&lt;\/key>\n    &lt;array>\n      &lt;string>\/bin\/zsh&lt;\/string>\n      &lt;string>\/Users\/kang\/Applications\/nut\/stop.sh&lt;\/string>\n    &lt;\/array>\n    &lt;!-- Run at boot -->\n    &lt;key>RunAtLoad&lt;\/key>\n    &lt;true\/>\n    &lt;!-- Logging -->\n    &lt;key>StandardOutPath&lt;\/key>\n    &lt;string>\/var\/log\/nut.out.log&lt;\/string>\n    &lt;key>StandardErrorPath&lt;\/key>\n    &lt;string>\/var\/log\/nut.err.log&lt;\/string>\n  &lt;\/dict>\n&lt;\/plist><\/code><\/pre>\n\n\n\n<p>We then have to enable the daemon.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo launchctl bootstrap system \/Library\/LaunchDaemons\/home.nut.custom.plist<\/code><\/pre>\n\n\n\n<p>Now the NUT daemon will be running when we reboot the Mac.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">PEANUT Installation<\/h4>\n\n\n\n<p>Once this is all done, I am then able to retrieve all of my UPS units&#8217; status from anywhere on my network as long as I have the <code>nut-client<\/code> package installed and have access to the <code>upsc<\/code> command. We are now ready to install the <a href=\"https:\/\/github.com\/Brandawg93\/PeaNUT\" target=\"_blank\" rel=\"noreferrer noopener\">PEANUT<\/a> web interface using <code><a href=\"https:\/\/podman.io\" target=\"_blank\" rel=\"noreferrer noopener\">podman<\/a><\/code>.<\/p>\n\n\n\n<p>On a computer host that is running other centralized services within my house, We performed the following steps.<\/p>\n\n\n\n<p>We created the following <code>systemd<\/code> unit file called: <code>\/etc\/systemd\/system\/peanut.service<\/code>, which contains the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># container-1518f57b9c9880dc5538fdd8c1a770993ab5a09dda03071b5a603142104831d2.service\n# autogenerated by Podman 4.9.3\n# Thu Dec 11 17:08:07 EST 2025\n\n&#91;Unit]\nDescription=Podman Peanut.service\nDocumentation=man:podman-generate-systemd(1)\nWants=network-online.target\nAfter=network-online.target\nRequiresMountsFor=%t\/containers\n\n&#91;Service]\nEnvironment=PODMAN_SYSTEMD_UNIT=%n\nRestart=always\nTimeoutStopSec=70\nWorkingDirectory=\/home\/kang\/peanut\nExecStart=\/usr\/bin\/podman run \\\n        --cidfile=%t\/%n.ctr-id \\\n        --cgroups=no-conmon \\\n        --rm \\\n        --sdnotify=conmon \\\n        -d \\\n        --replace \\\n        --name peanut \\\n        -v .\/config:\/config \\\n        -p 58180:8080 \\\n        --env WEB_PORT=8080 docker.io\/brandawg93\/peanut\nExecStop=\/usr\/bin\/podman stop \\\n        --ignore -t 10 \\\n        --cidfile=%t\/%n.ctr-id\nExecStopPost=\/usr\/bin\/podman rm \\\n        -f \\\n        --ignore -t 10 \\\n        --cidfile=%t\/%n.ctr-id\nType=notify\nNotifyAccess=all\n\n&#91;Install]\nWantedBy=default.target<\/code><\/pre>\n\n\n\n<p>The above file was generated with the <code>podman<\/code> utility itself using:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>podman generate systemd --new --files peanut<\/code><\/pre>\n\n\n\n<p>For the above to work, there must be a container <code>peanut<\/code> running first, which we did with the following command just to temporary create the file. This also assumes a local <code>config<\/code> directory is created for the volume mapping.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>podman run --name peanut -v .\/config:\/config --restart unless-stopped -p 58180:8080 --env WEB_PORT=8080 docker.io\/brandawg93\/peanut<\/code><\/pre>\n\n\n\n<p>Once the system file is created, we do the following to enable it.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl daemon-reload\nsudo systemctl enable peanut.service\nsudo systemctl start peanut<\/code><\/pre>\n\n\n\n<p>I did some reverse proxy settings on my Apache 2.0 server, and a CNAME record on my local DNS, and then I have my UPS monitoring system.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"659\" src=\"https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2025\/12\/Screenshot-2025-12-17-at-2.48.33-PM-1024x659.png\" alt=\"\" class=\"wp-image-3300\" srcset=\"https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2025\/12\/Screenshot-2025-12-17-at-2.48.33-PM-1024x659.png 1024w, https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2025\/12\/Screenshot-2025-12-17-at-2.48.33-PM-300x193.png 300w, https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2025\/12\/Screenshot-2025-12-17-at-2.48.33-PM-768x494.png 768w, https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2025\/12\/Screenshot-2025-12-17-at-2.48.33-PM-1536x988.png 1536w, https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2025\/12\/Screenshot-2025-12-17-at-2.48.33-PM-1200x772.png 1200w, https:\/\/blog.lufamily.ca\/kang\/wp-content\/uploads\/sites\/3\/2025\/12\/Screenshot-2025-12-17-at-2.48.33-PM.png 1564w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/figure>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>We have several UPS (Uninterruptible Power Supply) units around the house. They are there to prevent power interruptions to networking equipment and computer servers. When you are into home automation, keeping these services up and running is almost essential. Previously this year, I noticed one of the UPS unit keeps on chirping and the body &hellip; <a href=\"https:\/\/blog.lufamily.ca\/kang\/2025\/12\/17\/ups-monitoring\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;UPS Monitoring&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[111],"tags":[67,28,172],"class_list":["post-3294","post","type-post","status-publish","format-standard","hentry","category-tech","tag-networking","tag-technology","tag-ups"],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p7V6i8-R8","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/posts\/3294","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/comments?post=3294"}],"version-history":[{"count":7,"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/posts\/3294\/revisions"}],"predecessor-version":[{"id":3302,"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/posts\/3294\/revisions\/3302"}],"wp:attachment":[{"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/media?parent=3294"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/categories?post=3294"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.lufamily.ca\/kang\/wp-json\/wp\/v2\/tags?post=3294"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}