Beispiel #1
0
void ev3rt_load_configuration() {
	/**
	 * Create '/ev3rt/etc/'
	 */
	f_mkdir("/ev3rt");
	f_mkdir("/ev3rt/etc");

	static char localname[100];
	ini_gets("Bluetooth", "LocalName", "Mindstorms EV3", localname, 100, CFG_INI_FILE);
	ini_puts("Bluetooth", "LocalName", localname, CFG_INI_FILE);
	ev3rt_bluetooth_local_name = localname;

	static char pincode[17];
	ini_gets("Bluetooth", "PinCode", "0000", pincode, 17, CFG_INI_FILE);
	ini_puts("Bluetooth", "PinCode", pincode, CFG_INI_FILE);
	ev3rt_bluetooth_pin_code = pincode;

	static bool_t disable_port_1;
	disable_port_1 = ini_getbool("Sensors", "DisablePort1", false, CFG_INI_FILE);
	ini_putl("Sensors", "DisablePort1", disable_port_1, CFG_INI_FILE);
	ev3rt_sensor_port_1_disabled = &disable_port_1;
}
Beispiel #2
0
void ev3rt_load_configuration() {
	/**
	 * Create '/ev3rt/etc/'
	 */
	f_mkdir("/ev3rt");
	f_mkdir("/ev3rt/etc");

    char sio_default_port[5];
	ini_gets("Debug", "DefaultPort", NULL, sio_default_port, 5, CFG_INI_FILE);
    if (!strcasecmp("UART", sio_default_port)) {
        SIO_PORT_DEFAULT = SIO_PORT_UART;
    } else if (!strcasecmp("BT", sio_default_port)) {
        SIO_PORT_DEFAULT = SIO_PORT_BT;
    } else { // Use LCD by default
        SIO_PORT_DEFAULT = SIO_PORT_LCD;
	    ini_puts("Debug", "DefaultPort", "LCD", CFG_INI_FILE);
    }

	static bool_t low_battery_warning;
	low_battery_warning = ini_getbool("Debug", "LowBatteryWarning", true, CFG_INI_FILE);
	ini_putl("Debug", "LowBatteryWarning", low_battery_warning, CFG_INI_FILE);
	ev3rt_low_battery_warning = &low_battery_warning;

	static bool_t disable_bluetooth;
	disable_bluetooth = ini_getbool("Bluetooth", "TurnOff", false, CFG_INI_FILE);
	ini_putl("Bluetooth", "TurnOff", disable_bluetooth, CFG_INI_FILE);
    ev3rt_bluetooth_disabled = &disable_bluetooth;

	static char localname[100];
	ini_gets("Bluetooth", "LocalName", "Mindstorms EV3", localname, 100, CFG_INI_FILE);
	ini_puts("Bluetooth", "LocalName", localname, CFG_INI_FILE);
	ev3rt_bluetooth_local_name = localname;

	static char pincode[17];
	ini_gets("Bluetooth", "PinCode", "0000", pincode, 17, CFG_INI_FILE);
	ini_puts("Bluetooth", "PinCode", pincode, CFG_INI_FILE);
	ev3rt_bluetooth_pin_code = pincode;

	static int disable_pan;
#if !defined(BUILD_LOADER) // TODO: Bluetooth PAN is only supported by loader currently
    disable_pan = 1;
#else
	disable_pan = ini_getbool("Bluetooth", "DisablePAN", false, CFG_INI_FILE);
	ini_putl("Bluetooth", "DisablePAN", disable_pan, CFG_INI_FILE);
#endif
	ev3rt_bluetooth_pan_disabled = &disable_pan;

    // TODO: check valid IP address
	static char ip_address[16];
	ini_gets("Bluetooth", "IPAddress", "10.0.10.1", ip_address, 17, CFG_INI_FILE);
	ini_puts("Bluetooth", "IPAddress", ip_address, CFG_INI_FILE);
	ev3rt_bluetooth_ip_address = ip_address;

	static bool_t disable_port_1;
	disable_port_1 = ini_getbool("Sensors", "DisablePort1", false, CFG_INI_FILE);
	ini_putl("Sensors", "DisablePort1", disable_port_1, CFG_INI_FILE);
    if (SIO_PORT_DEFAULT == SIO_PORT_UART) disable_port_1 = true;
    DEBUG_UART = disable_port_1 ? 0 : 4;
	ev3rt_sensor_port_1_disabled = &disable_port_1;

	static bool_t auto_term_app;
	auto_term_app = ini_getbool("USB", "AutoTerminateApp", true, CFG_INI_FILE);
	ini_putl("USB", "AutoTerminateApp", auto_term_app, CFG_INI_FILE);
	ev3rt_usb_auto_terminate_app = &auto_term_app;
}
Beispiel #3
0
static void write_config_value_int(const char *filename, const char *section, const char *key, const long value) {
    if (ini_putl(section, key, value, filename) != 1) {
        LOG_ERR("Settings", "Unable to save config value: %lu.", value);
    }
}
Beispiel #4
0
/* Command interpreter for VBIT,
	The leading SO and trailing carriage return are already removed
	The X command returns 2
	Other good commands return 0 and bad commands return 1.
	Also after a P command if selecting a single page fails, the page is created and 8 is the return code 
	*/
static int vbit_command(char *Line)
{
	static uint8_t firstLine=true;
	static uint16_t SRAMAddress;	// The address pointer into the FIFO serial ram
	unsigned char rwmode;
	unsigned char returncode=0;
	int pagecount;
	uint8_t directorySteps;
	int8_t sign; // 1=plus -1=minus
	char ch;
	unsigned char valid;
	long n;
	unsigned char i;
	char str[80];
	char *ptr;
	char *dest;
	str[0]='O';
	str[1]='K';
	str[2]='\0';
	// char data[80];
	
	// This stuff is to do with locating pages in the display list (Directory command)
	// (also shared with ee/ea command for uploading pages)
	NODEPTR np;
	DISPLAYNODE node;
	PAGEINDEXRECORD ixRec;
	uint16_t charcount;	
	uint8_t res;
	DWORD fileptr;		// Used to save the file pointer to the body of the ttx file	
	PAGE page;
	// ee/ea specific variable
	static DWORD StartOfPage;
	DWORD EndOfPage;	// Records the start and end of the new page
	PAGEINDEXRECORD pageindex;
	uint16_t ix;

	char packet[45];
	static uint8_t row;	// Teletext row counter for JA/JZ/JW command
	// tba
	
	// Read, Update or not
	switch (Line[2])
	{
	case 'R' : rwmode=CMD_MODE_READ; break;
	case 'U' : rwmode=CMD_MODE_WRITE;break;
	default:
		rwmode=CMD_MODE_NONE;
	}
	/* Is there actually any data */
	if (*Line==0)
		returncode=1;
	else
	switch (Line[1])
	{
	case 'b': // Dump the current page
		dumpPage();
		break;
	case 'C': // Create magazine lists
		// TODO: Kill video
		// TODO: C<pages to pre-allocate> // this would speed up the process immensely
		// It only needs to be approximate as FatFS will extend the file as needed.
		
		cli();
		pagecount=300;
		for (i=1;i<=1;i++) // TODO: Do we need more lists? Probably can find a way around it.
			SDCreateLists(i,pagecount);
		sei();
		break;
	case 'D': // Directory - D[<F|L>][<+|->][<n>]
		// Where F=first, L=Last, +=next, -=prev, n=number of pages to step (default 1)
		//xprintf(PSTR("D Command needs to be written"));
		// It would probably be a good idea to save the seek pointer
		// do the reading required
		// and then reset it. This would save memory.
		// If the ee command is active, don't allow D to mess the page variable.
		if (firstLine)
		{
			returncode=1; // ee command is busy. 
			break;
		}
		directorySteps=0;
		sign=1;
		for (i=2;Line[i];i++)
		{
			ch=Line[i];
			// xprintf(PSTR("Processing Line[%d]=%c\n\r"),i,Line[i]);
			switch (ch)
			{
			case 'F' : ; // Set the first item
				DirectoryFirst();
				break;
			case 'L' : ; // Set the last item
				DirectoryLast();
				break;
			case '+' : ; // Next item
				directorySteps=1;
				sign=1;
				// xprintf(PSTR("D+ not implemented"));
				break;
			case '-' : ; // Previous item
				directorySteps=1;
				sign=-1;
				// xprintf(PSTR("D- not implemented"));
				break;
			default: // Number of steps (TODO: Extend to a generic decimal)
				if (ch>='0' && ch <='9')
				{
					directorySteps=ch;
				}
				Line[i]=0;// Force this to be the last option
				break;	// Break because this must be the last option
			}
			// Find the page
			if (LocatePage(directorySteps*sign)==NULLPTR)
			{
				// Probably want to set an error value as we failed to iterate
				// But I don't think that we return anything different.
				// We rely on TED scheduler to remember the count returned by the P command and NOT overrun
			}
			// TODO: Work out what to do with the rest of the parameters
			np=GetNodePtr(&currentPage);
			// TODO: Handle sub pages
			GetNode(&node,np);
			// Instead treat the page like a single page
			f_lseek(&listFIL,(node.pageindex)*sizeof(PAGEINDEXRECORD));	// Seek the page index
			f_read(&listFIL,&ixRec,sizeof(PAGEINDEXRECORD),&charcount);	// and read it	
			// Now seek the actual page that we are referencing
			res=f_open(&PageF,"pages.all",FA_READ);					// Now look for the relevant page
			f_lseek(&PageF,ixRec.seekptr);	// Seek the actual page
			// Now we have the page, we need to seek through it to get
			// the data
			while (PageF.fptr<(ixRec.seekptr+ixRec.pagesize))
			{
				fileptr=PageF.fptr;		// Save the file pointer in case we found "OL"
				f_gets(str,sizeof(str),&PageF);
				if (str[0]=='O' && str[1]=='L')
				{
					f_lseek (&PageF, fileptr);	// Step back to the OL line
					break;
				}
				if (ParseLine(&page, str))
				{
					xprintf(PSTR("[insert]file error handler needed:%s\n"),str);
					// At this point we are stuffed.
					f_close(&PageF);
					returncode=1;
					break; // what else should we do if we get here?
				}
			}	
			// bb mpp qq cc tttt ssss n xxxxxxx
			// Leading zeros rely on PRINTF_LIB_FLOAT in makefile!!!
			sprintf_P(str,PSTR("%02X %03X %02d %02X %04X 0000 %1d 00000000"),
			3, // seconds (hex)
			0x100+(currentPage/2), // mpp
			page.subpage, // ss
			page.control, // S
			page.time,  // Cycle time (secs)
			(currentPage>>8)+1); // Mag
			f_close(&PageF);
		}
		// str[0]=0;	// might return the directory paramaters here
		break;
	case 'E' : // EO, ES, EN, EP, EL, EM - examine 
		switch (Line[2])
		{
		case 'M': // EM - Return Miscellaneous flags
			{
				// These are BFLSU. The code below is not correct
				n = ini_getl("service", "serialmode", 0, inifile);	
				if (n)
					xputs(PSTR("20"));
				else
					xputs(PSTR("00"));
				break;
			}
				break;
			default:
				returncode=1;
		case 'O': // EO - Output dataline actions set by QO
			// 18 characters on a line, but odd ignores last action
			n = ini_gets("service", "outputodd", "111Q2233P445566778", str, sizearray(str), inifile);	
			n = ini_gets("service", "outputeven", "111Q2233P445566778", str, sizearray(str), inifile);	
			xprintf(PSTR("%s"),str);
			break;
		}		
		break; // E commands
	case 'e' : // ea or ee : Upload page(s).
		// These pages add the lines at the end of the page file, and patch the index.
		// Warning. "page" is shared with the directory command
		// directory calls are blocked until you do ee.
		switch (Line[2])
		{
		case 'a' : // Add a page, line at a time.
			passBackspace=true;
			if (firstLine)
			{
				firstLine=false;
				ClearPage(&page); // Clear out our Page object
				res=f_open(&PageF,"pages.all",FA_READ| FA_WRITE);	// Ready to write
				// TODO: check the value of res
				//xprintf(PSTR("Size of pages.all=%ul\n\r"),PageF.fsize);
				/* Move to end of pages.all to append data */
				res=f_lseek(&PageF, PageF.fsize);				
				//xprintf(PSTR("lseek res=%d\n\r"),res);
				StartOfPage=PageF.fptr;  // We need the Start Of Page for the index
			}
			// uh fellows, Although we get \r => Ctrl-P, we need to map it to 0x8d which is the file format.
			for (ptr=&Line[4];*ptr;ptr++)
				if (*ptr==0x10)
					*ptr=0x8d;
			f_puts(&Line[4],&PageF);	// The rest of the line is the file contents
			f_putc('\n',&PageF);	// Add the LF that the interpreter strips out			
			xprintf(PSTR("Now writing:%s\n\r"),&Line[4]);
			// Don't unencode the line, pages.all should follow MRG \r => ctrl-P substitutions
			// Probably can ignore Viewdata escapes.
			// Write the rest of the line to pages.all
			// Parse the line so we have all the page details so we know where to put it in the array/node
			if (ParseLine(&page, &Line[4]))
			{
				xprintf(PSTR("Your page sucks. Unable to parse this nonsense\n\r"));
				returncode=1;	// failed
				// TODO: implement the break
				// break;
			}
			break; // a
		case 'e' : // We finished. End the update
			passBackspace=false;
			EndOfPage=PageF.fptr;
			f_close(&PageF);		// We are done with pages.all
			// We need to refresh the transmission file object so close and re-open it
			f_close(&pagefileFIL);
			res=f_open(&pagefileFIL,"pages.all",FA_READ);		// Only need to open this once!			
			// Or are we all done? We need to write the page to Pmpp.TTI so that we can rebuild the index later
			pageindex.seekptr=StartOfPage;
			pageindex.pagesize=(uint16_t)(EndOfPage-StartOfPage); // Warning! 16 bit file size limits to about 50 subpages.
		    xprintf(PSTR("seek %ld size %d \n\r"),pageindex.seekptr,pageindex.pagesize);
			// 1: Append pages.idx with the file start/end (append pageindex)
			f_close(&listFIL); // TODO: Perhaps we should check that this is actually opened first?
			res=f_open(&listFIL,"pages.idx",FA_READ| FA_WRITE);	// Ready to write // TODO: check the value of res
			ix=listFIL.fsize;		// This is the address in the file
			res=f_lseek(&listFIL, ix);	// Locate the end of the file
			// Now append the page index
			f_write(&listFIL,&(pageindex.seekptr),4,&charcount);	// 4 byte seek pointer
			f_write(&listFIL,&(pageindex.pagesize),2,&charcount);	// 2 byte file size 			
			// Now restore the file to the previous opened readonly state
			f_close(&listFIL);
		    res=f_open(&listFIL,"pages.idx",FA_READ);			
			// 2: Add the page to the page array. (Or we could rebuild just by doing a restart)
			ix=ix/sizeof(pageindex);
			xprintf(PSTR("New page mag=%d page=%02X --> added at ix=%d\n\r"),page.mag,page.page,ix);
			LinkPage(page.mag, page.page, page.subcode, ix);
			// TODO:
			// 3: Add the page to the node list.  (ditto)
			// TODO:
			firstLine=true;		// and reset ready for the next file
			break; // e
		default:
			str[0]=0;
			returncode=1;
		}
		break; // e commands
	case 'G': /* G - Packet 8/30 format 1 [p830f1]*/
		if (rwmode==CMD_MODE_NONE)
		{
			str[0]=0;
			returncode=1;
			break;
		}
		/* C, L N, T, D */
		switch (Line[3])
		{
		case 'C' : /* Code. 1=format 1 */
			//xputs(PSTR("GUC command not implemented. Why would we need it\n"));
			// It should always default to 0
			/** Where is the initial page done? The code below is wrong */
			//SetInitialPage(pkt830,str1,str2); // nb. Hard coded to 100
			break;
		case 'D' : /* up to 20 characters label*/
			if (rwmode==CMD_MODE_READ)
			{			
				n = ini_gets("p830f1", "label", "VBITFax             ", str, sizearray(str), inifile);			
				xprintf(PSTR("%s"),str);
			}
			if (rwmode==CMD_MODE_WRITE)
			{
				n = ini_puts("p830f1", "label", &Line[4], inifile);	
				SetStatusLabel(pkt830,&Line[4]);
			}			
			break;
		case 'L' : /* Link */
			xputs(PSTR("GUL command\n"));
			/* Alrighty. The MAG is already in the MRAG. All we actually need is 
			ppssss where pp=hex page number and ssss=hex subcode */
			n = ini_gets("p830f1", "initialpage", "003F7F", str, MAXSTR, inifile);
			break;
		case 'N' : /* Net IC  */
			if (rwmode==CMD_MODE_READ)
			{
				n = ini_gets("p830f1", "nic", "fa6f", str, sizearray(str), inifile);			
				xprintf(PSTR("%s"),str);
			}
			if (rwmode==CMD_MODE_WRITE)
			{
				n = ini_puts("p830f1", "nic", &Line[4], inifile);	
				SetNIC1(pkt830,&Line[4]);
			}
			break;
		case 'T' : /* Time */
			xputs(PSTR("GUT command\n"));
			i2c_init();			
			break;
		default:
			str[0]=0;
			returncode=1;	
		}
		break;
	case 'H': // H or HO. Set header
		if (Line[3]=='\0' || Line[5]=='\0') // Don't get confused by checksums
		{
			strcpy_P(str,PSTR("      "));
			strncat(str,g_Header,32);	// Just readback the header. TODO. Get the correct length
			str[40]=0;
		}
		else
		{
			strncpy(g_Header,&Line[9],32); // accept new header
			//g_Header[32]=0;					// Make sure it is capped
			n = ini_puts("service", "header", g_Header, inifile);	// Save this value back to the INI file.
		}
		break;
	case 'I': // III or I2
		// I20xnnmm
		if (Line[2]=='2') // SAA7113 I2C. value. 0xnnmm where nn=address mm=value to write 
		{
			strcpy_P(str,PSTR("Setting SAA7113 I2C register\n"));
			ptr=&Line[3];
			xatoi(&ptr,&n);
			xprintf(PSTR("Blah=%04X\n"),n);
			i2c_SetRegister((n>>8)&0xff,n&0xff);			
			xprintf(PSTR("Done\n"));
		}		
		break;
	case 'J' : // J<h>,DATA - Send a packet to SRAM address
		// Probably want a whole family of J commands.
		// JA<h> - Set the address pointer to SRAM page <h> where <h> is 0..d
		// JW<data> - Write a complete 45 byte packet to the current address and increment
		// JR<data> - Read back the next block of data and increment the pointer.
		// [JT<h> - Retransmit page <h> immediately. (can't work. You must Tx the parent page) ]
		// JT<mpp> - Transmit page <mpp> immediately. (probably need to set a flag in the magazine stream
		// to insert the page in the next transmission slot
		switch (Line[2])
		{
		case 'A': // eg. JA,0   - Set to the start of 
			//xprintf(PSTR("JA set SRAM address (page level)\n"));
			Line[2]='0';Line[3]='x';
			ptr=&Line[2];
			xatoi(&ptr,&n);
			//xprintf(PSTR("JA page=%X SRAMPAGECOUNT=%X SRAMPAGEBASE=%X\n"),n,SRAMPAGECOUNT,SRAMPAGEBASE);
			if (n>=SRAMPAGECOUNT)	// Make sure the page is in range
				returncode=1;					
			else
			{
				n=SRAMPAGEBASE+n*SRAMPAGESIZE;	// This is the actual address
				//xprintf(PSTR("JA address=%04X\n"),n);
				SRAMAddress=n;
				//row=1;
			}
			// We should now fill the packet with some instructions on how to use it!
			// Set the SRAM page address 0..e. There are 14 pages 
			// Coarse address setting
			// For the lulz, JZ gives random access down to byte level
			break;
		case 'Z': // Jay-Z, geddit?, JZ<hex addr 16 bit>
			Line[2]='0';Line[3]='x';
			ptr=&Line[2];
			xatoi(&ptr,&n);
			//xprintf(PSTR("JZ set SRAM address (byte level)\n"));
			//xprintf(PSTR("JZ page=%04X\n"),n);
			// Set the SRAM page address at byte level. Needs an actual 16 bit address
			// where only 15 bits are used.
			// For finer control than the JA command.
			// For the lulz and ability to plonk stuff using random access.
			SRAMAddress=n;
			break;
		case 'W': // JW,<row>,data - Write a packet to the SRAM page buffer
			//xprintf(PSTR("JW Write SRAM data\n"));
			ptr=&Line[3];
			while (*ptr>=' ' && !isdigit(*ptr)) ptr++;	// Seek the row value
			row=atoi(ptr);
			if (!row) // Row address must be greater than 0 					
			{
				returncode=1;	
				break;
			}
			while (isdigit(*ptr) || *ptr==',') ptr++;	// Seek the comma after row
			// If the fifo is transmitting, we must wait here
			for (i=0;FIFOBusy;i++);	// Can this break if the video source has stopped? We may want to timeout on this!
			// xprintf(PSTR("JW addr=%d\n"),row);
			// Write a single packet
			// Not sure how we are going to map control codes but probably the same as OL 
			// Write the packet that we are going to decode into @SRAMAddress
			// TODO: Check the row number to see we don't have a buffer overrun
			// Load and decode the packet

			// Replace this with a section that reads a line of data from USB
			
			// For the first attempt, I'll just copy the rest of the command line
			// I think it is null terminated? Hmm or \r
			// JW,<rest of command line>
			PORTC.OUT&=~VBIT_SEL; // Set the mux to MPU so that we are in control
			for (i=0;i<45;i++)packet[i]=0;
			WritePrefix(packet, 5, row); // This prefix gets replaced later
			packet[3]=row;	// The row gets encoded just before writing to FIFO
			//row++;
			// MRG line format is bit 8 set if it is a control code < ' '
			for (int i=5;i<45 && *ptr && *ptr!='\r';i++)
			{
				packet[i]=*ptr++ & 0x7f;	
			}
			// ** find the address of the row **
			n=SRAMAddress+(row-1)*PACKETSIZE;
			SetSerialRamAddress(SPIRAM_WRITE, n);
			//xprintf(PSTR("JW write address=%04X\n"),n);

			//SRAMAddress+=PACKETSIZE;
			WriteSerialRam(packet,45);
			DeselectSerialRam();
			break;
		case 'R':
			xprintf(PSTR("JR Read back SRAM data\n"));
			// Read back a single packet (translated back into OL format)
			break;
		case 'T':
			xprintf(PSTR("JT Transmit mpp\n"));
			// Set a flag to transmit the selected page ASAP.
			break;
		default:
			returncode=1;
		}
		break;
	case 'L': // L<nn>,<line data>
		// We don't use L in vbit. Because it would require RAM buffering or more file writing,
		// instead we use the e command which writes the file directly.
		xprintf(PSTR("L command not implemented. Use 'e'\n"));
		str[0]=0;
		returncode=1;
		break;
	case 'M': // MD - Delete all the pages selected by the last P command.
		xprintf(PSTR("Delete...\n"));
		// Iterate down all the pages
		// Traverse down subpages and release nodes
		// Go into the pages index and null out the entries in pages.idx
		break;
	case 'O':	/* O - Opt out. Example: O1c*/
		/* Two digit hex number. Only 6 bits are used so the valid range is 0..3f */
			ptr=&Line[0];
			Line[0]='0';Line[1]='x';
			xatoi(&ptr,&n);
			OptRelays=n & 0x3f;
		break;		
	case 'P': // P<mppss>. An invalid character will set null. P without parameters will return the current value
		ptr=&Line[2];
		if (!*ptr)
		{
			sprintf_P(str,PSTR("%s\n\r"),pageFilter);
			break;
		}
		dest=pageFilter;
		for (i=0;i<5;i++)
		{
			ch=*ptr++;
			if (ch=='*')
				valid=1;
			else
			{
				switch (i)
				{
				case 0 : // M
					valid=(ch>'0' && ch<'9' );break;
				case 1 :; // PP
				case 2 :
					valid=((ch>='0' && ch<='9') || (ch>='A' && ch<='F'));break;
				case 3 :; // SS
				case 4 :
					valid=(ch>='0' && ch<='9');break;
				}
			}
			if (valid)
				*dest++ = ch;
			else
			{
				dest[0]=0;
				break;
			}
		}
		*dest=0;	// terminate the string
		// TODO: Find out how many pages are in this page range
		pagecount=FindPageCount();
		if (pagecount<0)
		{
			pagecount=1;
			returncode=8;
			// TODO: At this point we must create the page, as we are about to receive the contents.
			xprintf(PSTR("Page has been created. Please send some data to fill the page\n\r"));
		}
		sprintf_P(str,PSTR("%04d"),pagecount); // Where nnn is the number of pages in this filter. 099 is a filler ack and checksum 
		// str[0]=0;
		break;
	case 'Q' : // QO, QM
		if (Line[2]=='M') // QMnn
		{
			ptr=&Line[3];
			xatoi(&ptr,&n);
			xprintf(PSTR("QM command, n=%d\n"),n);
			n = ini_putl("service", "serialmode", n, inifile);	
			// And at this point put it into the vbi configuration
			// vbi_mode_serial=0 or CTRL_C11_MAGAZINESERIAL_bm
			/** TBA. This setting needs to be in the VBI section
			if (n) 
				vbi_mode_serial=0;
			else
				vbi_mode_serial=CTRL_C11_MAGAZINESERIAL_bm;
			*/
			break;
		}
		// QO sets both odd and even lines
		// QD only sets the odd.
		if (Line[2]=='O' || Line[2]=='D') // QO[18 characters <P|Q|1..8|F>]. QD is the odd line and has 18 lines
		{
			int i;
			char ch;
			ptr=&Line[3];

			xputc('0');
			// Validate it.
			for (i=0;i<18;i++)
			{
				ch=*ptr++;
				switch (ch)
				{
				case '1':;case'2':;case'3':;case'4':;case'5':;case'6':;case'7':;case'8':;case'I':;
				case'F':;
				case'P':;
				case'Q':;
				case'Z':;
					break;
				default:
					returncode=1;
				}
				g_OutputActions[0][i]=ch;		// odd field
				if (Line[2]=='O')
					g_OutputActions[1][i]=ch;	// even field (QO only)
			}
			*ptr=0;
			if (returncode) break;
			n = ini_puts("service", "outputodd", &Line[3], inifile);	
			if (Line[2]=='O')
				n = ini_puts("service", "outputeven", &Line[3], inifile);	// QO only
			break;
		}
		returncode=1;
		break;
	// We could probably use the S command to encapsulate Newfor. We aren't going to be setting the page status much.
	case 'S' : ; // Newfor. This should be a Newfor command. Hmm, but how to escape SO and SI?
		// Work out how to escape data. The parity and reserved characters will break the CI
		break;
	case 'T': // T <hhmmss> (this syntax was superceded by GUT and GUt)
		// testIni();
		// test3();
		// test2();
		// UTC is the time of day in seconds
		Line[8]=0;
		ptr=&(Line[2]);
		UTC=0; // Maybe save this. We need to revert if it fails.
		for (i=2;i<8;i++)
		{
			// First multiply according to which digit
			switch (i)
			{
			case 3:;
			case 5:;
			case 7:
				UTC*=10;break;
			case 4:;
			case 6:
				UTC*=6;break; // (already *10!)
			}
			ch=*(ptr++)-'0';
			UTC+=ch;
		}
		
		// xprintf(PSTR("UTC=%d\n\r"),UTC); // This upsets the protocol!
		
		/**
		UTC=Line[7]-'0';				// s units
		UTC=UTC+(Line[6]-'0')*10;		// s tens
		UTC=UTC+(Line[5]-'0')*60;		// m units
		UTC=UTC+(Line[4]-'0')*60*10;	// m tens
		UTC=UTC+(Line[3]-'0')*60*60;	// h units
		UTC=UTC+(Line[2]-'0')*60*60*10;	// h tens
		*/
		// strcpy_P(str,PSTR("OK\n"));
		str[0]=0;
		break;
	case 'U': // TEST
		Init830F1();
		break;
	case 'V': // Communication settings. 2 hex chars (bit=(on/off) 7=Viewdata/Text 1=CRLF/CR 0=Echo/Silent
		// VBIT seems a bit fussy. When using TED Scheduler it is best to sat V00
		pagecount=sscanf(&(Line[2]),"%2X",&i);
		// xprintf(PSTR("sscanf returns %d. Parameter is %X0\n\r"),pagecount,i);
		if (pagecount>0)
		{
			echoMode=i;		// Yes, it was OK
			// TODO: Save the result in the INI
		}
		else
			returncode=1;	// No, it failed
		break;
	case 'W': // TEST Ad-tec opt outs
		// W14 - Send a mode 14 opt out
		// We want to be able to test various ATP950 modes.
		// read the parameter
		ptr=&Line[2];	
		xatoi(&ptr,&n);
		OptOutMode=n;
		xprintf(PSTR("W=%04X\n"),OptOutMode);
		// Which command? Set the appropriate opt out mode
		switch (OptOutMode)
		{
		case 14:
			xprintf(PSTR("Mode 14 shenanigans\n"),n);
			// Or just flag that we want an opt-out packet
			OptOutMode=14;
			OptOutType=OPTOUT_START;
			// Assemble a mode 14 packet
			break;
		default:
			OptOutMode=0;
			returncode=1;
		}
		// Assemble the packet and 
		break;
	case 'X':	/* X - Exit */
		return 2;	
	case 'Y': /* Y - Version. Y2 should return a date string */
		strcpy_P(str,PSTR("VBIT620 Version 0.04"));
		break;		
	case '?' :; // Status TODO
		xprintf(PSTR("STATUS %02X\n\r"),statusI2C);
		// Want to know if the chips check out and the file system is OK
		// Video Input:
		xprintf(PSTR("Video input: "));report(statusI2C & 0x01); // chip responds, generating field interrupts
		// Digital Encoder
		xprintf(PSTR("Digital encoder: "));report(statusI2C & 0x02); // chip responds
		// FIFO
		statusFIFO=test2();
		xprintf(PSTR("FIFO R/W verified: "));report(statusFIFO); // we can read and write to it
		// File system
		xprintf(PSTR("File system: "));report(statusDisk); // There is a card, it is formatted, it has onair/pages.all
		break;
	default:
		xputs(PSTR("Unknown command\n"));
		returncode=1;
	}