FutabaMDM166AA.cpp
author StephaneLenclud
Tue, 10 Feb 2015 17:14:09 +0100
changeset 35 638eb0763e20
parent 34 892f755c2612
permissions -rw-r--r--
Liscense and Copyright fix.
StephaneLenclud@25
     1
//
StephaneLenclud@35
     2
// Copyright (C) 2014-2015 Stéphane Lenclud.
StephaneLenclud@25
     3
//
StephaneLenclud@35
     4
// This file is part of MiniDisplay.
StephaneLenclud@35
     5
//
StephaneLenclud@35
     6
// MiniDisplay is free software: you can redistribute it and/or modify
StephaneLenclud@35
     7
// it under the terms of the GNU General Public License as published by
StephaneLenclud@35
     8
// the Free Software Foundation, either version 3 of the License, or
StephaneLenclud@35
     9
// (at your option) any later version.
StephaneLenclud@35
    10
//
StephaneLenclud@35
    11
// MiniDisplay is distributed in the hope that it will be useful,
StephaneLenclud@35
    12
// but WITHOUT ANY WARRANTY; without even the implied warranty of
StephaneLenclud@35
    13
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
StephaneLenclud@35
    14
// GNU General Public License for more details.
StephaneLenclud@35
    15
//
StephaneLenclud@35
    16
// You should have received a copy of the GNU General Public License
StephaneLenclud@35
    17
// along with MiniDisplay.  If not, see <http://www.gnu.org/licenses/>.
StephaneLenclud@25
    18
//
StephaneLenclud@25
    19
StephaneLenclud@25
    20
#include "FutabaMDM166AA.h"
StephaneLenclud@25
    21
StephaneLenclud@25
    22
#include <stdio.h>
StephaneLenclud@25
    23
#include <time.h>
StephaneLenclud@25
    24
StephaneLenclud@25
    25
StephaneLenclud@25
    26
StephaneLenclud@32
    27
typedef void (MDM166AA::*TSetIconStatus) (int aIndex, int aStatus);
StephaneLenclud@32
    28
StephaneLenclud@32
    29
const TSetIconStatus KFunctionPerIcon[]=
StephaneLenclud@32
    30
	{
StephaneLenclud@33
    31
	&MDM166AA::SetIconNetworkSignal,	//EMiniDisplayIconNetworkSignal,
StephaneLenclud@33
    32
    &MDM166AA::SetIconInternet,			//EMiniDisplayInternet,
StephaneLenclud@33
    33
	&MDM166AA::SetIconEmail,	//EMiniDisplayIconEmail,
StephaneLenclud@32
    34
    &MDM166AA::SetIconMute,	//EMiniDisplayIconMute,
StephaneLenclud@32
    35
    &MDM166AA::SetIconVolume, //EMiniDisplayIconVolume,
StephaneLenclud@32
    36
	&MDM166AA::SetIconVolumeLabel,	//EMiniDisplayIconVolumeLabel,
StephaneLenclud@32
    37
	&MDM166AA::SetIconPlay,	//EMiniDisplayIconPlay,
StephaneLenclud@32
    38
	&MDM166AA::SetIconPause,	//EMiniDisplayIconPause,
StephaneLenclud@32
    39
	&MDM166AA::SetIconRecording	//EMiniDisplayIconRecording
StephaneLenclud@32
    40
	};
StephaneLenclud@32
    41
StephaneLenclud@32
    42
const int KMaxIconType = sizeof(KFunctionPerIcon)/sizeof(TSetIconStatus);
StephaneLenclud@32
    43
StephaneLenclud@32
    44
/**
StephaneLenclud@32
    45
Define how segments each of our icons have.
StephaneLenclud@32
    46
Order matters.
StephaneLenclud@32
    47
*/
StephaneLenclud@32
    48
const int KSegmentsPerIcon[]=
StephaneLenclud@32
    49
	{
StephaneLenclud@33
    50
	3,	//EMiniDisplayIconNetworkSignal,
StephaneLenclud@33
    51
	1,	//EMiniDisplayIconInternet,
StephaneLenclud@32
    52
    2,	//EMiniDisplayIconEmail,
StephaneLenclud@32
    53
    1,	//EMiniDisplayIconMute,
StephaneLenclud@32
    54
    14, //EMiniDisplayIconVolume,
StephaneLenclud@32
    55
	1,	//EMiniDisplayIconVolumeLabel,
StephaneLenclud@32
    56
	1,	//EMiniDisplayIconPlay,
StephaneLenclud@32
    57
	1,	//EMiniDisplayIconPause,
StephaneLenclud@32
    58
	1	//EMiniDisplayIconRecording
StephaneLenclud@32
    59
	};
StephaneLenclud@32
    60
StephaneLenclud@32
    61
/**
StephaneLenclud@32
    62
Define how status each of our icon can assume.
StephaneLenclud@32
    63
Its typically two for On and Off status.
StephaneLenclud@32
    64
*/
StephaneLenclud@32
    65
const int KStatusPerIcon[]=
StephaneLenclud@32
    66
	{
StephaneLenclud@33
    67
	2,	//EMiniDisplayIconNetworkSignal,
StephaneLenclud@33
    68
	2,	//EMiniDisplayIconInternet,
StephaneLenclud@32
    69
    2,	//EMiniDisplayIconEmail,
StephaneLenclud@32
    70
    2,	//EMiniDisplayIconMute,
StephaneLenclud@33
    71
    3,  //EMiniDisplayIconVolume,
StephaneLenclud@33
    72
	2,	//EMiniDisplayIconVolumeLabel,
StephaneLenclud@33
    73
	2,	//EMiniDisplayIconPlay,
StephaneLenclud@32
    74
	2,	//EMiniDisplayIconPause,
StephaneLenclud@32
    75
	2	//EMiniDisplayIconRecording
StephaneLenclud@32
    76
	};
StephaneLenclud@32
    77
StephaneLenclud@32
    78
StephaneLenclud@32
    79
StephaneLenclud@25
    80
static void sleep(unsigned int mseconds)
StephaneLenclud@25
    81
	{
StephaneLenclud@25
    82
    clock_t goal = mseconds + clock();
StephaneLenclud@25
    83
    while (goal > clock());
StephaneLenclud@25
    84
	}
StephaneLenclud@25
    85
StephaneLenclud@25
    86
//
StephaneLenclud@25
    87
// class MDM166AA
StephaneLenclud@25
    88
//
StephaneLenclud@25
    89
StephaneLenclud@25
    90
MDM166AA::MDM166AA():
StephaneLenclud@25
    91
    iOffScreenMode(true),
StephaneLenclud@30
    92
	iNeedAccurateClockData(false),
StephaneLenclud@25
    93
    iFrameNext(NULL),
StephaneLenclud@25
    94
    iFrameCurrent(NULL),
StephaneLenclud@25
    95
    iFramePrevious(NULL),
StephaneLenclud@25
    96
    iFrameAlpha(NULL),
StephaneLenclud@25
    97
    iFrameBeta(NULL),
StephaneLenclud@28
    98
    iFrameGamma(NULL)
StephaneLenclud@25
    99
	{
StephaneLenclud@25
   100
	iDeviceId[0]=0;
StephaneLenclud@25
   101
	iFirmwareRevision[0]=0;
StephaneLenclud@25
   102
	//ResetBuffers();
StephaneLenclud@25
   103
	}
StephaneLenclud@25
   104
StephaneLenclud@25
   105
/**
StephaneLenclud@25
   106
*/
StephaneLenclud@25
   107
MDM166AA::~MDM166AA()
StephaneLenclud@25
   108
	{
StephaneLenclud@25
   109
    delete iFrameAlpha;
StephaneLenclud@25
   110
    iFrameAlpha=NULL;
StephaneLenclud@25
   111
    //
StephaneLenclud@25
   112
    delete iFrameBeta;
StephaneLenclud@25
   113
    iFrameBeta=NULL;
StephaneLenclud@25
   114
    //
StephaneLenclud@25
   115
    delete iFrameGamma;
StephaneLenclud@25
   116
    iFrameGamma=NULL;
StephaneLenclud@25
   117
    //
StephaneLenclud@25
   118
    iFrameNext=NULL;
StephaneLenclud@25
   119
    iFrameCurrent=NULL;
StephaneLenclud@25
   120
    iFramePrevious=NULL;
StephaneLenclud@25
   121
	}
StephaneLenclud@25
   122
StephaneLenclud@25
   123
/**
StephaneLenclud@25
   124
*/
StephaneLenclud@25
   125
int MDM166AA::Open()
StephaneLenclud@25
   126
	{
StephaneLenclud@25
   127
	int success = HidDevice::Open(KTargaVendorId,KFutabaProductIdMDM166AA,NULL);
StephaneLenclud@25
   128
	if (success)
StephaneLenclud@25
   129
		{
StephaneLenclud@25
   130
        //Allocate both frames
StephaneLenclud@25
   131
        delete iFrameAlpha;
StephaneLenclud@25
   132
        iFrameAlpha=NULL;
StephaneLenclud@25
   133
        iFrameAlpha=new BitArrayLow(KMDM166AAFrameBufferPixelCount);
StephaneLenclud@25
   134
        //
StephaneLenclud@25
   135
        delete iFrameBeta;
StephaneLenclud@25
   136
        iFrameBeta=NULL;
StephaneLenclud@25
   137
        iFrameBeta=new BitArrayLow(KMDM166AAFrameBufferPixelCount);
StephaneLenclud@25
   138
        //
StephaneLenclud@25
   139
        delete iFrameGamma;
StephaneLenclud@25
   140
        iFrameGamma=NULL;
StephaneLenclud@25
   141
        iFrameGamma=new BitArrayLow(KMDM166AAFrameBufferPixelCount);
StephaneLenclud@25
   142
        //
StephaneLenclud@25
   143
        iFrameNext=iFrameAlpha;
StephaneLenclud@25
   144
        iFrameCurrent=iFrameBeta;
StephaneLenclud@25
   145
        iFramePrevious=iFrameGamma;
StephaneLenclud@25
   146
        //
StephaneLenclud@25
   147
		SetNonBlocking(1);
StephaneLenclud@25
   148
		//
StephaneLenclud@28
   149
		SendCommandReset();
StephaneLenclud@30
   150
		
StephaneLenclud@30
   151
		//We will need accurate clock data
StephaneLenclud@34
   152
		//iNeedAccurateClockData=true;
StephaneLenclud@30
   153
		//Until we get it just use rough time instead
StephaneLenclud@34
   154
		//This is needed otherwise the clock won't work for the first minute.
StephaneLenclud@34
   155
		//It flashes the clock when opening the display but that's no big deal.
StephaneLenclud@34
   156
		ShowClock();
StephaneLenclud@34
   157
		SetClockData();
StephaneLenclud@31
   158
StephaneLenclud@31
   159
		//Turns mast ON
StephaneLenclud@31
   160
		//SetIconNetwork(0,EIconOn);
StephaneLenclud@31
   161
		//Show volume label
StephaneLenclud@31
   162
		//SendCommandSymbolControl(EIconVolumeLabel,EIconOn);
StephaneLenclud@31
   163
		//Icon checks
StephaneLenclud@31
   164
		//SetAllIcons(EIconOn);
StephaneLenclud@25
   165
		}
StephaneLenclud@25
   166
	return success;
StephaneLenclud@25
   167
	}
StephaneLenclud@25
   168
StephaneLenclud@25
   169
/**
StephaneLenclud@25
   170
*/
StephaneLenclud@25
   171
void MDM166AA::SetPixel(unsigned char aX, unsigned char aY, unsigned int aPixel)
StephaneLenclud@25
   172
	{
StephaneLenclud@25
   173
	//
StephaneLenclud@25
   174
	//int byteOffset=(aX*HeightInPixels()+aY)/8;
StephaneLenclud@25
   175
	//int bitOffset=(aX*HeightInPixels()+aY)%8;
StephaneLenclud@25
   176
    //iNextFrame[byteOffset] |= ( (aOn?0x01:0x00) << bitOffset );
StephaneLenclud@25
   177
StephaneLenclud@25
   178
	//Pixel is on if any of the non-alpha component is not null
StephaneLenclud@25
   179
	bool on = (aPixel&0x00FFFFFF)!=0x00000000;
StephaneLenclud@25
   180
StephaneLenclud@25
   181
    if (iOffScreenMode)
StephaneLenclud@25
   182
        {
StephaneLenclud@25
   183
        if (on)
StephaneLenclud@25
   184
            {
StephaneLenclud@25
   185
            iFrameNext->SetBit(aX*HeightInPixels()+aY);
StephaneLenclud@25
   186
            }
StephaneLenclud@25
   187
        else
StephaneLenclud@25
   188
            {
StephaneLenclud@25
   189
            iFrameNext->ClearBit(aX*HeightInPixels()+aY);
StephaneLenclud@25
   190
            }
StephaneLenclud@25
   191
        }
StephaneLenclud@25
   192
    else
StephaneLenclud@25
   193
        {
StephaneLenclud@25
   194
        //Just specify a one pixel block
StephaneLenclud@25
   195
        //TODO
StephaneLenclud@25
   196
        }
StephaneLenclud@25
   197
	}
StephaneLenclud@25
   198
StephaneLenclud@25
   199
/**
StephaneLenclud@25
   200
Clear our client side back buffer.
StephaneLenclud@25
   201
Call to SwapBuffers must follow to actually clear the display.
StephaneLenclud@25
   202
*/
StephaneLenclud@25
   203
void MDM166AA::Clear()
StephaneLenclud@25
   204
    {
StephaneLenclud@25
   205
	//That one also clear the symbols
StephaneLenclud@28
   206
    SetAllPixels(0x00);
StephaneLenclud@34
   207
	//SendCommandClear(); //Clear icons too
StephaneLenclud@25
   208
    }
StephaneLenclud@25
   209
StephaneLenclud@25
   210
/**
StephaneLenclud@25
   211
Turn on all pixels.
StephaneLenclud@25
   212
Must be followed by a SwapBuffers call.
StephaneLenclud@25
   213
*/
StephaneLenclud@25
   214
void MDM166AA::Fill()
StephaneLenclud@25
   215
	{
StephaneLenclud@25
   216
	SetAllPixels(0xFF);
StephaneLenclud@25
   217
	}
StephaneLenclud@25
   218
StephaneLenclud@25
   219
/**
StephaneLenclud@25
   220
Set all pixels on our screen to the desired value.
StephaneLenclud@25
   221
This operation is performed off screen to avoid tearing.
StephaneLenclud@25
   222
@param 8 pixels pattern
StephaneLenclud@25
   223
*/
StephaneLenclud@25
   224
void MDM166AA::SetAllPixels(unsigned char aPattern)
StephaneLenclud@25
   225
	{
StephaneLenclud@25
   226
	//With a single buffer
StephaneLenclud@25
   227
	//unsigned char screen[2048]; //One screen worth of pixels
StephaneLenclud@25
   228
	//memset(screen,0xFF,sizeof(screen));
StephaneLenclud@25
   229
	//SetPixelBlock(0,0,63,sizeof(screen),screen);
StephaneLenclud@25
   230
StephaneLenclud@25
   231
StephaneLenclud@25
   232
    if (iOffScreenMode)
StephaneLenclud@25
   233
        {
StephaneLenclud@25
   234
        memset(iFrameNext->Ptr(),aPattern,FrameBufferSizeInBytes());
StephaneLenclud@25
   235
        }
StephaneLenclud@25
   236
    else
StephaneLenclud@25
   237
        {
StephaneLenclud@25
   238
        //Using pattern SetPixelBlock variant.
StephaneLenclud@25
   239
        //TODO
StephaneLenclud@25
   240
        }
StephaneLenclud@25
   241
	//
StephaneLenclud@25
   242
	}
StephaneLenclud@25
   243
StephaneLenclud@25
   244
StephaneLenclud@25
   245
StephaneLenclud@25
   246
StephaneLenclud@25
   247
StephaneLenclud@25
   248
StephaneLenclud@25
   249
/**
StephaneLenclud@25
   250
Whole display RAM areas including invisible area are filled with 00H data.
StephaneLenclud@25
   251
(Include the symbol)
StephaneLenclud@25
   252
SL: Though there is no invisible area with that device.
StephaneLenclud@25
   253
*/
StephaneLenclud@25
   254
void MDM166AA::SendCommandClear()
StephaneLenclud@25
   255
	{
StephaneLenclud@25
   256
    //Send Clear Display Command
StephaneLenclud@25
   257
	FutabaVfdReport report;
StephaneLenclud@25
   258
	report[0]=0x00; //Report ID
StephaneLenclud@25
   259
	report[1]=0x02; //Report length
StephaneLenclud@25
   260
	report[2]=0x1B; //Command ID
StephaneLenclud@25
   261
	report[3]=0x50; //Command ID
StephaneLenclud@25
   262
	Write(report);
StephaneLenclud@25
   263
	}
StephaneLenclud@25
   264
StephaneLenclud@25
   265
/**
StephaneLenclud@30
   266
Check if accurate clock data is needed and update display clock if system clock seconds are zero.
StephaneLenclud@30
   267
This is intended to be called every frame from our SwapBuffers function.
StephaneLenclud@30
   268
*/
StephaneLenclud@30
   269
void MDM166AA::AttemptClockSynchronization()
StephaneLenclud@30
   270
	{
StephaneLenclud@30
   271
	//Check if accurate clock data is needed
StephaneLenclud@30
   272
	if (!iNeedAccurateClockData)
StephaneLenclud@30
   273
		{
StephaneLenclud@30
   274
		return;
StephaneLenclud@30
   275
		}
StephaneLenclud@30
   276
StephaneLenclud@30
   277
	//Fetch local time
StephaneLenclud@30
   278
	time_t rawtime;
StephaneLenclud@30
   279
	struct tm * timeinfo;
StephaneLenclud@30
   280
	time ( &rawtime );
StephaneLenclud@30
   281
	timeinfo = localtime ( &rawtime );
StephaneLenclud@30
   282
StephaneLenclud@30
   283
	//If our seconds are zero we synchronize our display clock
StephaneLenclud@30
   284
	if (timeinfo->tm_sec==0)
StephaneLenclud@30
   285
		{
StephaneLenclud@30
   286
		SendCommandSetClockData(timeinfo->tm_hour,timeinfo->tm_min);
StephaneLenclud@30
   287
		//Our clock is as accurate as it can for the time being
StephaneLenclud@30
   288
		iNeedAccurateClockData=false;
StephaneLenclud@30
   289
		}
StephaneLenclud@30
   290
	}
StephaneLenclud@30
   291
StephaneLenclud@30
   292
/**
StephaneLenclud@25
   293
Put our off screen buffer on screen.
StephaneLenclud@25
   294
On screen buffer goes off screen.
StephaneLenclud@25
   295
*/
StephaneLenclud@25
   296
void MDM166AA::SwapBuffers()
StephaneLenclud@25
   297
	{
StephaneLenclud@30
   298
	//We need to synchronize our clock seconds
StephaneLenclud@30
   299
	AttemptClockSynchronization();
StephaneLenclud@30
   300
StephaneLenclud@25
   301
	//Only perform buffer swapping if off screen mode is enabled
StephaneLenclud@25
   302
	if (OffScreenMode())
StephaneLenclud@25
   303
		{
StephaneLenclud@28
   304
		//Send next frame to our display RAM
StephaneLenclud@28
   305
		//We could attempt to implement a frame differencing algorithm much like we did for GP1212A01.
StephaneLenclud@28
   306
		//However we see little point doing that since we already run at above 20 FPS.
StephaneLenclud@27
   307
		SendCommandWriteGraphicData(FrameBufferSizeInBytes(),iFrameNext->Ptr());
StephaneLenclud@25
   308
StephaneLenclud@25
   309
        //Cycle through our frame buffers
StephaneLenclud@25
   310
        //We keep track of previous frame which is in fact our device back buffer.
StephaneLenclud@25
   311
        //We can then compare previous and next frame and send only the differences to our device.
StephaneLenclud@25
   312
        //This mechanism allows us to reduce traffic over our USB bus thus improving our frame rate from 14 FPS to 30 FPS.
StephaneLenclud@25
   313
        //Keep our previous frame pointer
StephaneLenclud@25
   314
        BitArrayLow* previousFrame=iFramePrevious;
StephaneLenclud@25
   315
        //Current frame becomes the previous one
StephaneLenclud@25
   316
        iFramePrevious = iFrameCurrent;
StephaneLenclud@25
   317
        //Next frame becomes the current one
StephaneLenclud@25
   318
        iFrameCurrent = iFrameNext;
StephaneLenclud@25
   319
        //Next frame is now our former previous
StephaneLenclud@25
   320
        iFrameNext = previousFrame;
StephaneLenclud@25
   321
		}
StephaneLenclud@25
   322
	}
StephaneLenclud@25
   323
StephaneLenclud@25
   324
StephaneLenclud@25
   325
//Define the edge of our pixel block
StephaneLenclud@25
   326
//Pixel blocks of 32x32 seems to run almost as fast as full screen update in worse case scenarii.
StephaneLenclud@25
   327
//Though I wonder if in some situations 16 could be better. Make this an attribute at some point if need be.
StephaneLenclud@25
   328
const int KPixelBlockEdge = 32;
StephaneLenclud@25
   329
const int KPixelBlockSizeInBits = KPixelBlockEdge*KPixelBlockEdge;
StephaneLenclud@25
   330
const int KPixelBlockSizeInBytes = KPixelBlockSizeInBits/8;
StephaneLenclud@25
   331
StephaneLenclud@25
   332
StephaneLenclud@25
   333
/**
StephaneLenclud@25
   334
*/
StephaneLenclud@25
   335
void MDM166AA::Request(TMiniDisplayRequest aRequest)
StephaneLenclud@25
   336
	{
StephaneLenclud@25
   337
	switch (aRequest)
StephaneLenclud@25
   338
		{
StephaneLenclud@25
   339
	case EMiniDisplayRequestDeviceId:
StephaneLenclud@25
   340
		RequestDeviceId();
StephaneLenclud@25
   341
		break;
StephaneLenclud@25
   342
	case EMiniDisplayRequestFirmwareRevision:
StephaneLenclud@25
   343
		RequestFirmwareRevision();
StephaneLenclud@25
   344
		break;
StephaneLenclud@25
   345
	case EMiniDisplayRequestPowerSupplyStatus:
StephaneLenclud@25
   346
		RequestPowerSupplyStatus();
StephaneLenclud@25
   347
		break;
StephaneLenclud@25
   348
	default:
StephaneLenclud@25
   349
		//Not supported
StephaneLenclud@25
   350
		break;
StephaneLenclud@25
   351
		};
StephaneLenclud@25
   352
	}
StephaneLenclud@25
   353
StephaneLenclud@25
   354
StephaneLenclud@25
   355
/**
StephaneLenclud@25
   356
*/
StephaneLenclud@25
   357
void MDM166AA::ResetBuffers()
StephaneLenclud@25
   358
	{
StephaneLenclud@25
   359
    //iNextFrame->ClearAll();
StephaneLenclud@25
   360
    //memset(iFrameAlpha,0x00,sizeof(iFrameAlpha));
StephaneLenclud@25
   361
	//memset(iFrameBeta,0x00,sizeof(iFrameBeta));
StephaneLenclud@25
   362
	}
StephaneLenclud@25
   363
StephaneLenclud@25
   364
/**
StephaneLenclud@25
   365
*/
StephaneLenclud@25
   366
void MDM166AA::RequestDeviceId()
StephaneLenclud@25
   367
    {
StephaneLenclud@25
   368
	//Not supported
StephaneLenclud@25
   369
    }
StephaneLenclud@25
   370
StephaneLenclud@25
   371
/**
StephaneLenclud@25
   372
*/
StephaneLenclud@25
   373
void MDM166AA::RequestFirmwareRevision()
StephaneLenclud@25
   374
    {
StephaneLenclud@28
   375
	//Not supported
StephaneLenclud@25
   376
    }
StephaneLenclud@25
   377
StephaneLenclud@25
   378
/**
StephaneLenclud@25
   379
*/
StephaneLenclud@25
   380
void MDM166AA::RequestPowerSupplyStatus()
StephaneLenclud@25
   381
    {
StephaneLenclud@25
   382
	//Not supported
StephaneLenclud@25
   383
    }
StephaneLenclud@25
   384
StephaneLenclud@25
   385
StephaneLenclud@25
   386
/**
StephaneLenclud@25
   387
This is for development purposes only.
StephaneLenclud@25
   388
Production application should stick to off-screen mode to avoid tearing.
StephaneLenclud@25
   389
*/
StephaneLenclud@25
   390
void MDM166AA::ToggleOffScreenMode()
StephaneLenclud@25
   391
	{
StephaneLenclud@25
   392
    SetOffScreenMode(!iOffScreenMode);
StephaneLenclud@25
   393
	}
StephaneLenclud@25
   394
StephaneLenclud@25
   395
/**
StephaneLenclud@25
   396
 * @brief MDM166AA::SetOffScreenMode
StephaneLenclud@25
   397
 * @param aOn
StephaneLenclud@25
   398
 * @return
StephaneLenclud@25
   399
 */
StephaneLenclud@25
   400
void MDM166AA::SetOffScreenMode(bool aOn)
StephaneLenclud@25
   401
    {
StephaneLenclud@25
   402
    if (aOn==iOffScreenMode)
StephaneLenclud@25
   403
    {
StephaneLenclud@25
   404
        //Nothing to do here
StephaneLenclud@25
   405
        return;
StephaneLenclud@25
   406
    }
StephaneLenclud@25
   407
StephaneLenclud@25
   408
    iOffScreenMode=aOn;
StephaneLenclud@25
   409
StephaneLenclud@25
   410
    //Clean up our buffers upon switching modes
StephaneLenclud@25
   411
    Clear();
StephaneLenclud@25
   412
    SwapBuffers();
StephaneLenclud@25
   413
    Clear();
StephaneLenclud@25
   414
    }
StephaneLenclud@25
   415
StephaneLenclud@25
   416
/**
StephaneLenclud@25
   417
Set our screen brightness.
StephaneLenclud@25
   418
@param The desired brightness level. Must be between MinBrightness and MaxBrightness.
StephaneLenclud@25
   419
*/
StephaneLenclud@25
   420
void MDM166AA::SetBrightness(int aBrightness)
StephaneLenclud@25
   421
    {
StephaneLenclud@25
   422
    if (aBrightness<MinBrightness()||aBrightness>MaxBrightness())
StephaneLenclud@25
   423
        {
StephaneLenclud@25
   424
        //Brightness out of range.
StephaneLenclud@25
   425
        //Just ignore that request.
StephaneLenclud@25
   426
        return;
StephaneLenclud@25
   427
        }
StephaneLenclud@25
   428
StephaneLenclud@25
   429
    FutabaVfdReport report;
StephaneLenclud@25
   430
    report[0]=0x00; //Report ID
StephaneLenclud@25
   431
    report[1]=0x03; //Report size
StephaneLenclud@25
   432
    report[2]=0x1B; //Command ID
StephaneLenclud@25
   433
    report[3]=0x40; //Command ID
StephaneLenclud@25
   434
    report[4]=aBrightness; //Brightness level
StephaneLenclud@25
   435
    Write(report);
StephaneLenclud@25
   436
    }
StephaneLenclud@25
   437
StephaneLenclud@25
   438
StephaneLenclud@25
   439
/**
StephaneLenclud@25
   440
*/
StephaneLenclud@25
   441
void MDM166AA::ShowClock()
StephaneLenclud@25
   442
	{
StephaneLenclud@30
   443
	//Assuming display clock is at least roughly set since we do it when opening our display connection.
StephaneLenclud@30
   444
	//We will need accurate clock data next we get a chance.
StephaneLenclud@30
   445
	//This should guarantee that if our display remain open for weeks our clock will be synchronized whenever we switch back from clock mode to render mode.
StephaneLenclud@30
   446
	iNeedAccurateClockData=true;
StephaneLenclud@30
   447
	//Show clock using specified styles
StephaneLenclud@25
   448
	SendCommandClockDisplay(EClockLarge,EClock24);
StephaneLenclud@25
   449
	}
StephaneLenclud@25
   450
StephaneLenclud@25
   451
/**
StephaneLenclud@25
   452
*/
StephaneLenclud@25
   453
void MDM166AA::HideClock()
StephaneLenclud@25
   454
	{
StephaneLenclud@34
   455
	SetAllPixels(0x00);
StephaneLenclud@34
   456
	SwapBuffers();
StephaneLenclud@25
   457
	}
StephaneLenclud@25
   458
StephaneLenclud@32
   459
/**
StephaneLenclud@32
   460
*/
StephaneLenclud@32
   461
int MDM166AA::IconCount(TMiniDisplayIconType aIcon)
StephaneLenclud@32
   462
	{
StephaneLenclud@32
   463
	return KSegmentsPerIcon[aIcon];
StephaneLenclud@32
   464
	}
StephaneLenclud@32
   465
StephaneLenclud@32
   466
int MDM166AA::IconStatusCount(TMiniDisplayIconType aIcon)
StephaneLenclud@32
   467
	{
StephaneLenclud@32
   468
	return KStatusPerIcon[aIcon];
StephaneLenclud@32
   469
	}
StephaneLenclud@32
   470
StephaneLenclud@32
   471
void MDM166AA::SetIconStatus(TMiniDisplayIconType aIcon, int aIndex, int aStatus)
StephaneLenclud@32
   472
	{
StephaneLenclud@32
   473
	if (aIcon<0||aIcon>=KMaxIconType||(KFunctionPerIcon[aIcon]==NULL))
StephaneLenclud@32
   474
		{
StephaneLenclud@32
   475
		//Out of range or no function pointer for that icon
StephaneLenclud@32
   476
		return;
StephaneLenclud@32
   477
		}
StephaneLenclud@32
   478
StephaneLenclud@32
   479
	(this->*KFunctionPerIcon[aIcon])(aIndex,aStatus);
StephaneLenclud@32
   480
	}
StephaneLenclud@25
   481
StephaneLenclud@25
   482
/**
StephaneLenclud@31
   483
*/
StephaneLenclud@33
   484
void MDM166AA::SetIconNetworkSignal(int aIndex, int aStatus)
StephaneLenclud@31
   485
	{
StephaneLenclud@33
   486
	if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconNetworkSignal])
StephaneLenclud@33
   487
		{
StephaneLenclud@33
   488
		//Out of range
StephaneLenclud@33
   489
		return;
StephaneLenclud@33
   490
		}
StephaneLenclud@33
   491
StephaneLenclud@33
   492
	SendCommandSymbolControl((TIconId)(aIndex+EIconNetworkSignalLow),(aStatus==0?EIconOff:EIconOn));
StephaneLenclud@33
   493
	}
StephaneLenclud@33
   494
StephaneLenclud@33
   495
/**
StephaneLenclud@33
   496
*/
StephaneLenclud@33
   497
void MDM166AA::SetIconInternet(int aIndex, int aStatus)
StephaneLenclud@33
   498
	{
StephaneLenclud@33
   499
	if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconInternet])
StephaneLenclud@31
   500
		{
StephaneLenclud@31
   501
		//Out of range
StephaneLenclud@31
   502
		return;
StephaneLenclud@31
   503
		}
StephaneLenclud@31
   504
StephaneLenclud@31
   505
	SendCommandSymbolControl((TIconId)(aIndex+EIconNetworkMast),(aStatus==0?EIconOff:EIconOn));
StephaneLenclud@31
   506
	}
StephaneLenclud@31
   507
StephaneLenclud@31
   508
/**
StephaneLenclud@31
   509
*/
StephaneLenclud@31
   510
void MDM166AA::SetIconEmail(int aIndex, int aStatus)
StephaneLenclud@31
   511
	{
StephaneLenclud@32
   512
	if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconEmail])
StephaneLenclud@31
   513
		{
StephaneLenclud@31
   514
		//Out of range
StephaneLenclud@31
   515
		return;
StephaneLenclud@31
   516
		}
StephaneLenclud@31
   517
StephaneLenclud@31
   518
	SendCommandSymbolControl((TIconId)(aIndex+EIconEnvelop),(aStatus==0?EIconOff:EIconOn));
StephaneLenclud@31
   519
	}
StephaneLenclud@31
   520
StephaneLenclud@31
   521
/**
StephaneLenclud@31
   522
*/
StephaneLenclud@31
   523
void MDM166AA::SetIconMute(int aIndex, int aStatus)
StephaneLenclud@31
   524
	{
StephaneLenclud@32
   525
	if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconMute])
StephaneLenclud@31
   526
		{
StephaneLenclud@31
   527
		//Out of range
StephaneLenclud@31
   528
		return;
StephaneLenclud@31
   529
		}
StephaneLenclud@31
   530
StephaneLenclud@31
   531
	SendCommandSymbolControl((TIconId)(aIndex+EIconMute),(aStatus==0?EIconOff:EIconOn));
StephaneLenclud@31
   532
	}
StephaneLenclud@31
   533
StephaneLenclud@31
   534
/**
StephaneLenclud@31
   535
*/
StephaneLenclud@31
   536
void MDM166AA::SetIconVolume(int aIndex, int aStatus)
StephaneLenclud@31
   537
	{
StephaneLenclud@32
   538
	if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconVolume])
StephaneLenclud@31
   539
		{
StephaneLenclud@31
   540
		//Out of range
StephaneLenclud@31
   541
		return;
StephaneLenclud@31
   542
		}
StephaneLenclud@31
   543
StephaneLenclud@32
   544
	if (aStatus<EIconOff)
StephaneLenclud@32
   545
		{
StephaneLenclud@32
   546
		//Assuming we just want to turn it off then
StephaneLenclud@32
   547
		aStatus=EIconOff;
StephaneLenclud@32
   548
		}
StephaneLenclud@32
   549
StephaneLenclud@32
   550
	//Make sure we cap at our highest status value
StephaneLenclud@32
   551
	aStatus = MIN(EIconOn,aStatus);
StephaneLenclud@32
   552
StephaneLenclud@32
   553
	SendCommandSymbolControl((TIconId)(aIndex+EIconVolumeLevel01),(TIconStatus)aStatus);
StephaneLenclud@32
   554
	}
StephaneLenclud@32
   555
StephaneLenclud@32
   556
StephaneLenclud@32
   557
/**
StephaneLenclud@32
   558
*/
StephaneLenclud@32
   559
void MDM166AA::SetIconVolumeLabel(int aIndex, int aStatus)
StephaneLenclud@32
   560
	{
StephaneLenclud@32
   561
	if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconMute])
StephaneLenclud@31
   562
		{
StephaneLenclud@31
   563
		//Out of range
StephaneLenclud@31
   564
		return;
StephaneLenclud@31
   565
		}
StephaneLenclud@31
   566
StephaneLenclud@32
   567
	SendCommandSymbolControl((TIconId)(aIndex+EIconVolumeLabel),(aStatus==0?EIconOff:EIconOn));
StephaneLenclud@31
   568
	}
StephaneLenclud@31
   569
StephaneLenclud@31
   570
StephaneLenclud@31
   571
/**
StephaneLenclud@31
   572
*/
StephaneLenclud@31
   573
void MDM166AA::SetIconPlay(int aIndex, int aStatus)
StephaneLenclud@31
   574
	{
StephaneLenclud@32
   575
	if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconPlay])
StephaneLenclud@31
   576
		{
StephaneLenclud@31
   577
		//Out of range
StephaneLenclud@31
   578
		return;
StephaneLenclud@31
   579
		}
StephaneLenclud@31
   580
StephaneLenclud@31
   581
	SendCommandSymbolControl((TIconId)(aIndex+EIconPlay),(aStatus==0?EIconOff:EIconOn));
StephaneLenclud@31
   582
	}
StephaneLenclud@31
   583
StephaneLenclud@31
   584
StephaneLenclud@31
   585
/**
StephaneLenclud@31
   586
*/
StephaneLenclud@31
   587
void MDM166AA::SetIconPause(int aIndex, int aStatus)
StephaneLenclud@31
   588
	{
StephaneLenclud@32
   589
	if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconPause])
StephaneLenclud@31
   590
		{
StephaneLenclud@31
   591
		//Out of range
StephaneLenclud@31
   592
		return;
StephaneLenclud@31
   593
		}
StephaneLenclud@31
   594
StephaneLenclud@31
   595
	SendCommandSymbolControl((TIconId)(aIndex+EIconPause),(aStatus==0?EIconOff:EIconOn));
StephaneLenclud@31
   596
	}
StephaneLenclud@31
   597
StephaneLenclud@31
   598
StephaneLenclud@31
   599
/**
StephaneLenclud@31
   600
*/
StephaneLenclud@31
   601
void MDM166AA::SetIconRecording(int aIndex, int aStatus)
StephaneLenclud@31
   602
	{
StephaneLenclud@32
   603
	if (aIndex<0||aIndex>=KSegmentsPerIcon[EMiniDisplayIconRecording])
StephaneLenclud@31
   604
		{
StephaneLenclud@31
   605
		//Out of range
StephaneLenclud@31
   606
		return;
StephaneLenclud@31
   607
		}
StephaneLenclud@31
   608
StephaneLenclud@31
   609
	SendCommandSymbolControl((TIconId)(aIndex+EIconRecording),(aStatus==0?EIconOff:EIconOn));
StephaneLenclud@31
   610
	}
StephaneLenclud@31
   611
StephaneLenclud@31
   612
/**
StephaneLenclud@31
   613
Set all our icons to the corresponding status.
StephaneLenclud@31
   614
*/
StephaneLenclud@31
   615
void MDM166AA::SetAllIcons(TIconStatus aStatus)
StephaneLenclud@31
   616
	{
StephaneLenclud@31
   617
	for (int i=EIconFirst;i<=EIconLast;i++)
StephaneLenclud@31
   618
		{
StephaneLenclud@31
   619
		SendCommandSymbolControl((TIconId)i,aStatus);
StephaneLenclud@31
   620
		}
StephaneLenclud@31
   621
	}
StephaneLenclud@31
   622
StephaneLenclud@31
   623
/**
StephaneLenclud@31
   624
Symbols control
StephaneLenclud@31
   625
Segment On/Off and Grayscale/Brightness
StephaneLenclud@31
   626
[Code]1BH,30H,Ps,Pb
StephaneLenclud@31
   627
*/
StephaneLenclud@31
   628
void MDM166AA::SendCommandSymbolControl(TIconId aIconId, TIconStatus aStatus)
StephaneLenclud@31
   629
	{
StephaneLenclud@31
   630
	FutabaVfdReport report;
StephaneLenclud@31
   631
    report[0]=0x00; //Report ID
StephaneLenclud@31
   632
    report[1]=0x04; //Report size
StephaneLenclud@31
   633
    report[2]=0x1B; //Command ID
StephaneLenclud@31
   634
    report[3]=0x30; //Command ID
StephaneLenclud@31
   635
	report[4]=aIconId;
StephaneLenclud@31
   636
	report[5]=aStatus;
StephaneLenclud@31
   637
StephaneLenclud@31
   638
    Write(report);
StephaneLenclud@31
   639
	}
StephaneLenclud@31
   640
StephaneLenclud@31
   641
StephaneLenclud@31
   642
/**
StephaneLenclud@25
   643
Clock setting 
StephaneLenclud@25
   644
[Code]1BH,00H,Pm,Ph 
StephaneLenclud@25
   645
[Function]Setting the clock data. The setting data is cleared, if the Reset command is input or power is turned off.
StephaneLenclud@25
   646
Ph = hour 
StephaneLenclud@25
   647
Pm = minute 
StephaneLenclud@25
   648
*/
StephaneLenclud@28
   649
void MDM166AA::SendCommandSetClockData(unsigned char aHour, unsigned char aMinute)
StephaneLenclud@25
   650
	{
StephaneLenclud@25
   651
	FutabaVfdReport report;
StephaneLenclud@25
   652
    report[0]=0x00; //Report ID
StephaneLenclud@25
   653
    report[1]=0x04; //Report size
StephaneLenclud@25
   654
    report[2]=0x1B; //Command ID
StephaneLenclud@25
   655
    report[3]=0x00; //Command ID
StephaneLenclud@25
   656
StephaneLenclud@25
   657
	//Minutes and Hours needs to be in hexadecimal view
StephaneLenclud@25
   658
	//To get 21:59 you need to pass in 0x21:0x59
StephaneLenclud@25
   659
	//Weirdest format ever, I know 
StephaneLenclud@25
   660
	report[4]=(aMinute/10*16)+aMinute%10;
StephaneLenclud@25
   661
	report[5]=(aHour/10*16)+aHour%10;
StephaneLenclud@25
   662
StephaneLenclud@25
   663
    Write(report);
StephaneLenclud@25
   664
	}
StephaneLenclud@25
   665
StephaneLenclud@25
   666
/**
StephaneLenclud@28
   667
Set display clock data according to local system time.
StephaneLenclud@30
   668
This will only provide 30s accuracy.
StephaneLenclud@30
   669
In fact display clock seconds are set to zero whenever clock data is set.
StephaneLenclud@30
   670
So you would only get second accuracy if this function was called when system time is at zero second.
StephaneLenclud@30
   671
It's the responsibility of AttemptClockSynchronization function to obtain second accuracy.
StephaneLenclud@30
   672
The present function is intended to provide only rough clock synchronization.
StephaneLenclud@30
   673
StephaneLenclud@30
   674
@note Unfortunately this command also turns on clock display.
StephaneLenclud@25
   675
*/
StephaneLenclud@28
   676
void MDM166AA::SetClockData()
StephaneLenclud@25
   677
	{
StephaneLenclud@25
   678
	time_t rawtime;
StephaneLenclud@25
   679
	struct tm * timeinfo;
StephaneLenclud@25
   680
StephaneLenclud@25
   681
	time ( &rawtime );
StephaneLenclud@25
   682
	timeinfo = localtime ( &rawtime );
StephaneLenclud@28
   683
	//Adjust minute as best as we can so that we have a 30 seconds offset at most rather a than a full minute.
StephaneLenclud@28
   684
	if (timeinfo->tm_sec>30)
StephaneLenclud@28
   685
		{
StephaneLenclud@28
   686
		//Use the next minute then
StephaneLenclud@28
   687
		timeinfo->tm_min++;
StephaneLenclud@28
   688
		if (timeinfo->tm_min==60)
StephaneLenclud@28
   689
			{
StephaneLenclud@28
   690
			//Use the next hour then
StephaneLenclud@28
   691
			timeinfo->tm_hour++;
StephaneLenclud@28
   692
			timeinfo->tm_min=0;
StephaneLenclud@28
   693
			if (timeinfo->tm_hour==24)
StephaneLenclud@28
   694
				{
StephaneLenclud@28
   695
				//Move to the next day then
StephaneLenclud@28
   696
				timeinfo->tm_hour=0;
StephaneLenclud@28
   697
				}
StephaneLenclud@28
   698
			}
StephaneLenclud@28
   699
		}
StephaneLenclud@28
   700
StephaneLenclud@28
   701
	//Send hours and minutes to our display
StephaneLenclud@28
   702
	SendCommandSetClockData(timeinfo->tm_hour,timeinfo->tm_min);
StephaneLenclud@25
   703
	}
StephaneLenclud@25
   704
StephaneLenclud@25
   705
StephaneLenclud@25
   706
/**
StephaneLenclud@25
   707
Clock display
StephaneLenclud@25
   708
[Code] 1BH,Ps,aL,aH,Pf
StephaneLenclud@25
   709
[Function] Clock is displayed small or big.
StephaneLenclud@25
   710
*/
StephaneLenclud@25
   711
void MDM166AA::SendCommandClockDisplay(TClockSize aClockSize, TClockFormat aClockFormat)
StephaneLenclud@25
   712
	{
StephaneLenclud@25
   713
	FutabaVfdReport report;
StephaneLenclud@25
   714
    report[0]=0x00; //Report ID
StephaneLenclud@25
   715
    report[1]=0x03; //Report size
StephaneLenclud@25
   716
    report[2]=0x1B; //Command ID
StephaneLenclud@25
   717
    report[3]=aClockSize; //
StephaneLenclud@25
   718
    report[4]=aClockFormat; //
StephaneLenclud@25
   719
StephaneLenclud@25
   720
    Write(report);
StephaneLenclud@25
   721
	}
StephaneLenclud@25
   722
StephaneLenclud@27
   723
StephaneLenclud@27
   724
/**
StephaneLenclud@27
   725
Display RAM filled with 00H.
StephaneLenclud@27
   726
Address Counter is set by 00H.
StephaneLenclud@27
   727
Dimming is set to 50%.
StephaneLenclud@31
   728
Turn off all icons segments.
StephaneLenclud@34
   729
StephaneLenclud@34
   730
This does not reset our clock settings.
StephaneLenclud@27
   731
*/
StephaneLenclud@27
   732
void MDM166AA::SendCommandReset()
StephaneLenclud@27
   733
	{
StephaneLenclud@27
   734
	FutabaVfdReport report;
StephaneLenclud@27
   735
	report[0]=0x00; //Report ID
StephaneLenclud@27
   736
	report[1]=0x01; //Report length.
StephaneLenclud@27
   737
	report[2]=0x1F; //Command ID
StephaneLenclud@27
   738
	Write(report);
StephaneLenclud@27
   739
	}
StephaneLenclud@27
   740
StephaneLenclud@27
   741
StephaneLenclud@27
   742
/**
StephaneLenclud@27
   743
Set Address Counter (AC) values: 1BH + 60H + xxH
StephaneLenclud@27
   744
xxH: 00 ~ BFH
StephaneLenclud@27
   745
AC value represents the start address for graphic data.
StephaneLenclud@27
   746
There are 192 bytes as display RAM. It can be set on anywhere even if AC value is not visible area.
StephaneLenclud@27
   747
The default value is 00H.
StephaneLenclud@27
   748
Default: 00H
StephaneLenclud@27
   749
When clock is displayed, AC value is set 00H.
StephaneLenclud@27
   750
*/
StephaneLenclud@27
   751
void MDM166AA::SendCommandSetAddressCounter(unsigned char aAddressCounter)
StephaneLenclud@27
   752
	{
StephaneLenclud@27
   753
	FutabaVfdReport report;
StephaneLenclud@27
   754
	report[0]=0x00; //Report ID
StephaneLenclud@27
   755
	report[1]=0x03; //Report length.
StephaneLenclud@27
   756
	report[2]=0x1B; //Command ID
StephaneLenclud@27
   757
	report[3]=0x60; //Command ID
StephaneLenclud@27
   758
	report[4]=aAddressCounter;
StephaneLenclud@27
   759
	Write(report);
StephaneLenclud@27
   760
	}
StephaneLenclud@27
   761
StephaneLenclud@27
   762
StephaneLenclud@27
   763
/**
StephaneLenclud@27
   764
Set the defined pixel block to the given value.
StephaneLenclud@27
   765
StephaneLenclud@27
   766
@param The size of our pixel data. Number of pixels divided by 8.
StephaneLenclud@27
   767
@param Pointer to our pixel data.
StephaneLenclud@27
   768
*/
StephaneLenclud@27
   769
void MDM166AA::SendCommandWriteGraphicData(int aSize, unsigned char* aPixels)
StephaneLenclud@27
   770
    {
StephaneLenclud@27
   771
	//TODO: Remove that at some point
StephaneLenclud@27
   772
	SendCommandSetAddressCounter(0);
StephaneLenclud@27
   773
StephaneLenclud@27
   774
	const int KMaxPixelBytes=48;
StephaneLenclud@27
   775
	const int KHeaderSize=3;
StephaneLenclud@27
   776
	
StephaneLenclud@27
   777
	int remainingSize=aSize;
StephaneLenclud@27
   778
	int sizeWritten=0;
StephaneLenclud@27
   779
StephaneLenclud@27
   780
	while (remainingSize>0)
StephaneLenclud@27
   781
		{
StephaneLenclud@27
   782
		//Only send a maximum of 48 bytes worth of pixels per report
StephaneLenclud@27
   783
		const int KPixelDataSize=(remainingSize<=KMaxPixelBytes?remainingSize:KMaxPixelBytes);
StephaneLenclud@27
   784
StephaneLenclud@27
   785
		FutabaVfdReport report;
StephaneLenclud@27
   786
		report[0]=0x00; //Report ID
StephaneLenclud@27
   787
		report[1]=KPixelDataSize+KHeaderSize; //Report length. +3 is for our header first 3 bytes.
StephaneLenclud@27
   788
		report[2]=0x1B; //Command ID
StephaneLenclud@27
   789
		report[3]=0x70; //Command ID
StephaneLenclud@27
   790
		report[4]=KPixelDataSize; //Size of pixel data in bytes		
StephaneLenclud@27
   791
		memcpy(report.Buffer()+5, aPixels+sizeWritten, KPixelDataSize);
StephaneLenclud@27
   792
		Write(report);
StephaneLenclud@27
   793
		//Advance
StephaneLenclud@27
   794
		sizeWritten+=KPixelDataSize;
StephaneLenclud@27
   795
		remainingSize-=KPixelDataSize;
StephaneLenclud@27
   796
		}
StephaneLenclud@27
   797
    }