Home AMX User Forum AMX General Discussion

USB Badge Reader

Hello All,

I have a USB badge reader, that I'd like to use for authenticating users to access a touch panel. I have a CSV file of all of the users that are allowed access. I'm trying to figure out the best way to program the panel for authentication. Basically, the badge reader just works as a keyboard function, inputting the badge ID number when it's scanned.

Thanks,
Matt

Comments

  • PhreaKPhreaK Posts: 966
    If this is a standard HID device you may be able to attach it to an X or S Series panel and have it function the same way as an external keyboard. On you're UI you will then need a 'text input' button (you can set this under the 'General' tab in the button properties editor). You can also have this hidden (opacity: 0) so that this input side stays completely transparent to your users.

    When text comes in from this it will then fill into this button. From here you a couple of options:

    1) Add a button with it's port set to the loopback port (0) and channel of 'Keyboard: Exit'. When pressed this will fire the contents of all of your text inputs on that page back to the master as a string event prefixed with the text input button's name. Depending on your desired interaction pattern you could also have this hidden and use a do_push *shudder* at say a one second interval when your are in this area of your UI.

    2) Alternatively, you could query the contents of this button at a set interval. This would keep all controls hidden and enable your users to auth etc just by the physical interaction with the reader.
  • PhreaK wrote: »
    If this is a standard HID device you may be able to attach it to an X or S Series panel and have it function the same way as an external keyboard. On you're UI you will then need a 'text input' button (you can set this under the 'General' tab in the button properties editor). You can also have this hidden (opacity: 0) so that this input side stays completely transparent to your users.

    When text comes in from this it will then fill into this button. From here you a couple of options:

    1) Add a button with it's port set to the loopback port (0) and channel of 'Keyboard: Exit'. When pressed this will fire the contents of all of your text inputs on that page back to the master as a string event prefixed with the text input button's name. Depending on your desired interaction pattern you could also have this hidden and use a do_push *shudder* at say a one second interval when your are in this area of your UI.

    2) Alternatively, you could query the contents of this button at a set interval. This would keep all controls hidden and enable your users to auth etc just by the physical interaction with the reader.

    Hey Kim,

    Thanks so much for the help! I am trying to do exactly what you thought, hooking it directly to the panel and using it as the keyboard function. What I think I am going to need the most help with is getting it to authenticate based on the numbers it reads. I have a CSV file with all of the badge numbers available, but how do I get the master to check against these numbers?

    If you could provide any netlinx code I'd appreciate any help I can get!

    THANKS!
  • PhreaKPhreaK Posts: 966
    With that you will probably just want to read the CSV into a structure at boot or if it is being updated regularly if you cannot identify a user following the first attempt. I actually did something very similar to this to provide the functionality we would normally get from LDAP at a trade show down here in Australia a few weeks back. You can see the bit of code that does the CSV read here. The explode function that it uses is from the NetLinx Common Libraries.

    Depending on your environment though (number of users, frequency of updates etc.) you may want to store this sort of data in an external service though and query it as required.
  • PhreaK wrote: »
    With that you will probably just want to read the CSV into a structure at boot or if it is being updated regularly if you cannot identify a user following the first attempt. I actually did something very similar to this to provide the functionality we would normally get from LDAP at a trade show down here in Australia a few weeks back. You can see the bit of code that does the CSV read here. The explode function that it uses is from the NetLinx Common Libraries.

    Depending on your environment though (number of users, frequency of updates etc.) you may want to store this sort of data in an external service though and query it as required.

    Thanks Kim,

    I'm a novice programmer, so I think this is going to be way above my head. For testing of the equipment purposes, is there a simple way to see if the reader is working and make it flip pages?

    Thanks,
    Matt
  • PhreaKPhreaK Posts: 966
    Sure is. If you just want to see if the reader works before diving in too deep just create a touch panel file with a single page and pop a text input button on there. Plug your reader in and see if it fills with content when a read takes place. If so, you're reader is compatible.

    Also, don't be afraid I've diving into something that seems over your head. If you keep doing what your comfortable doing you never have any fun :)
  • PhreaK wrote: »
    Sure is. If you just want to see if the reader works before diving in too deep just create a touch panel file with a single page and pop a text input button on there. Plug your reader in and see if it fills with content when a read takes place. If so, you're reader is compatible.

    Also, don't be afraid I've diving into something that seems over your head. If you keep doing what your comfortable doing you never have any fun :)

    Okay thanks,

    So I tried that, and sure enough it did read the badge and numbers appeared in the box. Without getting overly complicated for now, how would I create a string of code in netlinx that would authenticate a page flip based on if only my badge ID was entered? I'll dive into the CSV file and all that once I get a better sense of what I'm doing! Thanks for the words of encouragement! You seem like a very talented programmer.

    Matt
  • PhreaKPhreaK Posts: 966
    Awesome. If you have a look at the programmers guide for your touch panel you will be able to find a few commands in there for reading button text as well as setting it. A nice approach to implementing this may be to have a timeline (check the NetLinx Keywords help from NetLinx Studio for some info on these if you haven't played with them in the past) that checks to see if there's something in here the same length as your card ID's say once a second. If there is you then you have an ID to auth and can clear this out (again, check the programmers guide for the syntax to set button text) so that it's ready for your next read. As I mentioned before you can then make this text input button completely transparent if you like so that a user can walk up to panel and just auth with their card without even noticing what goes on behind the scenes.
  • PhreaK wrote: »
    Awesome. If you have a look at the programmers guide for your touch panel you will be able to find a few commands in there for reading button text as well as setting it. A nice approach to implementing this may be to have a timeline (check the NetLinx Keywords help from NetLinx Studio for some info on these if you haven't played with them in the past) that checks to see if there's something in here the same length as your card ID's say once a second. If there is you then you have an ID to auth and can clear this out (again, check the programmers guide for the syntax to set button text) so that it's ready for your next read. As I mentioned before you can then make this text input button completely transparent if you like so that a user can walk up to panel and just auth with their card without even noticing what goes on behind the scenes.

    Been fooling around with this all day: Can't seem to get it to work just for basic testing. Any ideas where I'm going wrong?

    PROGRAM_NAME='Keypad Testing'


    DEFINE_DEVICE

    dvTP = 10001:1:0 //AMX Modero NXTCV10
    dvMaster = 0:1:0 //NI700 Master Controller

    DEFINE_START
    SEND_COMMAND dvTP, "'?TXT-1,0'"

    DEFINE_VARIABLE
    NON_VOLATILE char Text[11]

    DEFINE_EVENT

    DATA_EVENT[dvTP]
    {
    ONLINE:
    {
    SEND_COMMAND dvTP, 'PAGE-Swipe'
    }
    }

    BUTTON_EVENT[dvTP,212]
    {

    PUSH:
    IF(Text == '13960017776')
    {
    SEND_COMMAND dvTP, 'PAGE-Error'
    }
    ELSE
    {
    SEND_COMMAND dvTP, 'PAGE-Main'
    }
    }
  • IT WORKED!

    I was able to use one badge id to authenticate... now how to get multiple badges using a text file is the next step..

    PROGRAM_NAME='Keypad Testing'


    DEFINE_DEVICE

    dvTP = 10001:1:0 //AMX Modero NXTCV10
    dvMaster = 0:1:0 //NI700 Master Controller

    DEFINE_VARIABLE
    NON_VOLATILE CHAR Text

    DEFINE_START
    SEND_COMMAND dvTP, "'^TXT-1,3',ITOA(Text)"



    DEFINE_EVENT

    DATA_EVENT[dvTP]
    {
    ONLINE:
    {
    SEND_COMMAND dvTP, 'Page-Swipe'
    }
    STRING:
    {
    IF
    (FIND_STRING(DATA.TEXT,'139600177',1))
    SEND_COMMAND dvTP, 'Page-Main'
    ELSE
    SEND_COMMAND dvTP, 'Page-Swipe'
    }
    }

    BUTTON_EVENT[dvTP,212]
    {
    PUSH:
    {
    SEND_COMMAND dvTP, '@PKP'
    }
    }
  • Attached is some code I used to test the NFC functionality. You should be able to adapt this for your USB reader. This code taps into custom event 700 which occurs when the panel reads an NFC card. It uses a file named NFCPriv.txt to hold the data which includes the starting page to flip to when the user logs in. FTP the data file to the master for use. Instead of triggering on the custom event, adapt this to use your string event.



    Please note that this code has been edited from a MUCH larger file that tests lots of different things. I know it compiles and I think it will work OK but I don't have any hardware to test it with at the moment so YMMV.
  • GregGGregG Posts: 251
    And where can the normal rabble like us find the list of secret magic event codes? ;-)
  • This particular one is not secret. For example, see page 127 of the Modero X Programming Guide. As for the others...
  • PhreaK wrote: »
    With that you will probably just want to read the CSV into a structure at boot or if it is being updated regularly if you cannot identify a user following the first attempt. I actually did something very similar to this to provide the functionality we would normally get from LDAP at a trade show down here in Australia a few weeks back. You can see the bit of code that does the CSV read here. The explode function that it uses is from the NetLinx Common Libraries.

    Depending on your environment though (number of users, frequency of updates etc.) you may want to store this sort of data in an external service though and query it as required.


    Kim:

    My read_file function continually truncates the length of cSUBJECT with objects that I cannot see in Debug. For whatever reason, it thinks there is a comma or space in the CSV file. Can you tell what I'm doing wrong?
    DEFINE_FUNCTION fnREAD_FILE_MTG (CHAR cFILENAME[])
    {
        LOCAL_VAR SLONG slFILE_VAL
        
        slFILE_VAL = FILE_OPEN(cFILENAME,FILE_READ_ONLY)
        
        IF(slFILE_VAL < 0)
        {
    	SELECT
    	{
    	    ACTIVE(slFILE_VAL == -2): SEND_STRING 0, "'Invalid Path'"
    	    ACTIVE(slFILE_VAL == -3): SEND_STRING 0, "'Invalid Value'"
    	    ACTIVE(slFILE_VAL == -5): SEND_STRING 0, "'I/O Error'"
    	    ACTIVE(slFILE_VAL == -14):SEND_STRING 0, "'Max Files Open'"
    	    ACTIVE(slFILE_VAL == -15):SEND_STRING 0, "'Invalid File Format'"
    	}
        }
        ELSE IF(slFILE_VAL > 0)
        {
    	LOCAL_VAR SLONG slVAL
    	LOCAL_VAR INTEGER i
    	LOCAL_VAR CHAR cBUFF[120]
    	
    	FOR(i=1; i<=MAX_LENGTH_ARRAY(uMTG); i++)
    	{
    	    slVAL = FILE_READ_LINE(slFILE_VAL,cBUFF,MAX_LENGTH_ARRAY(cBUFF))
    	    
    	    uMTG[i].cHOST_NAME = REMOVE_STRING(cBUFF,',',1)
    	    SET_LENGTH_ARRAY(uMTG[i].cHOST_NAME,LENGTH_ARRAY(uMTG[i].cHOST_NAME)-1)
    	    uMTG[i].cSTART_DATE = REMOVE_STRING(cBUFF,',',1)
    	    SET_LENGTH_ARRAY(uMTG[i].cSTART_DATE,LENGTH_ARRAY(uMTG[i].cSTART_DATE)-1)
    	    uMTG[i].cSTART_HOUR = REMOVE_STRING(cBUFF,',',1)
    	    SET_LENGTH_ARRAY(uMTG[i].cSTART_HOUR,LENGTH_ARRAY(uMTG[i].cSTART_HOUR)-1)
    	    uMTG[i].cSTART_MINUTE = REMOVE_STRING(cBUFF,',',1)
    	    SET_LENGTH_ARRAY(uMTG[i].cSTART_MINUTE,LENGTH_ARRAY(uMTG[i].cSTART_MINUTE)-1)
    	    uMTG[i].cEND_HOUR = REMOVE_STRING(cBUFF,',',1)
    	    SET_LENGTH_ARRAY(uMTG[i].cEND_HOUR,LENGTH_ARRAY(uMTG[i].cEND_HOUR)-1)
    	    uMTG[i].cEND_MINUTE = REMOVE_STRING(cBUFF,',',1)
    	    SET_LENGTH_ARRAY(uMTG[i].cEND_MINUTE,LENGTH_ARRAY(uMTG[i].cEND_MINUTE)-1)
    	    uMTG[i].cSUBJECT = REMOVE_STRING(cBUFF, ',',1)
    	    
    	    IF(i > MAX_LENGTH_ARRAY(uMTG))
    	    {
    		BREAK;
    	    }
    	}
    		
    	FILE_CLOSE(slFILE_VAL)
        }
    }
    
    
  • PhreaKPhreaK Posts: 966
    Have you got you uMTG definition handy for reference?

    One thing I see as a possibility from the code below is your cBUFF variable is only 120 characters. This will result in a max line length of 120. As your subject is the last column of that CSV format it will be the item that is truncated during the read line.
  • PhreaK wrote: »
    Have you got you uMTG definition handy for reference?

    One thing I see as a possibility from the code below is your cBUFF variable is only 120 characters. This will result in a max line length of 120. As your subject is the last column of that CSV format it will be the item that is truncated during the read line.

    I've attached code for reference. I tried to solve the cBUFF issue by having it ignore commas or blank space, but I'd prefer not to. Any tips would be helpful. I changed the cBUFF length back to 2048, what should it be?

    Thanks,
    PROGRAM_NAME='Calendar Demo Rev 1'
    (***********************************************************)
    (*  FILE CREATED ON: 01/16/2015  AT: 08:00:17              *)
    (***********************************************************)
    (*  FILE_LAST_MODIFIED_ON: 02/03/2015  AT: 07:17:07        *)
    (***********************************************************)
    DEFINE_DEVICE
    dvMaster	=	0:1:0
    dvTP		=	10001:1:0
    dvTP_Locked	=	10001:3:0
    dvTP_Mtg	=	10001:4:0
    
    
    (***********************************************************)
    (*               CONSTANT DEFINITIONS GO BELOW             *)
    (***********************************************************)
    DEFINE_CONSTANT
    MAX_USERS = 500
    MAX_MEETINGS = 36
    
    TL_FEEDBACK = 1
    TL_FEEDBACK2 = 2
    TL_FEEDBACK3 = 3
    
    (***********************************************************)
    (*              DATA TYPE DEFINITIONS GO BELOW             *)
    (***********************************************************)
    DEFINE_TYPE
    
    STRUCTURE _sUSERS //Presets Structure
    {
        CHAR cID[15]
        CHAR cFIRST_NAME[25]
        CHAR cLAST_NAME[25]    
        CHAR cEMAIL[50]
    }
    
    STRUCTURE _sMTG //Mtg Structure
    {
        CHAR cHOST_NAME[25]
        CHAR cSTART_DATE[25]
        CHAR cSTART_HOUR[4]
        CHAR cSTART_MINUTE[4]
        CHAR cEND_HOUR[4]
        CHAR cEND_MINUTE[4]
        CHAR cSUBJECT[50]
    }
    (***********************************************************)
    (*               VARIABLE DEFINITIONS GO BELOW             *)
    (***********************************************************)
    DEFINE_VARIABLE
    
    PERSISTENT _sUSERS uUSERS[MAX_USERS]
    PERSISTENT _sMTG uMTG[MAX_MEETINGS]
    VOLATILE CHAR cDATA[25] 
    VOLATILE CHAR cBADGE[20]
    VOLATILE INTEGER nVALID_ENTRY
    VOLATILE INTEGER nDONE
    VOLATILE CHAR cHOST_NAME[25]
    VOLATILE CHAR cSTART_DATE[25]
    VOLATILE CHAR cSTART_HOUR[4]
    VOLATILE CHAR cSTART_MINUTE[4]
    VOLATILE CHAR cEND_HOUR[4]
    VOLATILE CHAR cEND_MINUTE[4]
    VOLATILE CHAR cSUBJECT[50]
    VOLATILE INTEGER nMINUTE
    VOLATILE INTEGER nHOUR
    VOLATILE INTEGER nMINUTE2
    VOLATILE INTEGER nHOUR2
    VOLATILE INTEGER nTIME
    NON_VOLATILE sINTEGER nCURRENT_HOUR
    NON_VOLATILE sINTEGER nCURRENT_MIN
    NON_VOLATILE sINTEGER nCURRENT_MEETING
    VOLATILE LONG nTIMER[] = {500}
    VOLATILE LONG nTIMER2[] = {5000}
    VOLATILE LONG nTIMER3[] = {10000}
    VOLATILE INTEGER nCLOSED
    
    NON_VOLATILE DEVCHAN dcMTGs[] = 
    {
    {dvTP_Locked, 1},
    {dvTP_Locked, 2},
    {dvTP_Locked, 3},
    {dvTP_Locked, 4},
    {dvTP_Locked, 5},
    {dvTP_Locked, 6},
    {dvTP_Locked, 7},
    {dvTP_Locked, 8},
    {dvTP_Locked, 9},
    {dvTP_Locked, 10},
    {dvTP_Locked, 11},
    {dvTP_Locked, 12},
    {dvTP_Locked, 13},
    {dvTP_Locked, 14},
    {dvTP_Locked, 15},
    {dvTP_Locked, 16},
    {dvTP_Locked, 17},
    {dvTP_Locked, 18},
    {dvTP_Locked, 19},
    {dvTP_Locked, 20},
    {dvTP_Locked, 21},
    {dvTP_Locked, 22},
    {dvTP_Locked, 23},
    {dvTP_Locked, 24},
    {dvTP_Locked, 25},
    {dvTP_Locked, 26},
    {dvTP_Locked, 27},
    {dvTP_Locked, 28},
    {dvTP_Locked, 29},
    {dvTP_Locked, 30},
    {dvTP_Locked, 31},
    {dvTP_Locked, 32},
    {dvTP_Locked, 33},
    {dvTP_Locked, 34},
    {dvTP_Locked, 35},
    {dvTP_Locked, 36}
    }             
    
    (***********************************************************)
    (*               LATCHING DEFINITIONS GO BELOW             *)
    (***********************************************************)
    DEFINE_LATCHING
    
    (***********************************************************)
    (*       MUTUALLY EXCLUSIVE DEFINITIONS GO BELOW           *)
    (***********************************************************)
    DEFINE_MUTUALLY_EXCLUSIVE
    
    (***********************************************************)
    (*        SUBROUTINE/FUNCTION DEFINITIONS GO BELOW         *)
    (***********************************************************)
    (* EXAMPLE: DEFINE_FUNCTION <RETURN_TYPE> <NAME> (<PARAMETERS>) *)
    (* EXAMPLE: DEFINE_CALL '<NAME>' (<PARAMETERS>) *)
    
    DEFINE_FUNCTION INTEGER fnCONVERT_REQUEST (INTEGER nSTART_HOUR, INTEGER nSTART_MINUTE, INTEGER nEND_HOUR, INTEGER nEND_MINUTE)
    {
        STACK_VAR INTEGER nDURATION
        STACK_VAR i 
        STACK_VAR x 
        
        IF(nSTART_HOUR == 1)
        {
    	nSTART_HOUR = 13
        }
        ELSE IF(nSTART_HOUR == 2)
        {
    	nSTART_HOUR = 14
        }
        ELSE IF(nSTART_HOUR == 3)
        {
    	nSTART_HOUR = 15
        }
        ELSE IF(nSTART_HOUR == 4)
        {
    	nSTART_HOUR = 16
        }
        
        IF(nEND_HOUR == 1)
        {
    	nEND_HOUR = 13
        }
        ELSE IF(nEND_HOUR == 2)
        {
    	nEND_HOUR = 14
        }
        ELSE IF(nEND_HOUR == 3)
        {
    	nEND_HOUR = 15
        }
        ELSE IF(nEND_HOUR == 4)
        {
    	nEND_HOUR = 16
        }
        ELSE IF(nEND_HOUR == 5)
        {
    	nEND_HOUR = 17
        }
     
        i = ((nEND_HOUR - nSTART_HOUR)*60)
        x = nEND_MINUTE - nSTART_MINUTE 
        
        nDURATION = i + x 
    
        RETURN nDURATION
    
    }
    
    
    DEFINE_FUNCTION INTEGER fnPROCESS_TIME (INTEGER nHOUR, INTEGER nMINUTE)
    {
        STACK_VAR INTEGER t
        
        IF(nHOUR > 12)
        {
    	nHOUR = nHOUR - 12
        }
        
        IF(nHOUR == 8 && nMINUTE < 15)
        {
    	t = 1
        }
        ELSE IF(nHOUR == 8 && nMINUTE >= 15 && nMINUTE < 30)
        {
    	t = 2
        }
        ELSE IF(nHOUR == 8 && nMINUTE >= 30 && nMINUTE < 45)
        {
    	t = 3
        }
        ELSE IF(nHOUR == 8 && nMINUTE >= 45)
        {
    	t = 4
        }
        ELSE IF(nHOUR == 9 && nMINUTE < 15)
        {
    	t = 5
        }
        ELSE IF(nHOUR == 9 && nMINUTE >= 15 && nMINUTE < 30)
        {
    	t = 6
        }
        ELSE IF(nHOUR == 9 && nMINUTE >= 30 && nMINUTE < 45)
        {
    	t = 7
        }
        ELSE IF(nHOUR == 9 && nMINUTE >= 45)
        {
    	t = 8
        }
        ELSE IF(nHOUR == 10 && nMINUTE < 15)
        {
    	t = 9
        }
        ELSE IF(nHOUR == 10 && nMINUTE >= 15 && nMINUTE < 30)
        {
    	t = 10
        }
        ELSE IF(nHOUR == 10 && nMINUTE >= 30 && nMINUTE < 45)
        {
    	t = 11
        }
        ELSE IF(nHOUR == 10 && nMINUTE >= 45)
        {
    	t = 12
        }
        ELSE IF(nHOUR == 11 && nMINUTE < 15)
        {
    	t = 13
        }
        ELSE IF(nHOUR == 11 && nMINUTE >= 15 && nMINUTE < 30)
        {
    	t = 14
        }
        ELSE IF(nHOUR == 11 && nMINUTE >= 30 && nMINUTE < 45)
        {
    	t = 15
        }
        ELSE IF(nHOUR == 11 && nMINUTE >= 45)
        {
    	t = 16
        }
        ELSE IF(nHOUR == 12 && nMINUTE < 15)
        {
    	t = 17
        }
        ELSE IF(nHOUR == 12 && nMINUTE >= 15 && nMINUTE < 30)
        {
    	t = 18
        }
        ELSE IF(nHOUR == 12 && nMINUTE >= 30 && nMINUTE < 45)
        {
    	t = 19
        }
        ELSE IF(nHOUR == 12 && nMINUTE >= 45)
        {
    	t = 20
        }
        ELSE IF(nHOUR == 1 && nMINUTE < 15)
        {
    	t = 21
        }
        ELSE IF(nHOUR == 1 && nMINUTE >= 15 && nMINUTE < 30)
        {
    	t = 22
        }
        ELSE IF(nHOUR == 1 && nMINUTE >= 30 && nMINUTE < 45)
        {
    	t = 23
        }
        ELSE IF(nHOUR == 1 && nMINUTE >= 45)
        {
    	t = 24
        }
        ELSE IF(nHOUR == 2 && nMINUTE < 15)
        {
    	t = 25
        }
        ELSE IF(nHOUR == 2 && nMINUTE >= 15 && nMINUTE < 30)
        {
    	t = 26
        }
        ELSE IF(nHOUR == 2 && nMINUTE >= 30 && nMINUTE < 45)
        {
    	t = 27
        }
        ELSE IF(nHOUR == 2 && nMINUTE >= 45)
        {
    	t = 28
        }
        ELSE IF(nHOUR == 3 && nMINUTE < 15)
        {
    	t = 29
        }
        ELSE IF(nHOUR == 3 && nMINUTE >= 15 && nMINUTE < 30)
        {
    	t = 30
        }
        ELSE IF(nHOUR == 3 && nMINUTE >= 30 && nMINUTE < 45)
        {
    	t = 31
        }
        ELSE IF(nHOUR == 3 && nMINUTE >= 45)
        {
    	t = 32
        }
        ELSE IF(nHOUR == 4 && nMINUTE < 15)
        {
    	t = 33
        }
        ELSE IF(nHOUR == 4 && nMINUTE >= 15 && nMINUTE < 30)
        {
    	t = 34
        }
        ELSE IF(nHOUR == 4 && nMINUTE >= 30 && nMINUTE < 45)
        {
    	t = 35
    	
        }
        ELSE IF(nHOUR == 4 && nMINUTE >= 45)
        {
    	t = 36
        }
        
        RETURN t
    } 
    
    
    
    DEFINE_FUNCTION INTEGER fnFILL_CALENDAR (INTEGER nHOUR, INTEGER nMIN)
    {   
        STACK_VAR INTEGER x
        STACK_VAR INTEGER nINDEX 
        
        FOR(x=1; x<=MAX_LENGTH_ARRAY(uMTG); x++)
        {
    	IF(LENGTH_ARRAY(uMTG[x].cSUBJECT) > 0)
    	{
    	    IF(uMTG[x].cSUBJECT == uMTG[x].cSUBJECT)
    	    {
    		ON[dvTP_Locked, x]
    	    }
    	}
    	ELSE
    	{
    	    OFF[dvTP_Locked, x]
    	}
        }
        
        IF(nHOUR > 12)
        {
    	nHOUR = nHOUR - 12
        }
        
        nINDEX = fnPROCESS_TIME(nHOUR, nMIN)
        
        IF(LENGTH_ARRAY(uMTG[nINDEX].cSUBJECT) > 0)
        {
    	SEND_COMMAND dvTP, "'^TXT-1,0,',uMTG[nINDEX].cSUBJECT"
    	RETURN TRUE 
        }
        ELSE
        {
    	SEND_COMMAND dvTP, "'^TXT-1,0,Vacant'"
    	RETURN FALSE 
        }
    	
    }
    
    DEFINE_FUNCTION INTEGER fnPROCESS_REQUEST (INTEGER nINDEX, INTEGER nNUM_MTGs)
    {
        STACK_VAR INTEGER i
        
        IF(LENGTH_ARRAY(uMTG[nINDEX].cSUBJECT) < 1)
        {
    	FOR(i=1; i<=nNUM_MTGs; i++)
    	{
    	    IF(LENGTH_ARRAY(uMTG[nINDEX].cSUBJECT) < 1)
    	    {
    		RETURN TRUE 
    	    }
    	    ELSE IF(LENGTH_ARRAY(uMTG[nINDEX].cSUBJECT) > 1)
    	    {
    		RETURN FALSE
    		BREAK;
    	    }
    	    
    	    nINDEX++
    	}
        }
    }
    
    DEFINE_FUNCTION fnRead_BadgeID (CHAR cFILENAME[])
    {
        LOCAL_VAR SLONG slFILE_VAL
        
        slFILE_VAL = FILE_OPEN(cFILENAME,FILE_READ_ONLY)
        IF(slFILE_VAL < 0)
        {
    	SELECT
    	{
    	    ACTIVE(slFILE_VAL == -2): SEND_STRING 0, "'Invalid Path'"
    	    ACTIVE(slFILE_VAL == -3): SEND_STRING 0, "'Invalid Value'"
    	    ACTIVE(slFILE_VAL == -5): SEND_STRING 0, "'I/O Error'"
    	    ACTIVE(slFILE_VAL == -14):SEND_STRING 0, "'Max Files Open'"
    	    ACTIVE(slFILE_VAL == -15):SEND_STRING 0, "'Invalid File Format'"
    	}
        }
        ELSE IF(slFILE_VAL > 0)
        {
    	LOCAL_VAR SLONG slVAL
    	LOCAL_VAR INTEGER i
    	LOCAL_VAR CHAR cBUFF[2048]
    	
    	FOR(i=1; i<=MAX_LENGTH_ARRAY(uUSERS); i++)
    	{
    	    slVAL = FILE_READ_LINE(slFILE_VAL,cBUFF,MAX_LENGTH_ARRAY(cBUFF))
    	    
    	    uUSERS[i].cID = REMOVE_STRING(cBUFF,',',1)
    	    SET_LENGTH_ARRAY(uUSERS[i].cID,LENGTH_ARRAY(uUSERS[i].cID)-1)
    	    uUSERS[i].cFIRST_NAME = REMOVE_STRING(cBUFF,',',1)
    	    SET_LENGTH_ARRAY(uUSERS[i].cFIRST_NAME,LENGTH_ARRAY(uUSERS[i].cFIRST_NAME)-1)
    	    uUSERS[i].cLAST_NAME = REMOVE_STRING(cBUFF,',',1)
    	    SET_LENGTH_ARRAY(uUSERS[i].cLAST_NAME,LENGTH_ARRAY(uUSERS[i].cLAST_NAME)-1)
    	    uUSERS[i].cEMAIL = cBUFF
    	}
    
    	FILE_CLOSE(slFILE_VAL)
        }
    }
    
    DEFINE_FUNCTION fnREAD_FILE_MTG (CHAR cFILENAME[])
    {
        LOCAL_VAR SLONG slFILE_VAL
        
        slFILE_VAL = FILE_OPEN(cFILENAME,FILE_READ_ONLY)
        
        IF(slFILE_VAL < 0)
        {
    	SELECT
    	{
    	    ACTIVE(slFILE_VAL == -2): SEND_STRING 0, "'Invalid Path'"
    	    ACTIVE(slFILE_VAL == -3): SEND_STRING 0, "'Invalid Value'"
    	    ACTIVE(slFILE_VAL == -5): SEND_STRING 0, "'I/O Error'"
    	    ACTIVE(slFILE_VAL == -14):SEND_STRING 0, "'Max Files Open'"
    	    ACTIVE(slFILE_VAL == -15):SEND_STRING 0, "'Invalid File Format'"
    	}
        }
        ELSE IF(slFILE_VAL > 0)
        {
    	LOCAL_VAR SLONG slVAL
    	LOCAL_VAR INTEGER i
    	LOCAL_VAR CHAR cBUFF[2048]
    	
    	FOR(i=1; i<=MAX_LENGTH_ARRAY(uMTG); i++)
    	{
    	    slVAL = FILE_READ_LINE(slFILE_VAL,cBUFF,MAX_LENGTH_ARRAY(cBUFF))
    	    	    
    	    uMTG[i].cHOST_NAME = REMOVE_STRING(cBUFF,',',1)
    	    SET_LENGTH_ARRAY(uMTG[i].cHOST_NAME,LENGTH_ARRAY(uMTG[i].cHOST_NAME)-1)
    	    uMTG[i].cSTART_DATE = REMOVE_STRING(cBUFF,',',1)
    	    SET_LENGTH_ARRAY(uMTG[i].cSTART_DATE,LENGTH_ARRAY(uMTG[i].cSTART_DATE)-1)
    	    uMTG[i].cSTART_HOUR = REMOVE_STRING(cBUFF,',',1)
    	    SET_LENGTH_ARRAY(uMTG[i].cSTART_HOUR,LENGTH_ARRAY(uMTG[i].cSTART_HOUR)-1)
    	    uMTG[i].cSTART_MINUTE = REMOVE_STRING(cBUFF,',',1)
    	    SET_LENGTH_ARRAY(uMTG[i].cSTART_MINUTE,LENGTH_ARRAY(uMTG[i].cSTART_MINUTE)-1)
    	    uMTG[i].cEND_HOUR = REMOVE_STRING(cBUFF,',',1)
    	    SET_LENGTH_ARRAY(uMTG[i].cEND_HOUR,LENGTH_ARRAY(uMTG[i].cEND_HOUR)-1)
    	    uMTG[i].cEND_MINUTE = REMOVE_STRING(cBUFF,',',1)
    	    SET_LENGTH_ARRAY(uMTG[i].cEND_MINUTE,LENGTH_ARRAY(uMTG[i].cEND_MINUTE)-1)
    	    uMTG[i].cSUBJECT = cBUFF
    	    
    
    	    IF((LENGTH_ARRAY(cBUFF) > 0) && LEFT_STRING(cBUFF, 1) == $2C || LEFT_STRING(cBUFF, 1) == $00)
    	    {
    		SET_LENGTH_ARRAY(uMTG[i].cHOST_NAME, 0)
    		SET_LENGTH_ARRAY(uMTG[i].cSTART_DATE, 0)
    		SET_LENGTH_ARRAY(uMTG[i].cSTART_HOUR, 0)
    		SET_LENGTH_ARRAY(uMTG[i].cSTART_MINUTE, 0)
    		SET_LENGTH_ARRAY(uMTG[i].cEND_HOUR, 0)
    		SET_LENGTH_ARRAY(uMTG[i].cEND_MINUTE, 0)
    		SET_LENGTH_ARRAY(uMTG[i].cSUBJECT, 0)
    	    }
    	}
    		
    	FILE_CLOSE(slFILE_VAL)
        }
    }
    
    DEFINE_FUNCTION fnWRITE_FILE_MTG(CHAR cFILENAME[])
    {
        LOCAL_VAR CHAR cBUFF[120]
        LOCAL_VAR SLONG slFILE_VAR
        LOCAL_VAR INTEGER i
        LOCAL_VAR SLONG slRESULT
        
        slFILE_VAR = FILE_OPEN(cFILENAME, FILE_RW_NEW)
        
        IF(slFILE_VAR > 0)
        {
    	FOR(i=1; i<=MAX_LENGTH_ARRAY(uMTG); i++)
    	{
    	    cBUFF = "uMTG[i].cHOST_NAME,',',uMTG[i].cSTART_DATE,',',uMTG[i].cSTART_HOUR,',',uMTG[i].cSTART_MINUTE,',',uMTG[i].cEND_HOUR,',',uMTG[i].cEND_MINUTE,',',uMTG[i].cSUBJECT"
    	    slRESULT = FILE_WRITE_LINE(slFILE_VAR,cBUFF,LENGTH_ARRAY(cBUFF))
    	    
    	    SEND_STRING 0, "slRESULT"
    	}
    
    	FILE_CLOSE(slFILE_VAR)
        
    	SEND_STRING 0,"'GOOD FILE CLOSED ',ITOA(FILE_CLOSE(slFILE_VAR))"
        }
        ELSE
        {
    	SEND_STRING 0,"'BAD FILE OPEN ',ITOA(FILE_CLOSE(slFILE_VAR))"
        }
    }
    
    
    (***********************************************************)
    (*                STARTUP CODE GOES BELOW                  *)
    (***********************************************************)
    DEFINE_START
    TIMELINE_CREATE(TL_FEEDBACK,  nTIMER,  1, TIMELINE_ABSOLUTE, TIMELINE_REPEAT) //Half Second
    TIMELINE_CREATE(TL_FEEDBACK2, nTIMER2, 1, TIMELINE_ABSOLUTE, TIMELINE_REPEAT) // 5 Seconds
    TIMELINE_CREATE(TL_FEEDBACK3, nTIMER3, 1, TIMELINE_ABSOLUTE, TIMELINE_REPEAT) //60 Seconds
    (***********************************************************)
    (*                THE EVENTS GO BELOW                      *)
    (***********************************************************)
    DEFINE_EVENT
    
    DATA_EVENT[dvMaster] //Master Data Event 
    {
        ONLINE:
        {
    	SEND_COMMAND dvTP, "'ADBEEP'" //When online, BEEP!
        }
    }
    
    DATA_EVENT[dvTP]
    {    
        STRING:
        {
    	STACK_VAR INTEGER x 
    	
    	SEND_STRING 0, "'FROM TP: ', DATA.TEXT"
    	
    	SELECT
    	{
    	    
    	    
    	    ACTIVE(FIND_STRING(DATA.TEXT, 'Button 16-', 1)):
    	    {
    		REMOVE_STRING(DATA.TEXT, 'Button 16-', 1)
    		cBADGE = DATA.TEXT 
    
    		IF(LENGTH_ARRAY(cBADGE == 11))
    		{
    		    fnRead_BadgeID('Badge_IDs.csv')
    		    
    		    FOR(x=1; x<=MAX_LENGTH_ARRAY(uUSERS); x++)
    		    {
    			IF(cBADGE == uUSERS[x].cID)
    			{
    			    SEND_COMMAND dvTP, "'@PPN-New Mtg'"
    			    SEND_COMMAND dvTP, "'@PPK-Authorization'"
    			    SEND_COMMAND dvTP, "'@PPK-Invalid Entry'"
    			    SEND_COMMAND dvTP_Mtg, "'^TXT-1,0,',uUSERS[x].cFIRST_NAME,',',uUSERS[x].cLAST_NAME"
    			    ON[nVALID_ENTRY]
    			    
    			    cHOST_NAME = "uUSERS[x].cFIRST_NAME,' ',uUSERS[x].cLAST_NAME"
    			    SEND_COMMAND dvTP_Mtg, "'^TXT-2,0,',DATE"
    			    
    			    BREAK;
    			}
    			ELSE
    			{
    			    SEND_COMMAND dvTP, "'@PPN-Invalid Entry'"
    			    SEND_COMMAND dvTP, "'@PPK-Authorization'"
    			    OFF[nVALID_ENTRY]
    			    WAIT 40
    			    {
    				SEND_COMMAND dvTP, "'@PPK-Invalid Entry'"
    				SEND_COMMAND dvTP, "'PAGE-Locked'"
    			    }
    			}
    		    }
    		}
    	    }
    	    ACTIVE(FIND_STRING(DATA.TEXT, '@PPF-Authorization;Locked', 1)):
    	    {
    		SEND_COMMAND dvTP_Mtg, "'^TXT-3,0,'"
    		SEND_COMMAND dvTP_Mtg, "'^TXT-4,0,'"
    		SEND_COMMAND dvTP_Mtg, "'^TXT-5,0,'"
    		SEND_COMMAND dvTP_Mtg, "'^TXT-6,0,'"
    		SEND_COMMAND dvTP_Mtg, "'^TXT-11,0,'"
    		SEND_COMMAND dvTP_Mtg, "'^TXT-12,0,'"
    		SEND_COMMAND dvTP_Mtg, "'^TXT-13,0,'"
    	    }
    	    ACTIVE(FIND_STRING(DATA.TEXT,'KEYB-',1)):
    	    {
    		REMOVE_STRING(DATA.TEXT,'KEYB-',1)
    		cDATA = DATA.TEXT
    	    }
    	    ACTIVE(FIND_STRING(DATA.TEXT,'KEYP-',1)):
    	    {
    		REMOVE_STRING(DATA.TEXT,'KEYP-',1)
    		
    		IF(nTIME == 1)
    		{
    		    nHOUR = ATOI(DATA.TEXT) 
    		}
    		ELSE IF(nTIME == 2)
    		{
    		    nMINUTE = ATOI(DATA.TEXT)
    		}
    		ELSE IF(nTIME == 3)
    		{
    		    nHOUR2 = ATOI(DATA.TEXT)
    		}
    		ELSE IF(nTIME == 4)
    		{
    		    nMINUTE2 = ATOI(DATA.TEXT)
    		}
    	    }
    	}
    	PULSE[nDONE]
        }
    }
    
    
    BUTTON_EVENT[dvTP_Mtg, 3]
    {
        PUSH:
        {
    	TO[BUTTON.INPUT]
    	nTIME = 1;
        }
        RELEASE:
        {
    	WAIT_UNTIL(nDONE)
    	{
    	    cSTART_HOUR = ITOA(nHOUR)
    	    
    	    SEND_COMMAND dvTP_Mtg, "'^TXT-3,0,',cSTART_HOUR" 
    	    
    	    IF(nHOUR >= 8 && nHOUR < 12)
    	    {
    		SEND_COMMAND dvTP_Mtg, "'^TXT-5,0,AM'"
    	    }
    	    ELSE
    	    {
    		SEND_COMMAND dvTP_Mtg, "'^TXT-5,0,PM'"
    	    }
    	}
        }
    }
    
    BUTTON_EVENT[dvTP_Mtg, 4]
    {
        PUSH:
        {
    	nTIME = 2;
        }
        RELEASE:
        {
    	WAIT_UNTIL(nDONE)
    	{
    	    cSTART_MINUTE = ITOA(nMINUTE)
    	    
    	    IF(nMINUTE == 0)
    	    {	
    		cSTART_MINUTE = "'00'"
    	    }
    	    
    	    SEND_COMMAND dvTP_Mtg, "'^TXT-4,0,',cSTART_MINUTE" 
    	}
        }
    }
    
    BUTTON_EVENT[dvTP_Mtg, 11]
    {
        PUSH:
        {
    	nTIME = 3;
        }
        RELEASE:
        {
    	WAIT_UNTIL(nDONE)
    	{
    	    cEND_HOUR = ITOA(nHOUR2)
    	    
    	    SEND_COMMAND dvTP_Mtg, "'^TXT-11,0,',cEND_HOUR" 
    	    
    	    IF(nHOUR2 >= 8 && nHOUR2 < 12)
    	    {
    		SEND_COMMAND dvTP_Mtg, "'^TXT-13,0,AM'"
    	    }
    	    ELSE
    	    {
    		SEND_COMMAND dvTP_Mtg, "'^TXT-13,0,PM'"
    	    }
    	}
        }
    }
    
    BUTTON_EVENT[dvTP_Mtg, 12]
    {
        PUSH:
        {
    	nTIME = 4;
        }
        RELEASE:
        {
    	WAIT_UNTIL(nDONE)
    	{
    	    cEND_MINUTE = ITOA(nMINUTE2)
    	    
    	    IF(nMINUTE2 == 0)
    	    {	
    		cEND_MINUTE = "'00'"
    	    }
    	    SEND_COMMAND dvTP_Mtg, "'^TXT-12,0,',cEND_MINUTE" 
    	}
        }
    }
    
    BUTTON_EVENT[dvTP_Mtg, 6]
    {
        PUSH:
        {
        }
        RELEASE:
        {
    	WAIT_UNTIL(nDONE)
    	{
    	    cSUBJECT = cDATA 
    	    SEND_COMMAND dvTP_Mtg, "'^TXT-6,0,',cSUBJECT"
    	}
        }
    }
    
    BUTTON_EVENT[dvTP_Mtg, 10] //reserve a new meeting
    {
        PUSH:
        {
    	LOCAL_VAR INTEGER x
    	LOCAL_VAR INTEGER INDEX 
    	LOCAL_VAR INTEGER nDURATION 
    	LOCAL_VAR INTEGER nNUM_MTGS 
    	LOCAL_VAR INTEGER ACCESS 
    	
    	INDEX = fnPROCESS_TIME(nHOUR, nMINUTE)
    	nDURATION = fnCONVERT_REQUEST(nHOUR, nMINUTE, nHOUR2, nMINUTE2)
    	nNUM_MTGS = (nDURATION/15)
    	ACCESS = fnPROCESS_REQUEST(INDEX, nNUM_MTGS)
    	
    	IF(LENGTH_ARRAY(cSUBJECT) > 0)
    	{
    	    OFF[dvTP_Mtg, 16]
    	    
    	    IF(ACCESS == 1)
    	    {	
    		FOR(x=1; x<=nNUM_MTGS; x++)
    		{
    		    uMTG[INDEX].cHOST_NAME    = cHOST_NAME
    		    uMTG[INDEX].cSTART_DATE   = DATE
    		    uMTG[INDEX].cSTART_HOUR   = cSTART_HOUR
    		    uMTG[INDEX].cSTART_MINUTE = cSTART_MINUTE
    		    uMTG[INDEX].cEND_HOUR     = cEND_HOUR
    		    uMTG[INDEX].cEND_MINUTE   = cEND_MINUTE
    		    uMTG[INDEX].cSUBJECT      = cSUBJECT
    		    
    		    INDEX++
    		    
    		    IF(x > nNUM_MTGS)
    		    {
    			BREAK;
    		    }
    		}
    		
    		fnWRITE_FILE_MTG('New_Mtg.csv')
    		
    		SEND_COMMAND dvTP, "'@PPX-New Mtg'"
    	    }
    	    ELSE IF(ACCESS == 0)
    	    {
    		SEND_COMMAND dvTP, "'@PPN-Error'"
    	    }
    	}
    	ELSE
    	{
    	    ON[dvTP_Mtg, 16]
    	}
        }
    }
    
    BUTTON_EVENT[dvTP, 4] //Cancel the current meeting
    {
        PUSH:
        {
    	LOCAL_VAR INTEGER i
    	LOCAL_VAR INTEGER nDURATION2
    	LOCAL_VAR INTEGER nNUM_MTGs2
    	LOCAL_VAR INTEGER nLINE
    	
    	nLINE = fnPROCESS_TIME(nCURRENT_HOUR, nCURRENT_MIN)
    	nDURATION2 = fnCONVERT_REQUEST(ATOI(uMTG[nLINE].cSTART_HOUR), ATOI(uMTG[nLINE].cSTART_MINUTE), ATOI(uMTG[nLINE].cEND_HOUR), ATOI(uMTG[nLINE].cEND_MINUTE))
    	nNUM_MTGs2 = (nDURATION2/15)
    
    	FOR(i=1; i<=nNUM_MTGS2; i++)
    	{
    	    SET_LENGTH_ARRAY(uMTG[nLINE].cHOST_NAME, 0)
    	    SET_LENGTH_ARRAY(uMTG[nLINE].cSTART_DATE, 0)
    	    SET_LENGTH_ARRAY(uMTG[nLINE].cSTART_HOUR, 0)
    	    SET_LENGTH_ARRAY(uMTG[nLINE].cSTART_MINUTE, 0)
    	    SET_LENGTH_ARRAY(uMTG[nLINE].cEND_HOUR, 0)
    	    SET_LENGTH_ARRAY(uMTG[nLINE].cEND_MINUTE, 0)
    	    SET_LENGTH_ARRAY(uMTG[nLINE].cSUBJECT, 0)
    	    
    	    nLINE++
    	    
    	    IF(i > nNUM_MTGs2)
    	    {
    		BREAK;
    	    }
    	}
    	
    	fnWRITE_FILE_MTG('New_Mtg.csv')
        }
    }
    
    BUTTON_EVENT[dcMTGs]
    {
        PUSH:
        {
    	STACK_VAR INTEGER BIC
    	BIC = BUTTON.INPUT.CHANNEL 
    	
    	ON[dvTP, 6]
    	ON[dvTP, 7]
    	
    	IF(LENGTH_ARRAY(uMTG[BIC].cSUBJECT) > 0)
    	{
    	    SEND_COMMAND dvTP, "'^TXT-2,0,',uMTG[BIC].cSUBJECT,' ',uMTG[BIC].cSTART_HOUR,':',uMTG[BIC].cSTART_MINUTE,' - ',uMTG[BIC].cEND_HOUR,':',uMTG[BIC].cEND_MINUTE"
    	}
    	ELSE
    	{
    	    SEND_COMMAND dvTP, "'^TXT-2,0,Meeting room vacant'"
    	}
    	
    	WAIT 50
    	{
    	    OFF[dvTP, 6]
    	    OFF[dvTP, 7]
    	}
        }
    } 
    
    BUTTON_EVENT[dvTP, 3]
    {
        PUSH:
        {
    	STACK_VAR INTEGER y 
    	
    	y = fnPROCESS_TIME(nCURRENT_HOUR, nCURRENT_MIN)
    	
    	IF(LENGTH_ARRAY(uMTG[y].cSUBJECT) > 0)
    	{
    	    SEND_COMMAND dvTP, "'^TXT-3,0,',uMTG[y].cSUBJECT"
    	}
    	ELSE
    	{
    	    SEND_COMMAND dvTP, "'^TXT-3,0,There is no meeting to cancel...'"
    	}
        }
    }
    
    
    TIMELINE_EVENT[TL_FEEDBACK]
    {
        nCURRENT_HOUR = TIME_TO_HOUR(TIME)
        nCURRENT_MIN =  TIME_TO_MINUTE(TIME)
    }
    
    TIMELINE_EVENT[TL_FEEDBACK2]
    {
        nCURRENT_MEETING = fnFILL_CALENDAR(nCURRENT_HOUR, nCURRENT_MIN)
    }
    
    TIMELINE_EVENT[TL_FEEDBACK3]
    {
       fnREAD_FILE_MTG('New_Mtg.csv')
    }
    
    (***********************************************************)
    (*            THE ACTUAL PROGRAM GOES BELOW                *)
    (***********************************************************)
    DEFINE_PROGRAM
    
    [dvTP, 1] = (nCURRENT_MEETING == 1)
    
    
    (***********************************************************)
    (*                     END OF PROGRAM                      *)
    (*        DO NOT PUT ANY CODE BELOW THIS COMMENT           *)
    (***********************************************************)
    
  • PhreaKPhreaK Posts: 966
    Ah, completely missed this the other day. Your last call to remove string where you are populating subject is likely returning nothing. When remove_string(..) is called, if the sequence is not found it will return an empty string.

    Unless you have trailing comma's you may have more luck replacing:
    uMTG[i].cSUBJECT = REMOVE_STRING(cBUFF, ',',1)
    
    with
    uMTG[i].cSUBJECT = cBUFF
    

    Alternatively, the explode(..) function I pointed to earlier in the thread may be able to neaten things up a bit.
  • PhreaK wrote: »
    Ah, completely missed this the other day. Your last call to remove string where you are populating subject is likely returning nothing. When remove_string(..) is called, if the sequence is not found it will return an empty string.

    Unless you have trailing comma's you may have more luck replacing:
    uMTG[i].cSUBJECT = REMOVE_STRING(cBUFF, ',',1)
    
    with
    uMTG[i].cSUBJECT = cBUFF
    

    Alternatively, the explode(..) function I pointed to earlier in the thread may be able to neaten things up a bit.

    Kim,

    thank you... that is exactly what is happening. I was able to write my own function to clean up the commas and blank spaces before I saw the explode function!
  • PhreaK wrote: »
    Have you got you uMTG definition handy for reference?

    One thing I see as a possibility from the code below is your cBUFF variable is only 120 characters. This will result in a max line length of 120. As your subject is the last column of that CSV format it will be the item that is truncated during the read line.

    sorry for the delay:
    DEFINE_TYPE
    STRUCTURE _sMTG //Mtg Structure
    {
        CHAR cHOST_NAME[25]
        CHAR cSTART_DATE[25]
        CHAR cSTART_HOUR[4]
        CHAR cSTART_MINUTE[4]
        CHAR cEND_HOUR[4]
        CHAR cEND_MINUTE[4]
        CHAR cSUBJECT[50]
    }
    
    DEFINE_VARIABLE
    VOLATILE _sMTG uMTG[MAX_MEETINGS]
    
    DEFINE_CONSTANT
    MAX_MEETINGS = 36
    
    
Sign In or Register to comment.