Desc: How to make a new driver to support another UPS File: new-drivers.txt Date: 14 July 2003 Auth: Russell Kroll <rkroll@exploits.org> Smart vs. Contact-closure ========================= If your UPS only does contact closure readings, then go straight to the genericups.txt document for information on adding support. It's a lot easier to add a few lines to a header file than it is to create a whole new driver. Overall concept =============== The basic design of drivers is simple. main.c handles most of the work for you. You don't have to worry about arguments, config files, or anything else like that. Your only concern is talking to the hardware and providing data to the outside world. Skeleton driver =============== Familiarize yourself with the design of skel.c in the drivers directory. It shows a few examples of the functions that main will call to obtain updated information from the hardware. Essential functions =================== upsdrv_initups -------------- Open the port (device_path) and do any low-level things that it may need to start using that port. If you have to set DTR or RTS on a serial port, do it here. Don't do any sort of hardware detection here, since you may be going into upsdrv_shutdown next. upsdrv_initinfo --------------- Try to detect what kind of UPS is out there, if any, assuming that's possible for your hardware. If there is a way to detect that hardware and it doesn't appear to be connected, display an error and exit. This is usually a good place to create variables like ups.mfr, ups.model, ups.serial, and other "one time only" items. upsdrv_updateinfo ----------------- Poll the hardware, and update any variables that you care about monitoring. Use dstate_setinfo() to store the new values. Do at most one pass of the variables. You MUST return from this function or upsd will be unable to read data from your driver. main will call this function at regular intervals. If your UPS hardware is slow or brain-damaged, you might use an odd/even approach to this function. Half the time, set a flag and return. The other half, clear the flag and actually do the poll. upsdrv_shutdown --------------- Do whatever you can to make the UPS power off the load but also return after the power comes back on. You may use a different command that keeps the UPS off if the user has requested that with a configuration setting. You should attempt the UPS shutdown command even if the UPS detection fails. If the UPS does not shut down the load, then the user is vulnerable to a race if the power comes back on during the shutdown process. Flags for main.c ================ Set experimental_driver to 1 if your driver is potentially broken. This will trigger a warning when it starts so the user doesn't take it for granted. Setting broken_driver to 1 will cause main to print an error and exit. This is only used during conversions of the driver core to keep users from using drivers which have not been converted. Drivers in this state will be removed from the tree after some period if they are not fixed. Data types ========== To be of any use, you must supply STATUS data. That is the minimum needed to let upsmon do its job. Whenever possible, you should also provide anything else that can be monitored by the driver. Some obvious things are the manufacturer name and model name, voltage data, and so on. If you can't figure out some value automatically, use the ups.conf options to let the user tell you. This can be useful when a driver needs to support many similar hardware models but can't probe to see what is actually attached. Manipulating the data ===================== All status data lives in structures that are managed by the dstate functions. All access and modifications must happen through those functions. Any other changes are forbidden, as they will not pushed out as updates to things like upsd. Adding variables ---------------- dstate_setinfo("ups.model", "Mega-Zapper 1500"); Many of these functions take format strings, so you can build the new values right there: dstate_setinfo("ups.model", "Mega-Zapper %d", rating); Setting flags ------------- Some variables have special properties. They can be writable, and some are strings. The ST_FLAG_* values can be used to tell upsd more about what it can do. dstate_setflags("input.transfer.high", ST_FLAG_RW); Status data =========== UPS status flags like on line (OL) and on battery (OB) live in ups.status. Don't manipulate this by hand. There are functions which will do this for you. status_init() - before doing anything else status_set(val) - add a status word (OB, OL, etc) status_commit() - push out the update Possible values for status_set: OL - On line OB - On battery (inverter is providing load power) LB - Low battery RB - The battery needs to be replaced BYPASS - UPS bypass circuit is active - no battery protection is available CAL - UPS is currently performing runtime calibration (on battery) OFF - UPS is offline and is not supplying power to the load OVER - UPS is overloaded TRIM - UPS is trimming incoming voltage (called "buck" in some hardware) BOOST - UPS is boosting incoming voltage Anything else will not be recognized by the usual clients. Coordinate with me before creating something new, since there will be duplication and ugliness otherwise. Note: upsd injects "FSD" by itself following that command by a master upsmon process. Drivers must not set that value. Staleness control ================= - dstate_dataok() You must call this regularly if polls are succeeding. A good place to call this is the bottom of upsdrv_updateinfo(). If you're not talking to a polled UPS, then you must ensure that it is still out there and is alive before calling dstate_dataok(). Even if nothing is changing, you should still "ping" it or do something else to ensure that it is really available. If the attempts to contact the UPS fail, you must call dstate_datastale() to inform the server and clients. - dstate_datastale() You must call this if your status is unusable. A good technique is to call this before exiting prematurely from upsdrv_updateinfo(). Serial port handling ==================== The two open_serial functions will handle locking for you. You should use them unless you have really strange needs in your driver. - void open_serial_simple(const char *port, speed_t speed, int flags) This will open the named serial port at a given speed and will set up the control lines according to 'flags'. This does almost no manipulation of the tty layer and probably will not work for RS-232 data unless you fix the port up yourself. - void open_serial(const char *port, speed_t speed) This is like open_serial_simple, but it configures the tty layer for data passing. You will probably need to use this one for any driver that expects to send and receive normal serial data. - int upsrecv(char *buf, int buflen, char endchar, const char *ignchars) This will wait up to 3 seconds for data to arrive from the UPS. It ignores any characters in ignchars, and returns when endchar is received. Data is stored in buf, up to buflen bytes. - int upsrecvchars(char *buf, int buflen) This is a simpler receive function to bring in a fixed number of bytes. A 3 second timeout is also used, but no other parsing of characters occurs. - int upssendchar(char data) Send a single character to the UPS. - int upssend(const char *fmt, ...) Send a formatted buffer to the UPS. - void upsflushin(int expected, int debugit, const char *ignchars) Use this to clear out the input queue. This can be useful when the hardware is known to return unexpected data in between polls. Variable names ============== PLEASE don't make up new variables and commands just because you can. The new dstate functions give us the power to create just about anything, but that is a privilege and not a right. Imagine the mess that would happen if every developer decided on their own way to represent a common status element. Check new-names.txt first to find the closest fit. If nothing matches, contact the upsdev list or mail me directly, and we'll figure it out. Patches which introduce unlisted names may be modified or dropped. Message passing support ======================= See commands.txt. Enumerated types ================ If you have a variable that can have several specific entries, it is enumerated. You should add each one to make it available to the client: dstate_addenum("input.transfer.low", "92"); dstate_addenum("input.transfer.low", "95"); dstate_addenum("input.transfer.low", "99"); dstate_addenum("input.transfer.low", "105"); Writable strings ================ Strings that may be changed by the client should have the ST_FLAG_STRING flag set, and a maximum length byte set in the auxdata. dstate_setinfo("ups.id", "Big UPS"); dstate_setflags("ups.id", ST_FLAG_STRING | ST_FLAG_RW); dstate_setaux("ups.id", 8); If the variable is not writable, don't bother with the flags or the auxiliary data. It won't be used. Instant commands ================ If your hardware can support a command, register it. dstate_addcmd("load.on");