Stránky

úterý 31. srpna 2010

Dekódovaní Canbus sběrnice v osobních automobilech

Při vývoji zařízeni pro osobní automobily jsme potřebovaly dostat událost z řídicí jednotky .
Automobily vyrobené po roce 2004 převážně používají sběrnici canbus jako komunikační protokol mezi perifériemi (řídicí jednotka, motor navigace, snímače dveří).




Can bus na osciloskopu. Zdroj: http://pikkupossu.1g.fi/tomi/projects/p-bus/p-bus.html



K dispozici jsme měli vůz škoda Octavia II, nicméně ten to postup bude platit pro všechny vozy skupiny VW (Škoda, Audi, Seat, VW) a pro ostatní značky, které mají canbus s malými odchylkami.

Vozy skupiny VW mají 2 sběrnice motorovou (500kb/s) a komfortní (100kb/s).
My se pokusíme dekódovat událost stisku dálkového ovladače odemknout/uzamknout. Tato událost běhá po komfortní sběrnici.

Pro odchytávaní jsme použily dobře dostupný adapter USB2CAN. Pozor na verze low speed a high speed !!! high speed neznamená že je lepší, ale že se používá na rychlou (motorovou) sběrnici .
Verze high speed nelze použít na komfortní sběrnici! Musíte použít low speed.





Analyticky software dodávaný s USB2CAN


Když napojíte automobil na adapter a stisknete na dálkovém ovládaní zamknout a odemknout auto vychrlí 100 různých zpráv. Takže je problém určit jaká zpráva znamená stisk na dálkovém ovladači. K určení zprávy, která obsahuje vaší informaci lze použít následující trik. USB2CAN obsahuje nástroj Log analyzer, kterým lze načíst zprávy za delší dobu a dát jejich hodnoty do grafu. Takže postup je takový. Vyprázdníme log pomoci Settings -> Reset log, provedeme 2x zamknuti a odemknutí s
10s pauzami. Potom log uložíme Settings -> Save log . Pustíme Log analyzer (externí program dodávaný USB2CAN) který nám zobrazuje průběh hodnot jednotlivých zpráv.




Naše požadovaná zprava obsahující stisk tlačítka zamknout / odemknout


Nutno podotknout, že hledání zprávy komplikuje skutečnost, že každá zpráva obsahuje 8 bajtu, takže počet možností je ve skutečnost počet zpráv x 8. Naštěstí většina hodnot je nulová a většina průběhů naprosto odlišná. Věřte, že to opravdu lze :-)

pondělí 30. srpna 2010

Jak vytvořit vlastní tlačítka s obrázky za pomocí template ve WPF?

Jak vytvořit vlastní tlačítka s obrázky za pomocí template ve WPF?

Aby bylo možné jednoduše vytvořit tlačítko, kde při každém stavu je použitý jiný zdrojový obrázek, je zapotřebí vytvořit si vlastní template, která bude osahovat veškeré potřebné informace.

Začneme tím, že si v Blendu vložíme na layout klasický button. Ten nám možnost použití různých obrázků při různých stavech tlačítka (např. MouseOver,MouseClick,MouseLeftButtonDown) nenabízí a tak je zapotřebí kliknout pravým tlač. na to tlačítko/edit template/Create empty.



Po zvolení jména template a umístění se Vám zobrazí prázdný grid, kam nandáte potřebné elementy (obrázky).  Já použiji 3 různé obrázky pro defaultní stav, zmáčknutý stav a stav, kdy nelze mačkat (disabled stav).
 

Všechny 3 obrázky dám na stejnou pozici, nastavím stejnou velikost a další potřebné vlastnosti (zarovnání,Stretch….). Pro zobrazení Contentu na tlačítku ještě do gridu přidám ContentPresenter,  který automaticky binduje Content tlačítka, viz. níže. ContentPresenter nastavím na střed a dám mu automatickou velikost.

Nyní je zapotřebí nastavit chování tlačítka v jednotlivých stavech. V záložce Triggers je vidět, že se nacházíme ve stavu Default, takže necháme obrázku, který má být vidět visibilitu na visible a ostatním nastavíme na hidden. Poté přidáme v záložce Triggers novou Property, kde nastavíme target-element.isPressed = true (true je tam potřeba napsat). Nyní musíme nastavit visibilitu obrázku, který má být vidět při stisknutém tlačítku a ostatní musí být hidden. Poslední stav je stav Disabled, takže dáme zase v Triggers přidat Property, kde bude target-element.isEnabled = false a nastavíme obrázku,který má být vidět při disabled stavu visibilitu a ostatní dáme na hidden. Tím máme vytvořenou template pro vlastní tlačítka. Samozřejmě je možné jednotlivým stavům přiřadit třeba různé storyboardy na nějaké animace atd., ale to už je jen na Vás.

Celá template se nám uloží v XAML kódu do Window.Resources , kde jsou vidět naše všechny kroky a lze je jednoduše modifikovat.
Show/Hide
<ControlTemplate x:Key="ButtonControlImageBut" TargetType="{x:Type Button}">
<Grid Margin="0">
<Image x:Name="image1" HorizontalAlignment="Stretch" Margin="0" VerticalAlignment="Stretch" Source="Images/butPressed.png" Stretch="Uniform" Visibility="Hidden"/>
<Image x:Name="image" HorizontalAlignment="Stretch" Margin="0" VerticalAlignment="Stretch" Source="Images/butDefault.png" Stretch="Uniform"/>
<Image x:Name="image2" HorizontalAlignment="Stretch" Margin="0" VerticalAlignment="Stretch" Source="Images/butDisabled.png" Stretch="Uniform" Visibility="Hidden"/>
<ContentPresenter Margin="0" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Visible"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Visibility" TargetName="image" Value="Hidden"/>
<Setter Property="Visibility" TargetName="image1" Value="Visible"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Visibility" TargetName="image2" Value="Visible"/>
<Setter Property="Visibility" TargetName="image" Value="Hidden"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>



Nyní se vrátíme tedy na hlavní layout, kde máme naše jedno tlačítko plně funkční (stisknutím tlač. Button, hned vedle Template). V Properties můžeme tlačítku nastavit požadovaný Content, font, velikost fontu atd. Když vložíme další tlačítko na layout, tak na něj dáme pravým tlač. a dáme EditTemplate/ApplyResource a zvolíme náš vlastní vytvořený ressource. Tímto způsobem můžeme templatu aplikovat na libovolné množství tlačítek.

I po nastavení isEnabled na false je vidět, že vše funguje jak má.

Další možnosti editace:
Celou template můžete v Xaml kódu zkopírovat a jednoduše použít templatu např. pro červené, modré a bílé tlačítka tím, že změníme název template a změníme imageSource cestu a je hotovo.
Pokud potřebujete použít na zobrazení Contentu tlačítek jinou kontrolu než je ContentPresenter, tak je možné použít třeba TextBlock a nastavit mu bindování Contentu, jinak by se nám tam požadovaný text nezobrazoval. Text="{TemplateBinding Content}"
Použití jednoho Storyboardu na více prvků najednou.


Pro názornou ukázku si na layout dám např. 5x TextBlock a vytvořím si jednoduchý Storyboard na prvním TextBlocku, který mi bude měnit barvu pozadí. Nyní se dostávám do situace, kdy ten Storyboard je jen pro ten jeden TextBlock. Já potřebuji, abych si v kódu zvolil, že se animace má provést na TextBlocku1, TextBlocku4 a TextBlocku5. Řešení je vcelku jednoduché. V Xaml kódu musíme odstranit Storyboard.TargetName="TB1" a tím máme Storyboard, který není přiřazen žádnému targetu. Pozor, pokud odstraníte TargetName, tak v Blendu již nelze upravovat samotný storyboard, jelikož hází chybu. Pokud jsou potřeba nějaké pozdější úpravy, tak si tam vždycky ten target musíte dopsat, vyzkoušet a pak zase vymazat.
Pro názorné předvedení si vložím na layout tlačítko a ve zdrojovém kódu udělám obsluhu události Click. Abych mohl přistupovat ke storyboardům, tak musím přidat

using System.Windows.Media.Animation;

V obsluze tlačítka si tedy najdu ten storyboard a ten aplikuju na mnou zvolené TextBlocky.

Storyboard animace = (Storyboard)FindResource("StoryboardAnimaceBackground");
animace.SetValue(Storyboard.TargetNameProperty, TB1.Name);
animace.Begin();
animace.SetValue(Storyboard.TargetNameProperty, TB4.Name);
animace.Begin();
animace.SetValue(Storyboard.TargetNameProperty, TB5.Name);
animace.Begin();


Tím je vyřešený celý problém. Udělám ještě drobnou úpravu v Xaml kódu, jelikož nechci aby mi animace vždy zůstala zastavená na konci, ale aby se ukončila. Do storyboardu v Xamlu přidám FillBehavior="Stop" a je to.


Přeji mnoho zdaru.

Pozor na práci s databází ve webovém prostředí

Nedávno jsem řešil opravdu podivný problém s webovou aplikací, která využívala databázi. Z neznámých důvodu docházelo i u velmi jednoduchých databázových operaci k přepočtům. Na vině bylo více vláknové prostředí, které je běžné pro webovou aplikaci




Vysvětlím problém na jednoduchém příkladu (z ukázek jsou vypuštěny transakce kuli zjednodušení)



Show/Hide
[WebMethod]
public PlayResult Play(int stationId)
{
var station = Station.Find(stationId);

//nejaka narocnejsi operace

station.Credit = station.Credit - 5;
station.Save();
}



Show/Hide
[WebMethod]
public void AddCredit(int stationId, int credit)
{
var station = Station.Find(stationId);
station.Credit = station.Credit + credit;
station.Save();
}



vypadá to jako jednoduchý přímočarý kód ale ....

Co když nastane situace, že se zavolá požadavek Play, načte kredit z databáze, začne vykonávat nějakou herní logiku a mezitím se spustí rychlý požadavek (nemusí byt ani rychlejší ale systém ho zrovna zpracuje rychleji) AddCredit přičte kredit a uloží do databáze. Po té se dokončí požadavek AddCredit odečte kredit a uloží. A co se stane? Požadavek play má v paměti načteny starý kredit a při uložení opět přepíše kredit na povodní hodnotu. Jako by k požadavku AddCredit vůbec nedošlo


Při hledaní problému jsem vyzkoušel sem 3 různé možnosti


Zapnout v databázi Izolaci transakci na serializable
toto je opravdu nedostatečné řešení jelikož veškeré požadavky jdou za sebou a v případě nějakého delšího dotazu musí ostatní čekat na dokončení. Navíc databáze nečeká na dokončení, ale vrací chybu.


Použít pesimistické zamykání v databázové vrstvě
Otestoval jsem zamykání pomocí SELECT id FROM stations WHERE id = 3 FOR UPDATE; (takto implementuje pesimistické zamykání nhibernate). Toto řešení nebylo použitelný. Sice update čekal, dokud se neprovede předchozí update, ale nebránilo to načíst do proměnných neplatné hodnoty. Vlákno se pozastavuje pozdě.


Použít synchronizaci vlakem v aplikační vrstvě
Toto řešení komplikuje provoz aplikace v aplikačním clusteru, protože je dost problém uzamykat vlákna mezi více strojena nicméně toto řešení nakonec fungovalo správně



Ukázka řešení


pro synchronizaci jsem použil Mutex (jelikož se jednotlivé worker procesy se v IIS nevidí nelze použít lock )

Aby nedocházelo k výkonnostním problémům je dobré synchronizovat požadavky jenom v rámci jedny stanice (ostatní stanice nemusí čekat na dokončení operace jiné stanice). to to lze vyřešit pomocí jemného parametru který lze v Mutex použít (alespoň v c#)


Show/Hide
[WebMethod]
public PlayResult Play(int stationId)
{
var contexLocker = new Mutex(false, "sync_mutext_" + stationId);
contextLocker.WaitOne();
try{

var station = Station.Find(stationId);

//nejaka narocnejsi operace

station.Credit = station.Credit - 5;
station.Save();
}
catch (Exception)
{
throw;
}
finally
{
contextLocker.Release();
}


}




Show/Hide
[WebMethod]
public void PridejCredit(int stationId, int credit)
{
var contexLocker = new Mutex(false, "sync_mutext_" + stationId);
contextLocker.WaitOne();
try{
var station = Station.Find(stationId);
station.Credit = station.Credit + credit;
station.Save();
}
catch (Exception)
{
throw;
}
finally
{
contextLocker.Release();
}
}



od té doby jsou data 100% správná