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