LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

[5] JAVA i STM32 – ekspresowy kurs programowania z MicroEJ – stacja pogodowa

 

Uwaga! Wszystkie opublikowane odcinki kursu są dostępne pod adresem. 

W ostatnim artykule serii poświęconej środowisku MicroEJ (JAVA dla mikrokontrolerów, w artykułach skupiamy się na wersji tego języka dla STM32) zostanie przedstawiony sposób na tworzenie własnych widgetów i wykorzystanie ich w interfejsie graficznym. Jako przykład posłuży odczytywanie w czasie rzeczywistym danych z czterech czujników: temperatury (MCP9801), ciśnienia (LPS25HB), wilgotności (HTS221) i natężenia światła (TSL2561) oraz prezentacja wyników na wykresach. Wszystkie czujniki zostały podłączone do zestawu STM32F429I-DISCO za pośrednictwem wspólnej magistrali I2C. Pokazany przykład może zostać wykorzystany jako prosta stacja pogodowa.

Interfejs graficzny

Pierwszym krokiem jest jak zwykle przygotowanie platformy (JPF). Tak samo jak w poprzednim przykładzie, można wykorzystać przygotowaną wcześniej platformę Full. Przechodząc do tworzenia projektu należy pamiętać, aby wybrać odpowiednią listę dołączanych modułów:

  • EDC
  • EJ.MOTION
  • MICROUI
  • MWT
  • SNI

Ostatnim krokiem konfiguracji projektu jest dodanie biblioteki Widgets. Sposób dołączania tej biblioteki wraz z dokumentacją do projektu zostało przedstawione w przykładzie ilustrującym wykonanie zegara z budzikiem z obsługą za pomocą touch-panela.

Interfejs graficzny obejmuje cztery przyciski klasy com.is2t.mwt.widgets.button.Button oraz cztery wykresy klasy ChartWidget, która zostanie opisana w kolejnym rozdziale. Z uwagi na mały rozmiar wyświetlacza, w danej chwili będzie wyświetlany tylko jeden wykres, a do przełączania wykresów posłużą wspomniane przyciski. Opisy przycisków zostały wykorzystane również do prezentacji ostatniej wartości odczytanej z czujnika. Wymienione obiekty zostały rozmieszczone na ekranie za pomocą widgetu typu BorderComposite. Umieszcza on obiekty na ekranie w pięciu lokalizacjach: NORTH, SOUTH, EAST, WEST i CENTER. Centralna część została wykorzystana do wyświetlenia wykresu, przyciski zaś umieszczono w części NORTH w siatce o rozmiarze 2×2 utworzonej za pomocą trzech obiektów typu GridComposite.

List. 1. Kod klasy MyLook

public class MyLook implements Look {
	private static final int FOREGROUND_COLOR_CONTENT = 0x000000;
	private static final int FOREGROUND_COLOR_DEFAULT = 0x000000;
	private static final int FOREGROUND_COLOR_FOCUSED = 0x000000;
	private static final int BACKGROUND_COLOR_CONTENT = 0xB7A9A9;
	private static final int BACKGROUND_COLOR_DEFAULT = 0xB7A9A9;
	private static final int BACKGROUND_COLOR_FOCUSED = 0xD1DDDD;
	private static final int BORDER_COLOR_CONTENT = 0x969aa2;
	private static final int BORDER_COLOR_DEFAULT = 0x969aa2;
	private static final int BORDER_COLOR_FOCUSED = 0x969aa2;
	private static final int SMALL_FONT_INDEX = 0;
	private static final int MEDIUM_FONT_INDEX = 1;
	private static final DisplayFont SMALL_FONT = DisplayFont.getFont(DisplayFont.LATIN, 10, DisplayFont.STYLE_PLAIN);
	private static final DisplayFont MEDIUM_FONT = DisplayFont.getFont(DisplayFont.LATIN, 30, DisplayFont.STYLE_PLAIN);
	private static final DisplayFont[] FONTS = {SMALL_FONT, MEDIUM_FONT};
	
	@Override
	public int getProperty(int resource) {
		switch (resource) {
		case LookExtension.GET_SMALL_FONT_INDEX:
			return SMALL_FONT_INDEX;
		case LookExtension.GET_MEDIUM_FONT_INDEX:
			return MEDIUM_FONT_INDEX;
		case Look.GET_FOREGROUND_COLOR_CONTENT:
			return FOREGROUND_COLOR_CONTENT;
		case Look.GET_BACKGROUND_COLOR_DEFAULT:
			return BACKGROUND_COLOR_DEFAULT;
		case Look.GET_BORDER_COLOR_CONTENT:
			return BORDER_COLOR_CONTENT;
		case Look.GET_BORDER_COLOR_DEFAULT:
			return BORDER_COLOR_DEFAULT;
		case Look.GET_BACKGROUND_COLOR_CONTENT:
			return BACKGROUND_COLOR_CONTENT;
		case Look.GET_FONT_INDEX_DEFAULT:
			return MEDIUM_FONT_INDEX;
		case Look.GET_FOREGROUND_COLOR_DEFAULT:
			return FOREGROUND_COLOR_DEFAULT;
		case Look.GET_BACKGROUND_COLOR_FOCUSED:
			return BACKGROUND_COLOR_FOCUSED;
		case Look.GET_BORDER_COLOR_FOCUSED:
			return BORDER_COLOR_FOCUSED;
		case Look.GET_FOREGROUND_COLOR_FOCUSED:
			return FOREGROUND_COLOR_FOCUSED;
		case Look.GET_FONT_INDEX_FOCUSED:
			return MEDIUM_FONT_INDEX;
		default:
			System.out.println("Need resource: "+resource);
		}
		return 0;
	}

	@Override
	public DisplayFont[] getFonts() {
		return FONTS;
	}
} 

Do projektu należy także dodać obiekty odpowiedzialne za wygląd aplikacji. W przykładzie wykorzystano klasy MyLook i MyTheme z poprzedniego projektu. Zmianie uległy jednak użyte czcionki: większa o rozmiarze 30 px do opisu przycisków oraz mniejsza o rozmiarze 10 px do opisu osi wykresów. Obie czcionki, tak jak poprzednio, należy dodać do projektu nie zapominając o pliku z ich listą i lokalizacją. Przed uruchomieniem symulacji należy jeszcze dodać z projektu zegara klasę DesktopRenderer pozwalającą na dowolną zmianę koloru tła. Podczas konfiguracji symulacji należy pamiętać o dodaniu pliku z listą czcionek w zakładce Configuration?MicroUI?Font.

Kod źródłowy klas MyLook oraz MyTheme przedstawiono na listingach 1 i 2, natomiast kod klasy Main – na listingu 3.

List. 2. Kod klasy MyTheme

public class MyTheme extends Theme {
	
	@Override
	public String getName() {
		return null;
	}

	@Override
	protected void populate() {		
		Drawer drawer = new PlainDrawer();
		add(new TitleLabelRenderer());
		add(new DesktopRenderer());
		add(new ButtonRenderer(drawer));
	}

	@Override
	public Look getDefaultLook() {
		return new MyLook();
	}

	@Override
	public boolean isStandard() {
		return false;
	}
} 

List. 3. Kod klasy Main

public class Main {
	
	private static Button temperatureButton;
	private static Button humidityButton;
	private static Button lightButton;
	private static Button pressureButton;	
	private static BorderComposite bc;

	public static void main(String[] args) {
		
		MicroUI.errorLog(true);
		MWT.RenderingContext.add(new MyTheme());
		Desktop desktop = new Desktop();
		Panel panel = new Panel();
		
		GridComposite valuesGcH = new GridComposite(MWT.HORIZONTAL, 2);
		GridComposite valuesGcV1 = new GridComposite(MWT.VERTICAL, 2);
		GridComposite valuesGcV2 = new GridComposite(MWT.VERTICAL, 2);
		
		temperatureButton = new Button("T: ");
		valuesGcV1.add(temperatureButton);
				
		pressureButton = new Button("P: ");
		valuesGcV1.add(pressureButton);
		
		humidityButton = new Button("H: ");
		valuesGcV2.add(humidityButton);
		
		lightButton = new Button("L: ");
		valuesGcV2.add(lightButton);
		
		valuesGcH.add(valuesGcV1);
		valuesGcH.add(valuesGcV2);
		
		bc =new BorderComposite(MWT.HORIZONTAL);
		bc.addAt(valuesGcH, MWT.NORTH);
		
		panel.setWidget(bc);
		panel.show(desktop, true);
		desktop.show();			
	}
}
 

Tworzenie nowego widgetu

Aby móc przedstawić dane na wykresach należy przygotować dwie klasy: ChartWidget oraz ChartRenderer. Pierwsza z nich przechowuje wszystkie potrzebne dane, w tym listę punktów do wyświetlenia. Druga z klas wykorzystuje te dane aby narysować wykres na przydzielonym obszarze ekranu.

List. 4. Kod klasy ChartWidget

public class ChartWidget extends Widget {

	private float maxY;
	private float minY;
	private int maxPoints;
	private ArrayList points;
	private int color;

	public ChartWidget(int maxPoints, int color) {
		this.maxPoints = maxPoints;
		this.points = new ArrayList(maxPoints);
		this.color = color;
	}

	public float getMaxY() {
		return maxY;
	}

	public float getMinY() {
		return minY;
	}
	
	public int getColor() {
		return color;
	}

	public void addPoint(float point) {
		if(this.points.size() == this.maxPoints)
			this.points.remove(0);
		this.points.add(point);
		
		this.maxY = this.points.get(0);
		this.minY = this.points.get(0);
		for(int i=1; i<this.points.size(); i++)="" {="" if(this.points.get(i)=""> this.maxY)
				this.maxY = this.points.get(i);
			if(this.points.get(i) < this.minY)
				this.minY = this.points.get(i);
		}
		this.repaint();
	}
	
	public ArrayList getPoints() {
		return this.points;
	}
}