/*
 * $RCSfile: spyder.c,v $  $Revision: 1.6 $  $Name:  $
 * $Id: spyder.c,v 1.6 2007/09/09 18:16:26 bpaauwe Exp $
 * $Author: bpaauwe $
 * $Date: 2007/09/09 18:16:26 $
 * ----------------------------------------------------------------------------
 *
 *  Copyright (c) Bob Paauwe (2007)
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT
 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 *  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 *  more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * ----------------------------------------------------------------------------
 *
 *  Description:
 *    A program that tries to determine what devices are linked to what.
 * ----------------------------------------------------------------------------
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include <time.h>
#include "libilib/ilib.h"
#include "device.h"

#define VERSION "spyder version 0.2.20a Copyright (C) 2007, Bob Paauwe"

typedef struct _responders_t {
	char *name;
	link_record_t link;
	struct _responders_t *next;
} responders_t;

typedef struct _group_t {
	char *name;
	int group;
	responders_t *list;
	struct _group_t *next;
} group_t;

device_t *read_file(char *fname);
device_t *find_device(device_t *list, unsigned char *id);
link_record_t *find_link(device_t *dev, unsigned char *id, int group);
void print_link(link_record_t *link);
group_t *add_group(group_t *group, device_t *dev, device_t *resp,
		link_record_t *link, int gnum);
device_t *get_device(device_t *dev_list, char *name);
void print_devices(device_t *dev_list);
void add_link(device_t *dev, char *mst, int group, char *resp, char *on,
		char *ramp);
void get_plc_links(ilib_t *iplc, device_t *dev);

void usage(char *prog)
{
	printf("usage: %s [-v] [-h] [-G] [-U] [-l]\n", prog);
	printf("       %s -a <master> <group> <responder> <on-level> <ramp>\n",
			prog);
	printf("\n");
	printf("       %s -G  Display all groups (scenes) in the device file.\n");
	printf("       %s -U  [device_name] Update selected device or all devices.\n");
	printf("       %s -l  Display all links in the PLC.\n");
	printf("       %s -a  Add a link.\n");
	printf("       %s -v  Display version.\n");
	printf("       %s -h  This help message.\n");
}

main (int argc, char **argv)
{
	ilib_t *iplc;
	device_t *devices, *dev_ptr;
	char f_name[256];
	char buffer[15];
	unsigned char version;
	int i;
	unsigned short response;
	link_record_t entry;

	strcpy (f_name, "device_info.txt");
	devices = read_file(f_name);
	if (devices == NULL) {
		printf("No known devices, can't do anything\n");
		return -2;
	}

	if (argc > 0) {
		for (i = 1; i < argc; i++) {
			if (argv[i][0] == '-') {
				switch (argv[i][1]) {
					case 'v':
						printf("%s\n", VERSION);
						return 0;
						break;
					case 'h':
						usage(argv[0]);
						print_devices(devices);
						return 0;
						break;
					case 'U': /* Update devices */
						printf("Update: %d argc = %d\n", i, argc);
						i++;
						if (i < argc) {
							dev_ptr = get_device(devices, argv[i]);
						} else {
							dev_ptr = devices;
						}
						update_devices(f_name, dev_ptr);
						return 0;
						break;
					case 'G':
						/*
						 * Using the device file, display all the
						 * groups (or scenes).
						 */
						list_groups(f_name);
						return 0;
						break;
					case 'a':
						/* need a command line way to add links to a group */
						/*
						 * -a mst group resp on ramp
						 */
						if ( (i + 5) < (argc - 1)) {
							/* need more data */
							usage(argv[0]);
							printf("i = %d, argc = %d\n", i, argc);
							return 3;
						}
						add_link(devices, argv[i+1], atoi(argv[i+2]), argv[i+3],
								argv[i+4], argv[i+5]);
						save_device_info(f_name, devices);
						return 0;
						break;
					case 'l':
						iplc = ilib_open(USE_HIDDEV, "/dev/hiddev0");
						/* iplc = ilib_open(USE_IPLC, "/dev/usb/iplc0"); */
						printf("PLC has %d links\n", iplc_count_links(iplc));
						ilib_close(iplc);
						return 0;
						break;
					case 'x':
						save_device_info(f_name, devices);
						return 0;
						break;
					default:
						usage(argv[0]);
						print_devices(devices);
						return 0;
						break;
				}
			}
		}
	}

	/*
	 * Default if no options are specified is to build a clean device
	 * file by querying all the devices.
	 */

	query_devices(f_name);
}


int query_devices(char *f_name)
{
	ilib_t *iplc;
	device_t *devices, *dev_ptr;
	char buffer[15];
	unsigned char version;
	int i;
	unsigned short response;
	link_record_t entry;
	char *backup_f_name;

	if ((iplc = ilib_open(USE_HIDDEV, "/dev/hiddev0")) != NULL) {
	/* if ((iplc = ilib_open(USE_IPLC, "/dev/usb/iplc0")) != NULL) { */
		/* Send some test commands */
		if (ibios_get_version(iplc, &version, buffer) == 0) {
			printf("PLC ID: %s   Firmware version %02x\n", buffer, version);
		} else {
			printf("Command failed\n");
		}

		/*
		 * Start by reading the data in the file. This will give us
		 * a list of devices and some (possible) information about
		 * links.
		 */
		devices = read_file(f_name);
		if (devices == NULL) {
			printf("No known devices, can't do anything\n");
			return -2;
		}
		/* backup existing file */
		backup_f_name = (char *)malloc(strlen(f_name) + 5);
		strcpy (backup_f_name, f_name);
		strcat (backup_f_name, ".bak");
		save_device_info(backup_f_name, devices);
		free(backup_f_name);

		/*
		 * Now look at each device and read it's link table.
		 */
		dev_ptr = devices;
		while (dev_ptr) {
			unsigned char flag;

			if (dev_ptr->type == 1) { /* PLC, skip it for now */
				/* If it's this PLC, read the links directly from memory */
				if (is_plc(iplc, dev_ptr->id)) {
					get_plc_links(iplc, dev_ptr);
				}
				dev_ptr = dev_ptr->next;
				continue;
			} else {
				dev_ptr = dev_ptr->next;
				continue;
			}

			get_device_info (iplc, dev_ptr);

			if ((dev_ptr->status & DEVICE_VERIFIED) != DEVICE_VERIFIED) {
				printf("Reading links for device %s (%02x.%02x.%02x)  [%x]\n",
						dev_ptr->name,
						dev_ptr->id[0], dev_ptr->id[1], dev_ptr->id[2],
						dev_ptr->status);
				i = 0;
				dev_ptr->link_cnt = 0;
				do {
					memset(&entry, 0, sizeof(link_record_t));
					get_device_link(iplc, dev_ptr->id, i, &entry);

					flag = entry.flags;
					printf("  %3d = %s %d %02x.%02x.%02x 0x%02x "
							"0x%02x 0x%02x  / 0x%02x\n", i,
							(entry.flags & 0x40) ? "MST" : "SLV",
							entry.group_number,
							entry.device_id[0],
							entry.device_id[1],
							entry.device_id[2],
							entry.data1,
							entry.data2,
							entry.data3, flag);

					if (flag & 0x80) { /* Record in use */
						memcpy(&(dev_ptr->links[dev_ptr->link_cnt]), &entry,
								sizeof(link_record_t));
						dev_ptr->link_cnt++;
					}
					i++;
				} while ((flag != 0) && (i < MAX_LINKS));
				dev_ptr->status = DEVICE_OK;
				save_device_info("device_info.txt", devices);
			}
			dev_ptr = dev_ptr->next;
		}

		/*
		 * Scan all the link tables and look for device id's that aren't
		 * in the list.
		 */

		printf("Scanning device tables for unknown devices\n");
		dev_ptr = devices;
		while (dev_ptr) {
			if (dev_ptr->status & DEVICE_OK) {
				for (i = 0; i < dev_ptr->link_cnt; i++) {
					if (!find_device(devices, dev_ptr->links[i].device_id)) {
						printf("%s has link to unknown device %02x.%02x.%02x\n",
								dev_ptr->name,
								dev_ptr->links[i].device_id[0],
								dev_ptr->links[i].device_id[1],
								dev_ptr->links[i].device_id[2]);

						response = insteon_send_ping(iplc,
								dev_ptr->links[i].device_id, buffer, 6);

						if (response == 0x1000) {
							printf("   Device exist and should be added\n");
						} else {
							/* Try re-reading the link, just in case */
							memset(&entry, 0, sizeof(link_record_t));
							get_device_link(iplc, dev_ptr->id, i, &entry);
							if (memcmp(&entry, &(dev_ptr->links[i]),
										sizeof(link_record_t)) != 0) {
								memcpy(&(dev_ptr->links[i]), &entry,
										sizeof(link_record_t));
								printf("   Link updated\n");
							} else {
								printf("   Bad device link, delete it.\n");
							}
						}
					}
				}
			}
			dev_ptr = dev_ptr->next;
		}
		save_device_info(f_name, devices);

	} else {
		printf("Open failed, aborting....\n");
		return -1;
	}
	return 0;
}



/*
 * list_groups
 *
 * Display all the groups in an easy to read format.
 */
int list_groups(char *fname)
{
	device_t *devices;
	device_t *dev;
	device_t *resp;
	link_record_t *link;
	int i;
	group_t *group = NULL;
	responders_t *rnode;


	devices = read_file(fname);
	if (devices == NULL) {
		printf("No known devices, can't do anything\n");
		return 1;
	}

	/*
	 * Search for every master link record, output it and verify that
	 * there is a coresponding record in the responder.
	 */
	dev = devices;
	while (dev) {
		for (i = 0; i < dev->link_cnt; i++) {
			if (dev->links[i].flags & 0x40) {
				resp = find_device(devices, dev->links[i].device_id);
				/* Find matching link in responder */
				if (resp) {
					link = find_link(resp, dev->id, dev->links[i].group_number);
					group = add_group(group, dev, resp, link,
							dev->links[i].group_number);
				} else {
					printf("Unkown device in link table\n");
				}

#if 0
				if (link) {
					printf("%-25.25s -> %-25.25s %2d level = %3d ramp = %d\n",
							dev->name,  resp->name,
							dev->links[i].group_number,
							link->data1, link->data2);
				} else {
					printf("%-25.25s -> %-25.25s %2d Unmatched\n",
							dev->name, resp->name,
							dev->links[i].group_number);
				}
#endif
			}
		}
		dev = dev->next;
	}

	while (group) {
		rnode = group->list;
		printf("%s  GROUP:%d\n", group->name, group->group);
		while (rnode) {
			printf("   %-25.25s ON %3d%% @ %2d\n",
					rnode->name,
					((rnode->link.data1 * 100) / 255), rnode->link.data2);
			rnode = rnode->next;
		}
		printf("\n");
		group = group->next;
	}

}

/*
 * update_devices
 *
 * Use the specified file and update all the device link tables to match.
 */
int update_devices(char *fname, device_t *devices)
{
	ilib_t *iplc;
	unsigned char buffer[15];
	unsigned char version;
	device_t *dev;
	link_record_t entry;
	float eta;
	int i;
	unsigned long start;

	if (devices == NULL) {
		printf("Device list is NULL, aborting update.\n");
		return 0;
	}

	if ((iplc = ilib_open(USE_HIDDEV, "/dev/hiddev0")) != NULL) {
	/* if ((iplc = ilib_open(USE_IPLC, "/dev/usb/iplc0")) != NULL) { */
		/* Send some test commands */
		if (ibios_get_version(iplc, &version, buffer) == 0) {
			printf("PLC ID: %s   Firmware version %02x\n", buffer, version);
		} else {
			printf("Command failed\n");
		}


		dev = devices;
		while (dev) {
			if ((dev->type == 0x0000) || (dev->type == 0x0001)) {
				printf("Skipping PLC programming.\n");
			} else {
				/*
				 * 8 bytes per link
				 * (MAX_LINKS - link_cnt) bytes extra
				 *
				 * estimate is about 2 seconds per byte to write.
				 */
				eta = ((dev->link_cnt * 8) + (MAX_LINKS - dev->link_cnt))
					* 1.82;
				eta += (dev->link_cnt * 8) * 0.91;
				printf("Programming %s, estimated time ", dev->name);
				if (eta > 60) {
					printf("%4.1f minutes\n", (eta / 60.0));
				} else {
					printf("%2.0f seconds\n", eta);
				}

				start = time(NULL);
				for (i = 0; i < dev->link_cnt; i++) {
					printf("  Programming: ");
					print_link(&(dev->links[i]));
					set_device_link(iplc, dev->id, i, &(dev->links[i]));

					/* Read link we just wrote and verify it */
					get_device_link(iplc, dev->id, i, &entry);
					if (memcmp(&entry, &(dev->links[i]), sizeof(link_record_t))
							!= 0) {
						printf("Link verification failed!\n");
						set_device_link(iplc, dev->id, i, &(dev->links[i]));
					}
					usleep(10000);
				}

				/*
				 * If we knew how many links were in the table before we
				 * started programming, then we'd know if we needed to 
				 * clear any.  This might be a bit faster than clearing
				 * out all the remaining records.
				 */
				for (; i < MAX_LINKS; i++) {
					/* Clear link flag byte */
					clear_device_link(iplc, dev->id, i);
				}
				printf("Elapsed time: %4.1f minutes.\n",
						(float)(time(NULL) - start) / 60.0);
			}
			dev = dev->next;
		}
	} else {
		printf("Open failed, aborting....\n");
		return -1;
	}
}

/*
 * add_link
 *
 * Add link to both the database (file) and the two devices link tables.
 *
 * This tries to be intelligent when reading the parameters. The devices
 * can be either device id's or device names.  The values can be hex or
 * decimal.
 */
void add_link(device_t *devices,
		char *mst, int group, char *resp,
		char *on, char *ramp)
{
	ilib_t *iplc;
	device_t *master, *responder;
	int on_level, ramp_rate;

	/* if ((iplc = ilib_open(USE_IPLC, "/dev/usb/iplc0")) == NULL) { */
	if ((iplc = ilib_open(USE_HIDDEV, "/dev/hiddev0")) == NULL) {
		printf("Failed to open PLC\n");
		return;
	}

	master = find_device(devices, mst);
	responder = find_device(devices, resp);

	on_level = atoi(on);
	if (on_level == 0) {
		on_level = ahtoi(on);
	}

	ramp_rate = atoi(ramp);
	if (ramp_rate == 0) {
		ramp_rate = ahtoi(ramp);
	}

	/* Add link to master's table */
	master->links[master->link_cnt].flags = 0xe2;
	master->links[master->link_cnt].group_number = group;
	memcpy(master->links[master->link_cnt].device_id, responder->id, 3);
	master->links[master->link_cnt].data1 = on_level;
	master->links[master->link_cnt].data2 = ramp_rate;
	master->links[master->link_cnt].data3 = group;
	set_device_link(iplc, master->id, master->link_cnt,
			&(master->links[master->link_cnt]));
	master->link_cnt++;

	/* Add link to responder's table */
	responder->links[responder->link_cnt].flags = 0xa2;
	responder->links[responder->link_cnt].group_number = group;
	memcpy(responder->links[responder->link_cnt].device_id, master->id, 3);
	responder->links[responder->link_cnt].data1 = on_level;
	responder->links[responder->link_cnt].data2 = ramp_rate;
	responder->links[responder->link_cnt].data3 = group;
	set_device_link(iplc, responder->id, responder->link_cnt,
			&(responder->links[responder->link_cnt]));
	responder->link_cnt++;

	ilib_close(iplc);
	return;
}


/*
 * get_device_link
 *
 * link_num is zero based.
 */
int get_device_link(ilib_t *iplc, unsigned char *dev_id,
		int link_num, link_record_t *entry)
{
	unsigned short laddr;
	int ret;
	int i;

	memset(entry, 0, sizeof(link_record_t));

	/* Link data base grows downward from 0x0ff8 and are 8 bytes each */
	laddr = 0x0ff8 - (link_num * 8);

	if ((ret = insteon_read_byte(iplc, dev_id, laddr++)) != -1) {
		entry->flags = ret & 0xff;
		if (!(ret & 0x80)) { /* record not in use */
			return 0;
		} else if ((ret & 0x02) == ret) {
			/* record was in use, but now deleted */
			entry->group_number = 0;
			entry->device_id[0] = 0;
			entry->device_id[1] = 0;
			entry->device_id[2] = 0;
			entry->data1 = 0;
			entry->data2 = 0;
			entry->data3 = 0;
			return entry->flags;
		}

		/* Read link group number */
		if ((ret = insteon_read_byte(iplc, dev_id, laddr++)) != -1) {
			entry->group_number = ret & 0xff;
		} else {
			return -1;
		}

		/* Read link device id */
		for (i = 0; i < 3; i++) {
			if ((ret = insteon_read_byte(iplc, dev_id, laddr++)) != -1) {
				entry->device_id[i] = ret & 0xff;
			} else {
				return -1;
			}
		}

		/* Read link data 1 byte */
		if ((ret = insteon_read_byte(iplc, dev_id, laddr++)) != -1) {
			entry->data1 = ret & 0xff;
		} else {
			return -1;
		}

		/* Read link data 2 byte */
		if ((ret = insteon_read_byte(iplc, dev_id, laddr++)) != -1) {
			entry->data2 = ret & 0xff;
		} else {
			return -1;
		}

		/* Read link data 3 byte */
		if ((ret = insteon_read_byte(iplc, dev_id, laddr++)) != -1) {
			entry->data3 = ret & 0xff;
		} else {
			return -1;
		}
	} else {
		return -1;
	}

	return entry->flags;
}


/*
 * set_device_link
 *
 * write a new device link into a device's link table.
 */
int set_device_link(ilib_t *iplc, unsigned char *dev_id,
		int link_num, link_record_t *entry)
{
	unsigned short laddr;
	int ret;

	/* Link data base grows downward from 0x0ff8 and are 8 bytes each */
	laddr = 0x0ff8 - (link_num * 8);

	if ((ret = insteon_write_byte(iplc, dev_id, laddr++, entry->flags)) == -1) {
		printf("set_device_link: Failed to send message to %02x.%02x.%02x\n",
				dev_id[0], dev_id[1], dev_id[2]);
		return 0;
	}

	ret = 0;
	ret |= insteon_write_byte(iplc, dev_id, laddr++, entry->group_number);
	ret |= insteon_write_byte(iplc, dev_id, laddr++, entry->device_id[0]);
	ret |= insteon_write_byte(iplc, dev_id, laddr++, entry->device_id[1]);
	ret |= insteon_write_byte(iplc, dev_id, laddr++, entry->device_id[2]);
	ret |= insteon_write_byte(iplc, dev_id, laddr++, entry->data1);
	ret |= insteon_write_byte(iplc, dev_id, laddr++, entry->data2);
	ret |= insteon_write_byte(iplc, dev_id, laddr++, entry->data3);
	if (ret == -1) {
		printf("set_device_link: Failed to write link %d to %02x.%02x.%02x\n",
				link_num, dev_id[0], dev_id[1], dev_id[2]);
	}
	return ret;
}


/*
 * clear_device_link
 *
 * Clear a device link in a device's link table.
 */
int clear_device_link(ilib_t *iplc, unsigned char *dev_id, int link_num)
{
	unsigned short laddr;
	int ret;

	/* Link data base grows downward from 0x0ff8 and are 8 bytes each */
	laddr = 0x0ff8 - (link_num * 8);

	/* Write the record has been used before bit in the flag */
	if ((ret = insteon_write_byte(iplc, dev_id, laddr, 0x00)) == -1) {
		printf("clear_device_link: Failed to clear link # %d on "
				"%02x.%02x.%02x\n", link_num, dev_id[0], dev_id[1], dev_id[2]);
		return -1;
	}
	return 0;
}

/*
 * find_link
 *
 * Locate a link record in the device's link table that matches the given
 * device id and group.
 */
link_record_t *find_link(device_t *dev, unsigned char *id, int group)
{
	int i;

	for (i = 0; i < dev->link_cnt; i++) {
		if (memcmp(dev->links[i].device_id, id, 3) == 0) {
			if (dev->links[i].group_number == group) {
				if ((dev->links[i].flags & 0x40) == 0) {
					return  &dev->links[i];
				}
			}
		}
	}

	return NULL;
}


/*
 * add_group
 *
 * Add a new entry into the group list. This may add a new group or
 * add a new responder to an existing group list.
 */
group_t *add_group(group_t *group, device_t *dev, device_t *resp,
		link_record_t *link, int gnum)
{
	group_t *gnode;
	responders_t *rnode;

	if (group == NULL) {
		/* empty list */
		gnode = (group_t *)malloc(sizeof(group_t));
	} else {
		/* Search for match */
		gnode = group;
		while (gnode) {
			if (strcmp(gnode->name, dev->name) == 0) {
				if (gnode->group == gnum) {
					/* Match, add new responder */
					rnode = (responders_t *)malloc(sizeof(responders_t));
					rnode->name = resp->name;
					if (link) {
						memcpy(&(rnode->link), link, sizeof(link_record_t));
					} else {
						memset(&(rnode->link), 0, sizeof(link_record_t));
					}
					rnode->next = gnode->list;
					gnode->list = rnode;
					return group;
				}
			}
			gnode = gnode->next;
		}
		/* No match found, add new node */
		gnode = (group_t *)malloc(sizeof(group_t));
	}

	gnode->name = dev->name;
	gnode->group = gnum;
	gnode->next = group;
	gnode->list = (responders_t *)malloc(sizeof(responders_t));
	gnode->list->name = resp->name;
	if (link) {
		memcpy(&(gnode->list->link), link, sizeof(link_record_t));
	} else {
		memset(&(gnode->list->link), 0, sizeof(link_record_t));
	}
	gnode->list->next = NULL;

	return gnode;
}


/*
 * get_device_info
 *
 * Query a device for it firmware revision.
 */
int get_device_info(ilib_t *iplc, device_t *dev_ptr)
{
	unsigned short response;
	unsigned char buffer[15];

	dev_ptr->status &= ~(DEVICE_UNREACHABLE | DEVICE_OK);
	response = insteon_send_ping(iplc, dev_ptr->id, buffer, 10);

	printf("  %s Rev = %d.%d  0x%02x\n",
			device_type_name(buffer[5], buffer[6]),
			(buffer[7] >> 4), (buffer[7] & 0x0f), buffer[9]);
	if (response != 0x1000) {
		printf("  No response from device, skipping. (0x%04x)\n", response);
		dev_ptr->status |= DEVICE_UNREACHABLE;
		dev_ptr->link_cnt = 0;
	} else {
		dev_ptr->status |= DEVICE_OK;
		dev_ptr->version = buffer[7];
		dev_ptr->type = (buffer[5] << 8) | buffer[6];
	}
	return 0;
}


/*
 * Return a pointer to the device node that matches the passed in device
 * name.
 */
device_t *get_device(device_t *dev_list, char *name)
{
	device_t *dev;

	while (dev_list) {
		if (strncasecmp(name, dev_list->name, strlen(name)) == 0) {
			dev = (device_t *)malloc(sizeof(device_t));
			memcpy(dev, dev_list, sizeof(device_t));
			dev->next = NULL;
			return dev;
		}
		dev_list = dev_list->next;
	}

	return NULL;
}


/*
 * find_device
 *
 * Given a device id, check the list to see if it exist. Return the pointer
 * to the device.
 */
device_t *find_device(device_t *dev_list, unsigned char *name)
{
	device_t *dev;
	unsigned char id[3];

	/* Check for matching id */
	dev = dev_list;
	while (dev) {
		if (memcmp(name, dev->id, 3) == 0) {
			return dev;
		}
		dev = dev->next;
	}

	/* Check for matching id */
	dev = dev_list;
	while (dev) {
		memcpy(id, str_id_to_hex(name), 3);
		if (memcmp(id, dev->id, 3) == 0) {
			return dev;
		}
		dev = dev->next;
	}

	/* Check for matching name */
	dev = dev_list;
	while (dev) {
		if (strncasecmp(name, dev->name, strlen(name)) == 0) {
			return dev;
		}
		dev = dev->next;
	}

	return NULL;
}

void print_devices(device_t *dev_list)
{
	while (dev_list) {
		printf("%-25.25s  [%02x.%02x.%02x]  %-20.20s Rev:%d.%d\n",
				dev_list->name,
				dev_list->id[0], dev_list->id[1], dev_list->id[2],
				device_type_name((dev_list->type & 0xff00) >> 8, dev_list->type & 0xff),
				(dev_list->version & 0xf0) >> 4, (dev_list->version & 0x0f));
		dev_list = dev_list->next;
	}
}

/*
 * get_plc_links
 *
 * This reads the PLC's link database. It has to know about the strange
 * linked thread format that the links are stored in the PCL because there
 * isn't any easy way to return individual entries.
 */
void get_plc_links(ilib_t *iplc, device_t *dev)
{
	unsigned int start;
	unsigned int link;
	int i;
	unsigned char link_buf[10];
	unsigned char lsb;

	start = 0x10000;
	dev->link_cnt = 0;

	for (i = 0; i < 128; i++) {
		start -= 8;
		ibios_read_memory(iplc, start, 8, link_buf);
		link = link_buf[0] * 256 + (link_buf[1] & 0xf8);
		lsb = (link_buf[1] & 0x01) | (i * 2);

		while (link_buf[0] != 0) {
			dev->links[dev->link_cnt].flags = (link_buf[1] & 0x04) << 4;
			dev->links[dev->link_cnt].group_number = link_buf[4];
			dev->links[dev->link_cnt].device_id[0] = link_buf[3];
			dev->links[dev->link_cnt].device_id[1] = link_buf[2];
			dev->links[dev->link_cnt].device_id[2] = lsb;
			dev->links[dev->link_cnt].data1 = link_buf[5];
			dev->links[dev->link_cnt].data2 = link_buf[6];
			dev->links[dev->link_cnt].data3 = link_buf[7];
			/*
			printf("link %03d ", i);
			print_link(&(dev->links[dev->link_cnt]));
			*/
			dev->link_cnt++;
			if (dev->link_cnt == MAX_LINKS) {
				printf("More than MAX_LINKS links in PLC\n");
				return;
			}

			/* move to next link in thread */
			ibios_read_memory(iplc, link, 8, link_buf);
			link = link_buf[0] * 256 + (link_buf[1] & 0xf8);
			lsb = (link_buf[1] & 0x01) | (i * 2);
		}
	}
}


/*
 * Print a link record.
 */
void print_link(link_record_t *link)
{
	if (link) {
		printf("%s %d %02x.%02x.%02x 0x%02x 0x%02x 0x%02x  / 0x%02x\n",
				(link->flags & 0x40) ? "MST" : "SLV",
				link->group_number,
				link->device_id[0],
				link->device_id[1],
				link->device_id[2],
				link->data1,
				link->data2,
				link->data3, link->flags);
	}
}
