Jump to content
The simFlight Network Forums

Recommended Posts

Posted

Hi Pete,

PMDG have just announced that they will make available the Auto Pilot Lvars in the next hotfix/SP1 for the NGX. With that in mind I've started to update my GoFlight EFIS and MCP PRO LUA script so that the displays and button lights can be updated.

Until the lvars are available I have been testing code using CRS (course) values that can be accessed via 0C4E and 0C5E.

Although I can code this, I am having trouble with consistency. If I work the dial very slowly, everything is fine. If I turn it fast the display doesn't update. Then after a few seconds it catches up and updates even though I have the timer on 100ms (DISPLAYPOLL constant value in the code). When I watch the MCP PRO hardware it appears to freeze whilst the MCP in the VC is working fine. Once I stop turning the dial and after a few seconds I can see the CRS display flick through some values very quickly until it reflects whats in the VC. It feels like something is getting overwhelmed and sits in a queue or has timed out.

Not knowing how the underpinning events model schedules everything I have tried creating a variable to act as a "lock" token so that only the event holding the token can execute. I have also tried cancelling and restarting events in the code so that no double calls can execute. I have also added ipc.sleep delays. None of this has helped.

I'm not sure if its my code, LUA problems, or Go Flight Hardware/Driver Problems.

Basically the code consists of two events. One monitors the hardware and checks what dial, button has been pressed and calls the relevant funtion. The other updates the displays at a set interval:

   event.timer(DISPLAYPOLL, "GFAPDisplaysEvent") -- Start updating displays

   event.gfd(GFAPM, GFAPU, "GFAPEvent") -- Start monitoring events for MCP hardware

The main functions are:

function GFAPEvent()

   .....

   elseif dial == DCRS1 then
      changeCRS1(dialval)

   .....

end

function GFAPDisplaysEvent()

   gfd.SetDisplay(GFAPM,GFAPU, GFAPDCRS1, string.format("%03s", ipc.readSW("0C4E")))
   gfd.SetDisplay(GFAPM,GFAPU, GFAPDCRS2, string.format("%03s", ipc.readSW("0C5E")))
end

local function changeCRS1(speed)
   if speed > 0 then
      for x=1, speed, 1 do
         ipc.control(70008, 16384)
      end
   elseif speed < 0 then
      for x=-1, speed, -1 do
         ipc.control(70008, 8192)
      end
   end
end

Any ideas that can help me.

PS I have also added code to update the CRS display within the "for loops" in the changeCRS1 function and the same freezing like behaviour occurs.

PPS I have just sat watching FS Interogate and the MCP PRO whilst I rotated the CRS1 dial in the VC. Both updated with no problems. So the issue is stemming from either the IPC commands being sent or having a clash between two event calls.

Best wishes

Steve

Posted

Although I can code this, I am having trouble with consistency. If I work the dial very slowly, everything is fine. If I turn it fast the display doesn't update. Then after a few seconds it catches up and updates even though I have the timer on 100ms (DISPLAYPOLL constant value in the code). When I watch the MCP PRO hardware it appears to freeze whilst the MCP in the VC is working fine. Once I stop turning the dial and after a few seconds I can see the CRS display flick through some values very quickly until it reflects whats in the VC. It feels like something is getting overwhelmed and sits in a queue or has timed out.

Without examining your code (sorry, not much time at the moment), i would guess that you are updating the value in FS separately from updating the display. I've found in numerous hardware implementations, starting with EPIC-driven radio and A/P stacks made by FlightLink in the 90's , through all the PFC devices and more recently VRI and GoFlight, that this is always the sort problem that results.

What I find works best is this:

1. Yes, turning a dial updates FS, but also updates the display directly, not via FS, AND sets a time value = "time last changed by me" (say "TLCBM")

2. FS changing values are allowed to update the display but only after an elapsed time of X msecs after time "TLCBM".

The elapsed time is just a value above which you are pretty sure that the user has stopped turning the dial.

This overcomes the latency which is the cause of the build-up and apparent hang. Update dials locally when you know the value, only update from FS when the user isn't twiddling.

Try it and see.

Regards

Pete

Posted

DELAY = 2000
TLCBM = ipc.elapsedtime()

.....

local function changeCRS1(speed)
	if speed > 0 then
		for x=1, speed, 1 do
			ipc.control(70008, 16384)
		end
	elseif speed < 0 then
		for x=-1, speed, -1 do
			ipc.control(70008, 8192)
		end
	end
	gfd.SetDisplay(GFAPM,GFAPU, GFAPDCRS1, string.format("%03s", ipc.readSW("0C4E")))
	TLCBM = ipc.elapsedtime()
end


function GFAPDisplaysEvent()

	if ipc.elapsedtime() - TLCBM > DELAY then
		gfd.SetDisplay(GFAPM,GFAPU, GFAPDCRS1, string.format("%03s", ipc.readSW("0C4E")))
		gfd.SetDisplay(GFAPM,GFAPU, GFAPDCRS2, string.format("%03s", ipc.readSW("0C5E")))
	end
end

Tried the above. Still freezes the CRS display when turning the CRS dial fast. I tried various DELAY values from 100 to 2000ms. If I set the DELAY to 5000 and I keep turning the dial without pausing the freeze will occur but sometimes it will eventually unfreeze and the display updates which indicates its not an event clash because there was no way I paused for 5 seconds between dial rotations.

Posted

Tried the above. Still freezes the CRS display when turning the CRS dial fast.

Right -- and the offsets are uopdating okay. So it's got to be something in GFDev or deeper in GF's drivers. Maybe you have to limit the real time display calls so you don't send one more often than n mSecs. It'll still catch up at the end. Otherwise you'd have to limit the pace at which you read the dial.

It might be over the top to use "speed". Try just doing one change per click regardless of speed.

Pete

Posted

Hi Pete,

I tracked the problem down to the goflight event call.

Firstly I removed the goflight event call and replaced it with the following code that simply bombards FSX with course change values:

for x=1, 100000, 1 do
	changeCRS1(1)
end

The above ran fine, no lock ups with the course changing in FSX and the MCP PRO.

I then replaced all the code in GFAPEvent() with:

function GFAPEvent()
	changeCRS1(1)
end


...

event.gfd(GFAPM, GFAPU, "GFAPEvent") -- Start monitoring events for MCP hardware

so any activity on the MCP PRO would change the course. Although it was harder to lock up, it still locked the displays if you turned any dial continually fast, whilst all the buttons, switches and dials continued to work and call the event...so the VC was being updated but the MCP PRO display had frozen. Eventually the display recovers and works normally.

I thought cancelling and restarting the GFAPEvent within itself would stop double calls maybe, but no joy and adding sleep commands makes it unusable.

Any more thoughts?

Thanks

Steve

Posted

Right -- and the offsets are uopdating okay. So it's got to be something in GFDev or deeper in GF's drivers. Maybe you have to limit the real time display calls so you don't send one more often than n mSecs. It'll still catch up at the end. Otherwise you'd have to limit the pace at which you read the dial.

It might be over the top to use "speed". Try just doing one change per click regardless of speed.

Pete

Sorry pete, I posted before seeing this reply. I have added an ipc.sleep(100) into the GFAPEvent which really slows things down as well as cancelling and restarting the event:

function GFAPEvent()
	event.cancel("GFAPEvent") -- stop double event call
	changeCRS1(1)
	ipc.sleep(100)
	event.gfd(GFAPM, GFAPU, "GFAPEvent") -- Start monitoring events for MCP hardware
end

I can still lock the displays up. Sometimes it can take a minute, othertimes it will do it straight away.

Posted

I tracked the problem down to the goflight event call.

...

so any activity on the MCP PRO would change the course. Although it was harder to lock up, it still locked the displays if you turned any dial continually fast, whilst all the buttons, switches and dials continued to work and call the event...so the VC was being updated but the MCP PRO display had frozen. Eventually the display recovers and works normally.

Hmm. That's weird. It can't actually be the event call, because that is still calling the function because, as you say, the VC updates correctly. So somehow the call made to update the GF display woorks when called one way but not another, seemingly identical way.

The only difference is that, in the former code example, where there's no event and, in fact, no knob turning, the display updates, whilst when the GF device is providing continual inputs and dealing with outputs at the same time, it, or the USB driver or GFDev, kludges.

Seems to be some sort of mess occurring because it needs to do lots of inputs and outputs at the same time.

I won't have time for a few days, but if i can reproduce it I'll try using a USB monitor on it so I can see what happens. If it is at that sort of level I'm not sure there is an answer, really, except possibly report it as a suspected GFDev bug and hope it gets fixed. BTW could you tell me the exact version number of the GFDev you are using?

Regards

Pete

Posted

Basically the code consists of two events. One monitors the hardware and checks what dial, button has been pressed and calls the relevant funtion. The other updates the displays at a set interval:

   event.timer(DISPLAYPOLL, "GFAPDisplaysEvent") -- Start updating displays

   event.gfd(GFAPM, GFAPU, "GFAPEvent") -- Start monitoring events for MCP hardware

I was just reviewing this so I could determine whast I needed to do to work out what is going on, when I noticed that part, :"updates the displays at a set interval".

Why are you doing that, and what is the "set interval value"? Generally it is better to only ever update displays when they need updating. maybe an occasional refresh is in order in case something glitched, but usually it isn't needed.

By updating then at a regular interval independently of them being changed i reckon you are more likely to get into a clash situation, even if such shouldn't be detrimental. The proper way to keep displays up to date is to use the "event.offset" function, to tell you when the offset from which the display is derived has changed. So, instead of:

function GFAPDisplaysEvent()
   if ipc.elapsedtime() - TLCBM > DELAY then
   	gfd.SetDisplay(GFAPM,GFAPU, GFAPDCRS1, string.format("%03s", ipc.readSW("0C4E")))
   	gfd.SetDisplay(GFAPM,GFAPU, GFAPDCRS2, string.format("%03s", ipc.readSW("0C5E")))
   end
end
event.timer(DISPLAYPOLL, "GFAPDisplaysEvent") -- Start updating displays

you ideally want a separate display function for each display and a separate event.offset for it. Like

function GFAPDisplaysCRS1(off,val)
   	gfd.SetDisplay(GFAPM,GFAPU, GFAPDCRS1, string.format("%03s", val))
end

event.offset(0x0C4E, "SW" , "GFAPDisplaysCRS1")

and similarly for CRS2.

Of course, if you then also need the delay whilst the user's turning the knob, that must be added, but you'd then need some extra code to "catch up" after the 'safety' time. Try without first.

Regards

Pete

Posted

I was just reviewing this so I could determine whast I needed to do to work out what is going on, when I noticed that part, :"updates the displays at a set interval".

Why are you doing that, and what is the "set interval value"? Generally it is better to only ever update displays when they need updating. maybe an occasional refresh is in order in case something glitched, but usually it isn't needed.

By updating then at a regular interval independently of them being changed i reckon you are more likely to get into a clash situation, even if such shouldn't be detrimental. The proper way to keep displays up to date is to use the "event.offset" function, to tell you when the offset from which the display is derived has changed. So, instead of:

function GFAPDisplaysEvent()
   if ipc.elapsedtime() - TLCBM > DELAY then
   	gfd.SetDisplay(GFAPM,GFAPU, GFAPDCRS1, string.format("%03s", ipc.readSW("0C4E")))
   	gfd.SetDisplay(GFAPM,GFAPU, GFAPDCRS2, string.format("%03s", ipc.readSW("0C5E")))
   end
end
event.timer(DISPLAYPOLL, "GFAPDisplaysEvent") -- Start updating displays

you ideally want a separate display function for each display and a separate event.offset for it. Like

function GFAPDisplaysCRS1(off,val)
   	gfd.SetDisplay(GFAPM,GFAPU, GFAPDCRS1, string.format("%03s", val))
end

event.offset(0x0C4E, "SW" , "GFAPDisplaysCRS1")

and similarly for CRS2.

Of course, if you then also need the delay whilst the user's turning the knob, that must be added, but you'd then need some extra code to "catch up" after the 'safety' time. Try without first.

Regards

Pete

Hi Pete,

Thats a fair observation, however the display routine above was eliminated in my testing as detailed in one of the posts. By making delay equal 5 (5000ms) seconds, the display routine never ran because I kept turning the dial and it still locked the displays:

function GFAPDisplaysEvent() 

        if ipc.elapsedtime() - TLCBM > DELAY then 
                gfd.SetDisplay(GFAPM,GFAPU, GFAPDCRS1, string.format("%03s", ipc.readSW("0C4E"))) 
                gfd.SetDisplay(GFAPM,GFAPU, GFAPDCRS2, string.format("%03s", ipc.readSW("0C5E"))) 
        end 
end

thus the routine that was running that caused the lock up was:

local function changeCRS1(speed) 
        if speed > 0 then 
                for x=1, speed, 1 do 
                        ipc.control(70008, 16384) 
                end 
        elseif speed < 0 then 
                for x=-1, speed, -1 do 
                        ipc.control(70008, 8192) 
                end 
        end 
        gfd.SetDisplay(GFAPM,GFAPU, GFAPDCRS1, string.format("%03s", ipc.readSW("0C4E"))) 
        TLCBM = ipc.elapsedtime() 
end 

called from:

function GFAPEvent() 
        changeCRS1(1) 
end

....


event.gfd(GFAPM, GFAPU, "GFAPEvent") -- Start monitoring events for MCP hardware

Posted

I'm using version 2.0.1.1 of GFDev dated 23/6/2011.

Where is that from? My latest is 2.0.0.1 dated earlier the same month. I can't seem to find it on the GF website -- what purports to be "GFDevSDK.exe" is actually their driver "GFDevFSX.exe".

I'm using 4.721 of FSUIPC.

I'm using 4.727.

I can manage to reproduce what you get, but only if I do silly things.

I'm not testing on the PMDG but the default FSX 738. This code works fine (I've only done CRS1 on the MCP Pro):

function GFAPDisplaysCRS1(off,val)
   	gfd.SetDisplay(GFAPM,GFAPU, GFAPDCRS1, string.format("%03s", val))
end

local function changeCRS1(mod, id, speed)
   if speed > 0 then
  	for x=1, speed, 1 do
 		ipc.control(65663, 1)
  	end
   elseif speed < 0 then
  	for x=-1, speed, -1 do
 		ipc.control(65662, 1)
  	end
   end
end

function GFAPEvent(mod, id)
	gfd.GetValues(mod, id)
	x = gfd.dial(0)
	if x ~= 0 then changeCRS1(mod, id, x) end
end

function GFAPDisplaysCRS1(off,val)
   gfd.SetDisplay(GFMCPPRO, 0, 0, string.format("%03s", val))
end

event.gfd(GFMCPPRO, 0, "GFAPEvent")
event.offset(0x0C4E, "SW" , "GFAPDisplaysCRS1")

That's about as simple as you can get it for the one display and dial. It would be even more fail-safe and efficient if, as I suggested, the dial processing also updated the display and set a time to make the display updater stop updating as well when the knob is turning, but even without that i couldn't get it to fail. However, the PMDG aircraft is a different matter and if that is overloading things a little too it would be worth adding that extra code -- but note, this way, you really should have a separate timer value for each dial (though i suppose it wouldn't matter as you are unlikely to turn more than one at a time, are you?).

To force an overload I changed it to

function GFAPDisplaysCRS1(off,val)
   	gfd.SetDisplay(GFAPM,GFAPU, GFAPDCRS1, string.format("%03s", val))
end

local function changeCRS1(mod, id, speed)
   if speed > 0 then
  	for x=1, speed, 1 do
 		ipc.control(65663, 1)
  	end
   elseif speed < 0 then
  	for x=-1, speed, -1 do
 		ipc.control(65662, 1)
  	end
   end
end

function GFAPEvent(mod, id)
	gfd.GetValues(mod, id)
	x = gfd.dial(0)
	if x ~= 0 then changeCRS1(mod, id, x) end
end

function GFAPDisplaysCRS1( )
   gfd.SetDisplay(GFMCPPRO, 0, 0, string.format("%03s", ipc.readSW("0C4E")))
end

event.gfd(GFMCPPRO, 0, "GFAPEvent")
 event.timer(10, "GFAPDisplaysCRS1")

which is more like your original, except probably with a much faster Timer update of the display -- 100 times per second. That does exhibit the block you see. However, if i change that time to 100 instead of 10, for 10 updates per second, it still didn't fail!

As far as I can tell, this block is not happening inside my code, but deeper. I couldn't really get into it very closely. When I use the USB logging tool it is MUCH harder to cause the block to occur, even with a very fast update rate. So it is definitely some overload and timing dependent. The USB transfers never stop -- the windows driver level is certainly polling continuously at 30 mSec intervals. By stopping the logging as soon as possible after noticing that the display had stopped changing I see that the USB packets were still actually changing at about the same rate as before, so now i am very suspicious that it is the device which chokes -- i.e. the firmware processing of the frames and display changes -- rather than something in the PC. However, in this case you'd expect things to be better with the PMDG aircraft than the default PROVIDED that you only update the display when the value changes, as in my first example above.

Note that such a conclusion does not conflict with your earlier result, here:

Firstly I removed the goflight event call and replaced it with the following code that simply bombards FSX with course change values:

for x=1, 100000, 1 do
        changeCRS1(1)
end

The above ran fine, no lock ups with the course changing in FSX and the MCP PRO.

because in this case the device is not also dealing with the dial turning and updated data sent o the PC as a result.

Anyway, I think I've taken this as far as I can. Try changing the code as shown above, so you only update the display when the relevant offset changes, and maybe also add the turning-timeout protection (but not large numbers like your 2000: A second (100) or even less (500) should suffice.

Regards

Pete

Posted

Thats a fair observation, however the display routine above was eliminated in my testing as detailed in one of the posts. By making delay equal 5 (5000ms) seconds, the display routine never ran because I kept turning the dial and it still locked the displays:

Your later reply crossed with my results summary.

I'm afraid I cannot reproduce any block without a very fast independently running display update, and even then it is hard to reproduce -- I can only just about twiddle the dial fast enough, and then only by doing it back and forth like mad.

Whatever is causing the block it is at some lower level than I can handle I'm afraid. I'm wondering if the specific hardware module you have is faulty. Or maybe it's down to the USB ocnnection route. This GFMCPPRO was plugged into a 2-USB hub on my monitor, not onto the PC directly nor onto one of the larger 7- or 8-port ones often used with GF stuff.

To eliminate GFDev.dLL as a possible suspect you could consider programming using the HID facilities in the Lua COM library. The GFD library uses GFDev.DLL, but the COM library goes direct to the port functions themselves.

Pete

Posted

I know someone else with an MCP PRO and uses the PMDG NGX. I'll get them to test things out and see if they have the same problem.

Did you try my code on the default 738?

I got GFdev from their latested software release version 2.03 here: http://www.goflighti.../software.shtml

Oh, you mean the complete software stuff to drive GF modules with GF software? Hmm. Don't want all that. It should be in the SDK, but the thing labelled SDK is not, its the FSX driver, and the SDK down the bottom of the page seems to be very very old (version 1.40).

Regards

Pete

Posted

Hi Pete,

I have re written things using event.offsets and it works OK. I still get the odd display freeze though. I will test my script on some other peoples MCP's to eliminate any hardware faults.

Thanks for your help

Steve

Posted

I have re written things using event.offsets and it works OK. I still get the odd display freeze though. I will test my script on some other peoples MCP's to eliminate any hardware faults.

I seem to recall seeing reports of occasional display freezes with GF devices when folks use the GoFlight software too, so it might just be one of those little niggles with these devices. I can't find any at present though. I may be worth while telling your story in the GoFlight support forum to see what others say.

Regards

Pete

Posted

Yes I think most Mcppro users have had that problem. It's a complete lock up though, displays, dials and switches which requires an unplug of the mcppro. Then you have the problem of fsuipc not seeing the mcppro anymore etc. It's so annoying when it happens. Perhaps at some point you could look at a way for fsuipc to re-recognise the mcppro after a replug.... That would be very useful.

My issue is different though, just the displays freezing for a short period.

Posted

Yes I think most Mcppro users have had that problem. It's a complete lock up though, displays, dials and switches which requires an unplug of the mcppro. Then you have the problem of fsuipc not seeing the mcppro anymore etc. It's so annoying when it happens. Perhaps at some point you could look at a way for fsuipc to re-recognise the mcppro after a replug.... That would be very useful.

It theoretically should do -- it can with other USB devices, and can with GF devices when driven via the USB interface (the COM library in Lua with the HID facilities). You only need to go into the FSUIPC options again and it does a re-scan.

I think currently the restart is needed to get GFDev.DLL unloaded and restarted. I'll look to see if there's another way to force that (maybe an uload library call or something)

My issue is different though, just the displays freezing for a short period.

Yes, but I'm wondering if it's related, just that using the lower level GFDev calls gets over the 'complete hang' which is presumably the GFMCPPro driver not knowing how to recover.

Pete

  • 2 months later...
Posted

Steve and Peter,

Here is an FYI about the freeze-ups on the GoFlight MCP Pro:

I posted this on the GoFlight Forum after talking to their support tech:

http://www.goflightinc.com/forums/index.php/topic,1156.0.html

Ohsirus,

Thanks for your quick response.

I just checked with John, the GoFlight Tech, and he confirmed that A25 fixes the freeze problem at the expense of eliminating the serial number. Currently, I'm at version A22 and occasionally have freeze ups, which have been minimized by adding a USB 1.1 card.

Unfortunately, if I update to the latest version now, my MCP Pro will probably no longer work with the Flightsims Lab driver I have for my PMDG 747-400 as there is no serial number with the update. Currently, I've migrated to the PMDG 737NGX which doesn't have software support yet from GoFlight either, although there is someone working on some sort of script file for it.

I saw your response about Flightsim Labs position on this, which is unfortunate. My take on this is that new users who now purchase the MCP Pro (I assume new units are shipping with A25), won't be able to buy the driver from Flighsims lab because it won't work. This would mean no future business opportunities for either the FSX PMDG 747 or the FS9 PMDG 737. Only those prospective customers with older units would be compatible with the driver. So Flightsims lab is shutting out new business opportunities and also prohibiting existing customers who have their MCPPro units upgraded to A25 to fix te freezing issue from using their units with these two PMDG planes.

Bill

Posted

Steve and Peter,

Here is an FYI about the freeze-ups on the GoFlight MCP Pro:

I posted this on the GoFlight Forum after talking to their support tech:

http://www.goflighti...pic,1156.0.html

Ohsirus,

Thanks for your quick response.

I just checked with John, the GoFlight Tech, and he confirmed that A25 fixes the freeze problem at the expense of eliminating the serial number. Currently, I'm at version A22 and occasionally have freeze ups, which have been minimized by adding a USB 1.1 card.

Unfortunately, if I update to the latest version now, my MCP Pro will probably no longer work with the Flightsims Lab driver I have for my PMDG 747-400 as there is no serial number with the update. Currently, I've migrated to the PMDG 737NGX which doesn't have software support yet from GoFlight either, although there is someone working on some sort of script file for it.

I saw your response about Flightsim Labs position on this, which is unfortunate. My take on this is that new users who now purchase the MCP Pro (I assume new units are shipping with A25), won't be able to buy the driver from Flighsims lab because it won't work. This would mean no future business opportunities for either the FSX PMDG 747 or the FS9 PMDG 737. Only those prospective customers with older units would be compatible with the driver. So Flightsims lab is shutting out new business opportunities and also prohibiting existing customers who have their MCPPro units upgraded to A25 to fix te freezing issue from using their units with these two PMDG planes.

Bill

Hello

I would forget about waiting for Goflight to support the NGX and use Stephen Munns script instead, works really well here with my MCP

which is Firmware A17.

I am not convinced at all that these lockups are caused by the firmware, I have no issues at all and my firmware revision is ancient.

The MCP seems to be really sensitive to how it is connected to the PC, you are going in the right direction using a USB card rather than MB ports.

Here is what I needed to do in order to get my MCP to play nicely.

Added a Belkin 5 port USB 2.0 card which uses the NEC chip, do not use any card with the VIA chip.

Plugged the MCP into a Belkin F5U021u powered hub dedicated just to the MCP.

The only hubs I have consistent results with are the F5U021u (4port) and the F5U027u (7port) all my Goflight stuff is on these hubs, Max two units per hub with the MCP on its own hub.

No lockups here with A17 since setting it up like this.

Goflight have been saying for years that a firmware update cures all ills with the MCP, they are still saying this and we are now at A25.

I believe that it is more to do with the MCP's power demands and signal path back to the host PC.

And do try Stephens script with the NGX, terrific stuff.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use. Guidelines Privacy Policy We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.