1 /* phoenixcontact_modbus.c - Driver for PhoenixContact-QUINT UPS
2 *
3 * Copyright (C)
4 * 2017 Spiros Ioannou <sivann@inaccess.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 *
21 */
22
23 #include "main.h"
24 #include <modbus.h>
25
26 #define DRIVER_NAME "NUT PhoenixContact Modbus driver"
27 #define DRIVER_VERSION "0.01"
28
29 #define CHECK_BIT(var,pos) ((var) & (1<<(pos)))
30 #define MODBUS_SLAVE_ID 192
31
32 /* Variables */
33 static modbus_t *modbus_ctx = NULL;
34 static int errcount = 0;
35
36 static int mrir(modbus_t * arg_ctx, int addr, int nb, uint16_t * dest);
37
38 /* driver description structure */
39 upsdrv_info_t upsdrv_info = {
40 DRIVER_NAME,
41 DRIVER_VERSION,
42 "Spiros Ioannou <sivann@inaccess.com>\n",
43 DRV_BETA,
44 {NULL}
45 };
46
upsdrv_initinfo(void)47 void upsdrv_initinfo(void)
48 {
49 upsdebugx(2, "upsdrv_initinfo");
50
51 dstate_setinfo("device.mfr", "Phoenix Contact");
52 dstate_setinfo("device.model", "QUINT-UPS/24DC");
53
54 /* upsh.instcmd = instcmd; */
55 /* upsh.setvar = setvar; */
56 }
57
upsdrv_updateinfo(void)58 void upsdrv_updateinfo(void)
59 {
60 errcount = 0;
61
62 upsdebugx(2, "upsdrv_updateinfo");
63
64 uint16_t tab_reg[64];
65
66 mrir(modbus_ctx, 29697, 3, tab_reg);
67
68 status_init();
69
70 if (tab_reg[0])
71 status_set("OB");
72 else
73 status_set("OL");
74
75 if (tab_reg[2]) {
76 status_set("CHRG");
77 }
78
79 if (tab_reg[1]) {
80 status_set("LB"); /* LB is actually called "shutdown event" on this ups */
81 }
82
83 mrir(modbus_ctx, 29745, 1, tab_reg);
84 dstate_setinfo("output.voltage", "%d", (int) (tab_reg[0] / 1000));
85
86 mrir(modbus_ctx, 29749, 5, tab_reg);
87 dstate_setinfo("battery.charge", "%d", tab_reg[0]);
88 /* dstate_setinfo("battery.runtime",tab_reg[1]*60); */ /* also reported on this address, but less accurately */
89
90 mrir(modbus_ctx, 29792, 10, tab_reg);
91 dstate_setinfo("battery.voltage", "%f", (double) (tab_reg[0]) / 1000.0);
92 dstate_setinfo("battery.temperature", "%d", tab_reg[1] - 273);
93 dstate_setinfo("battery.runtime", "%d", tab_reg[3]);
94 dstate_setinfo("battery.capacity", "%d", tab_reg[8] * 10);
95 dstate_setinfo("output.current", "%f", (double) (tab_reg[6]) / 1000.0);
96
97 /* ALARMS */
98 mrir(modbus_ctx, 29840, 1, tab_reg);
99 alarm_init();
100 if (CHECK_BIT(tab_reg[0], 4) && CHECK_BIT(tab_reg[0], 5))
101 alarm_set("End of life (Resistance)");
102 if (CHECK_BIT(tab_reg[0], 6))
103 alarm_set("End of life (Time)");
104 if (CHECK_BIT(tab_reg[0], 7))
105 alarm_set("End of life (Voltage)");
106 if (CHECK_BIT(tab_reg[0], 9))
107 alarm_set("No Battery");
108 if (CHECK_BIT(tab_reg[0], 10))
109 alarm_set("Inconsistent technology");
110 if (CHECK_BIT(tab_reg[0], 11))
111 alarm_set("Overload Cutoff");
112 /* We don't use those low-battery indicators below.
113 * No info or configuration exists for those alarm low-bat
114 */
115 if (CHECK_BIT(tab_reg[0], 12))
116 alarm_set("Low Battery (Voltage)");
117 if (CHECK_BIT(tab_reg[0], 13))
118 alarm_set("Low Battery (Charge)");
119 if (CHECK_BIT(tab_reg[0], 14))
120 alarm_set("Low Battery (Time)");
121 if (CHECK_BIT(tab_reg[0], 16))
122 alarm_set("Low Battery (Service)");
123
124 if (errcount == 0) {
125 alarm_commit();
126 status_commit();
127 dstate_dataok();
128 }
129 else
130 dstate_datastale();
131
132 }
133
134 void upsdrv_shutdown(void)
135 __attribute__((noreturn));
136
upsdrv_shutdown(void)137 void upsdrv_shutdown(void)
138 {
139 fatalx(EXIT_FAILURE, "shutdown not supported");
140 }
141
upsdrv_help(void)142 void upsdrv_help(void)
143 {
144 }
145
146 /* list flags and values that you want to receive via -x */
upsdrv_makevartable(void)147 void upsdrv_makevartable(void)
148 {
149 }
150
upsdrv_initups(void)151 void upsdrv_initups(void)
152 {
153 int r;
154 upsdebugx(2, "upsdrv_initups");
155
156 modbus_ctx = modbus_new_rtu(device_path, 115200, 'E', 8, 1);
157 if (modbus_ctx == NULL)
158 fatalx(EXIT_FAILURE, "Unable to create the libmodbus context");
159
160 r = modbus_set_slave(modbus_ctx, MODBUS_SLAVE_ID); /* slave ID */
161 if (r < 0) {
162 modbus_free(modbus_ctx);
163 fatalx(EXIT_FAILURE, "Invalid modbus slave ID %d",MODBUS_SLAVE_ID);
164 }
165
166 if (modbus_connect(modbus_ctx) == -1) {
167 modbus_free(modbus_ctx);
168 fatalx(EXIT_FAILURE, "modbus_connect: unable to connect: %s", modbus_strerror(errno));
169 }
170
171 }
172
173
upsdrv_cleanup(void)174 void upsdrv_cleanup(void)
175 {
176 if (modbus_ctx != NULL) {
177 modbus_close(modbus_ctx);
178 modbus_free(modbus_ctx);
179 }
180 }
181
182 /* Modbus Read Input Registers */
mrir(modbus_t * arg_ctx,int addr,int nb,uint16_t * dest)183 static int mrir(modbus_t * arg_ctx, int addr, int nb, uint16_t * dest)
184 {
185 int r;
186 r = modbus_read_input_registers(arg_ctx, addr, nb, dest);
187 if (r == -1) {
188 upslogx(LOG_ERR, "mrir: modbus_read_input_registers(addr:%d, count:%d): %s (%s)", addr, nb, modbus_strerror(errno), device_path);
189 errcount++;
190 }
191 return r;
192 }
193