[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; } }