//
//  ModBusKit.mm
//  ModBusKit
//
//  Created by Matthew Butch on 03/02/08.
//  Copyright 2008 Volitans Software and R Engineering, Inc.
/*
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2 of the License, or (at your option) any later version.
 
 This library 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
 Lesser General Public License for more details.
 
 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA.
 
 These library of functions are designed to enable a program send and
 receive data from a device that communicates using the Modbus protocol.
 */
//
//  This is an Objective-C interface to libmodbus

#import "ModBusKit.h"


@implementation ModBusKit

- (void)initialize
{
	NSDictionary *infoDict = [[NSBundle bundleForClass: [self class]] infoDictionary];
	compatibilityVersionNumber=[[infoDict objectForKey: @"VSCompatibilityVersion"] intValue];
	modbuskitVersionString=[NSString stringWithFormat:@"%@ (%@).",
							[infoDict objectForKey:@"CFBundleShortVersionString"], [infoDict objectForKey:@"CFBundleVersion"]];
	//libmodbusVersionString=[[NSBundle mainBundle] objectForInfoDictionaryKey:@"VSlibmodbusVersion"];
	libmodbusVersionString=[infoDict objectForKey:@"VSlibmodbusVersion"];
	outputDebugMessages=FALSE;
}

- (id)init
{
	if ((self = [super init]) != nil) 
	{
		[self initialize];
		lmbWrap= new libmodbusWrapper();
		/*if (someError) 
		{
            [self release];
            self = nil; 
        }*/
    }
    return self;
}

- (id) initWithSerialPort: (NSString *)serialLocation baudRate: (int) baudRate parity: (int) parity dataBits: (int) dataBits stopBits: (int) stopBits
{
	[self init];

	if ([self setupSerialPort:serialLocation baudRate: baudRate parity: parity dataBits: dataBits stopBits: stopBits])
		return (self);

	return(NULL);
}

- (id)initWithTCP: (NSString *)ipAddress port :(int)port
{
	[self init];

	if ([self setupTCP: ipAddress port: port])
		return(self);
	
	return (NULL);
}

- (int)compatibilityVersion
{
	return(compatibilityVersionNumber);
}

- (NSString *)modbuskitVersion
{
	return(modbuskitVersionString);
}

- (NSString *)libmodbusVersion
{
	return(libmodbusVersionString);
}

- (BOOL) setupSerialPort: (NSString *)serialLocation baudRate: (int) baudRate parity: (int) parity dataBits: (int) dataBits stopBits: (int) stopBits;
{
	if (address!=NULL)
		[address release];
	address=[[NSString alloc] initWithString: serialLocation];
	return(lmbWrap->lmbInitSerial([serialLocation cStringUsingEncoding: NSASCIIStringEncoding], baudRate, parity, dataBits, stopBits));
}

- (BOOL)setupTCP: (NSString *)ipAddress port: (int)port
{
	if (address!=NULL)
		[address release];
	address=[[NSString alloc] initWithString: ipAddress];
	return(lmbWrap->lmbInitTCP([ipAddress cStringUsingEncoding: NSASCIIStringEncoding], port));
}

- (int)openConnection
{
	if (outputDebugMessages)
		NSLog(@"Opening connection...");
	return(lmbWrap->lmbConnect());
}

- (void)closeConnection
{
	if (outputDebugMessages)
		NSLog(@"Closing connection...");
	lmbWrap->lmbDisconnect();
}

- (NSArray *) readDiscreteInput: (int) slaveID startAddress: (int) startAddress inputCount: (int) inputCount;
{
	uint8_t *data;
	int i;
	NSMutableArray *dataArray;
//	NSArray *returnArray;
	
	if (outputDebugMessages)
		NSLog(@"Reading Discrete Input registers...");
	
	data=(uint8_t *) malloc(inputCount * sizeof(uint8_t));
	dataArray=[[NSMutableArray alloc] initWithCapacity: inputCount];

	if (lmbWrap->lmbReadDiscreteInput(slaveID, startAddress, inputCount, data))
	{
		for(i=0;i<inputCount;i++)
		{
			[dataArray addObject:[NSNumber numberWithBool: data[i]]];
		}
//		returnArray=[NSArray arrayWithArray:dataArray];
//		[dataArray autorelease];
		return (dataArray);
	}
	return (NULL);
}

- (NSArray *) readCoil: (int) slaveID startAddress: (int) startAddress coilCount: (int) coilCount;
{
	uint8_t *data;
	int i;
	NSMutableArray *dataArray;
//	NSArray *returnArray;
	
	if (outputDebugMessages)
		NSLog(@"Reading Coil registers...");
	
	data=(uint8_t *) malloc(coilCount * sizeof(uint8_t));
	dataArray=[[NSMutableArray alloc] initWithCapacity: coilCount];
	if (lmbWrap->lmbReadCoil(slaveID, startAddress, coilCount, data))
	{
		for(i=0;i<coilCount;i++)
		{
			[dataArray addObject:[NSNumber numberWithBool: data[i]]];
		}
//		returnArray=[NSArray arrayWithArray:dataArray];
//		[dataArray autorelease];
		return (dataArray);
	}
	return(NULL);
}

- (BOOL) writeSingleCoil: (int) slaveID coilAddress: (int) coilAddress state: (BOOL) state;
{
	return(lmbWrap->lmbWriteSingleCoil(slaveID, coilAddress, state));
}

- (BOOL) writeMultipleCoils: (int) slaveID startAddress: (int) startAddress coilCount: (int) coilCount stateData: (NSArray *) stateData;
{

	uint8_t *data;
	int i;
	
	data=(uint8_t *)malloc(coilCount * sizeof(uint8_t));
	
	for (i=0;i<[stateData count];i++)
	{
		data[i]= [[stateData objectAtIndex:i] intValue];
	}
	
	return(lmbWrap->lmbWriteMultipleCoils(slaveID, startAddress, coilCount, data));
}

- (NSArray *) readInputRegisters: (int) slaveID startAddress: (int) startAddress registerCount: (int) registerCount;
{
	uint16_t *data;
	int i;
	NSMutableArray *dataArray;
//	NSArray *returnArray;
	
	if (outputDebugMessages)
		NSLog(@"Reading Input registers...");

	data=(uint16_t *) malloc(registerCount * sizeof(uint16_t));
	dataArray=[[NSMutableArray alloc] initWithCapacity: registerCount];
	if (lmbWrap->lmbReadInputRegisters(slaveID, startAddress, registerCount, data))
	{
		for(i=0;i<registerCount;i++)
		{
			[dataArray addObject:[NSNumber numberWithInt: data[i]]];
		}
//		returnArray=[NSArray arrayWithArray:dataArray];
//		[dataArray autorelease];
		return (dataArray);
	}
	return(NULL);
}

- (NSArray *) readHoldingRegisters: (int) slaveID startAddress: (int) startAddress registerCount: (int) registerCount;
{
	uint16_t *data;
	int i;
	NSMutableArray *dataArray;
//	NSArray *returnArray;
		
	if (outputDebugMessages)
		NSLog(@"Reading Holding registers...");

	data=(uint16_t *) malloc(registerCount * sizeof(uint16_t));
	dataArray=[[NSMutableArray alloc] initWithCapacity: registerCount];
	if (lmbWrap->lmbReadHoldingRegisters(slaveID, startAddress, registerCount, data))
	{
		for(i=0;i<registerCount;i++)
		{
			[dataArray addObject:[NSNumber numberWithInt: data[i]]];
		}
//		returnArray=[NSArray arrayWithArray:dataArray];
//		[dataArray autorelease];
		return (dataArray);
	}
	return(NULL);
}

- (BOOL) writeSingleHoldingRegister: (int) slaveID registerAddress: (int) registerAddress value: (uint16_t) value;
{
	return(lmbWrap->lmbWriteSingleHoldingRegister(slaveID, registerAddress, value));
}

- (BOOL) writeMultipleHoldingRegisters: (int) slaveID startAddress: (int) startAddress registerCount: (int) registerCount valueData: (NSArray *) valueData;
{
	uint16_t *data;
	int i;
	
	data=(uint16_t*)malloc([valueData count] * sizeof(uint16_t));
	
	for (i=0;i<[valueData count];i++)
	{
		data[i]= [[valueData objectAtIndex:i] intValue];
	}
	
	return(lmbWrap->lmbWriteMultipleHoldingRegisters(slaveID, startAddress, registerCount, data));
}

- (int) getLastError
{
	return(lmbWrap->lmbErrorCode());
}

- (NSString *) getLastErrorString
{
	switch(lmbWrap->lmbErrorCode())
	{
		case ILLEGAL_FUNCTION:
			return(@"Illegal Function");
		case ILLEGAL_DATA_ADDRESS:
			return(@"Illegal Data Address");
		case ILLEGAL_DATA_VALUE:
			return(@"Illegal Data Value");
		case SLAVE_DEVICE_FAILURE:
			return(@"Slave Device Failure");
		case ACKNOWLEDGE:
			return(@"Did not receive Acknowledge");
		case SLAVE_DEVICE_BUSY:
			return(@"Slave Device Busy");
		case NEGATIVE_ACKNOWLEDGE:
			return(@"Did not receive Acknowledge");
		case MEMORY_PARITY_ERROR:
			return(@"Memory Parity Error");
		case GATEWAY_PROBLEM_PATH:
			return(@"Problem with Gateway Path");
		case GATEWAY_PROBLEM_TARGET:
			return(@"Problem with Gateway Target");
		case COMM_TIME_OUT:
			return(@"Communication Time Out");
		case PORT_SOCKET_FAILURE:
			return(@"Socket Failure");
		case SELECT_FAILURE:
			return(@"Select Failure");
		case TOO_MANY_DATA:
			return(@"Too Much Data");
		case INVALID_CRC:
			return(@"Invalid CRC");
		case INVALID_EXCEPTION_CODE:
			return(@"Unknown Exception");
	}
	return(@"Unknown Error");
}

- (NSString *) getAddress
{
	return([NSString stringWithString:address]);
}

- (void) setDebug: (BOOL)debugToggle
{
	lmbWrap->lmbSetDebug(debugToggle);
	outputDebugMessages=debugToggle;
}

- (BOOL) getDebug
{
	return(outputDebugMessages);
}

- (void) setSaveRawData: (BOOL)toggle
{
	lmbWrap->lmbSetRawDataSave(toggle);
}

- (NSArray *) getLastRawQuery
{
	uint8_t *data;
	int i;
	NSMutableArray *dataArray;
//	NSArray *returnArray;
	
	if (outputDebugMessages)
		NSLog(@"Getting last raw query...");
	
	data=(uint8_t *) malloc(MIN_QUERY_LENGTH * sizeof(uint8_t));
	dataArray=[[NSMutableArray alloc] initWithCapacity: lmbWrap->lmbLastRawQueryLength()];
	if (lmbWrap->lmbLastRawQuery(data))
	{
		for(i=0;i<lmbWrap->lmbLastRawQueryLength();i++)
		{
			[dataArray addObject:[NSNumber numberWithInt: data[i]]];
		}
		free(data);
//		returnArray=[NSArray arrayWithArray:dataArray];
//		[dataArray autorelease];
		return (dataArray);
	}
	return(NULL);
	
}

- (NSArray *) getLastRawResponse{
	uint8_t *data;
	int i;
	NSMutableArray *dataArray;
//	NSArray *returnArray;
	
	if (outputDebugMessages)
		NSLog(@"Getting last raw response...");
	
	data=(uint8_t *) malloc(MAX_MESSAGE_LENGTH * sizeof(uint8_t));
	if (lmbWrap->lmbLastRawResponse(data))
	{
		dataArray=[[NSMutableArray alloc] initWithCapacity: lmbWrap->lmbLastRawResponseLength()];
		for(i=0;i<lmbWrap->lmbLastRawResponseLength();i++)
		{
			[dataArray addObject:[NSNumber numberWithInt: data[i]]];
		}
		free(data);
//		returnArray=[NSArray arrayWithArray:dataArray];
//		[dataArray autorelease];
		return (dataArray);
	}
	return(NULL);
	
}


+ (NSArray *)scanSerialPorts
{
	kern_return_t kernResult; 
	CFMutableDictionaryRef classesToMatch;
	io_iterator_t serialPortIterator;
	NSString* serialPort;
	NSMutableArray *serialPorts;
	
	serialPorts=[[NSMutableArray alloc] initWithCapacity:5];
	
	// Serial devices are instances of class IOSerialBSDClient
	classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue);
	if (classesToMatch != NULL) 
	{
		CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDAllTypes));
		
		// This function decrements the refcount of the dictionary passed it
		kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault, classesToMatch, &serialPortIterator);    
		if (kernResult == KERN_SUCCESS) {			
			while ((serialPort = [self getNextSerialPort:serialPortIterator]) != nil) 
			{
				[serialPorts addObject:serialPort];
			}
			(void)IOObjectRelease(serialPortIterator);
		}
	}
	
	[serialPorts autorelease];
	return(serialPorts);	
}

+ (NSString *)getNextSerialPort:(io_iterator_t)serialPortIterator
{
	NSString *serialPort = nil;
	
	io_object_t serialService = IOIteratorNext(serialPortIterator);
	if (serialService != 0) 
	{
		CFStringRef bsdPath = (CFStringRef)IORegistryEntryCreateCFProperty(serialService, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0);
		if (bsdPath) 
		{
			// If the port already exists in the list of ports, we want that one.  We only create a new one as a last resort.
			//			if ([serialPorts containsObject:(NSString*)bsdPath])
			serialPort = (NSString *)bsdPath;
		}
		
		// We have sucked this service dry of information so release it now.
		(void)IOObjectRelease(serialService);
	}
	
	return serialPort;
}

@end
