ponedeljek, 23. junij 2014

Tretje oko - pogled v globino s pomočjo sonarja

Sonar (Sound navigation and ranging) je naprava, ki uporablja zvočno valovanje
za določanje položaja oziroma lege objektov in predmetov v vodi.”
Najenostavnejši sonarji uporabljajo oddajnik in sprejemnik (sonda). Naprava meri čas, ki ga ultrazvok potrebuje, da prepotuje razdaljo od sonde do ovire in nazaj. Od ovire se odbije kot odmev (podobno kot v
hribih, če glasno vpijemo). Iz časa in hitrosti zvoka v vodi (1464 m/s) računalnik (sonar) izračuna prepotovano razdaljo.

Sonar so razvili v času prve svetovne vojne, in sicer za iskanje podmornic in ledenih gora. Največji napredek, ki je pozneje pripeljal do tega, da so znanstveniki danes prilagodili občutljive naprave za uporabo raziskovanja oceanov, pa je bil narejen med drugo svetovno vojno.
Sonarji so s časom prišli tudi na ribiške ladje, kjer ribiči z njihovo pomočjo locirajo ribje jate. Z razvojem informacijske tehnologije in elektronike so tudi sonarji postali
komercialno dostopnejši. 
2d sonar
Najenostavnejša oblika sonarja je globinomer, s pomočjo katerega na čolnih ali ladjah določamo globino morja (globina morja se izpiše na majhnem LCD zaslonu).
Pri športnih ribičih je najbolj razširjen 2D sonar. Gre za napravo, ki je sestavljena iz LCD zaslona in zvočne sonde za oddajanje in sprejemanje signala, ki se jo namesti na zrcalo čolna in je usmerjena proti morskemu dnu. Na zaslonu lahko ribič spremlja globino morja ter relief in strukturo morskega dna (na muljastem dnu je »absorpcije« zvočnega signala veliko več kot na kamnitem).
Struktura morskega dna je na zaslonu ponazorjena z odtenki sivine, rastrom ali barvami. 

Ribiče pa pri teh napravah zanimajo ribe – ribje jate. 
Del zvočnega signala se odbije tudi od »predmetov«, ki plavajo med gladino in morskim dnom. To velja tako za ribe, morske klobuke in tudi smeti.
Ribje jate so običajno predstavljene s simbolom ribe ali v obliki loka (fish arches).
Tudi samo območje sondiranja je precej omejeno, saj zvočne sonde pod plovilom običajno pokrivajo stožec s kotom 20-60°.
Izris morskega dna na sonarju 
Z razvojem mikroprocesorjev in odprtokodnih operacijskih sistemov so napredovali tudi »komercialni« sonarji. Pred nekaj leti sta ameriški podjetji Humminbird in Lowrance tržišču ponudili komercialne modele sonarjev, ki poleg klasičnega 2D pregleda omogočajo panoramski pregled morskega dna (side immaging). Omenjena tehnologija je v povezavi z GPS-om in zaslonom visoke
ločljivosti zmagovita kombinacija za iskanje potopljenih objektov ali rib.
Ultrazvočna sonda poleg klasične 2D sonde vsebuje še dve »bočni« sondi. Zvočna sonda lahko deluje na frekvencah 455 kHz ali 800 kHz. Večja kot je frekvenca, večja je ločljivost sondiranja, manjša pa je operativna globina. 
Sam uporabljam frekvenco 800kHz do globine 15 metrov, sicer pa frekvenco 455kHz, saj povsem zadostuje za prepoznavo kamna 50x50 centimetrov na globini 18 metrov.

Kaj je »side imaging«?
Primer interpretacije sonarske slike
Gre za prikaz dna, ki se razteza levo in desno pod plovilom. Na napravi določimo širino zajema in s tem posledično »kakovost« izrisa. Širši kot je zajem, manjša je prepoznavnost objektov.









Zajem in obdelava podatkov
HumView, program za pregled sonarskega zapisa
Omenjene naprave so opremljene tudi s priključki za SD kartice, na katere lahko posnamemo sonarski zapis. Zapis lahko naknadno obdelamo in pregledamo z različnimi programi (komercialnimi ali odprtokodnimi).
Omeniti moram, da sonarski zapis vsebuje tudi GPS zapis. Sonar za vsako točko na SD kartico zapiše tudi koordinato »LAT/LON«. Do koordinat nekega potopljenega objekta lahko pridemo tudi med samim
izrisom sonarske slike (kurzor postavitena izrisani objekt in določite GPS točko (waypoint) ali koordinate objekta prepišete z ekrana).
Izris sonarske slike je bistveno natančnejši,v višji ločljivosti in bolj kontrasten, če ga prenesemo na računalnik. Omogočeno je merjenje velikosti potopljenega objekta, določanje višine objekta glede na senco potopljenega objekta ter izvoz točk v program »Google Earth« za nadaljnjo obdelavo ali arhiv pozicij.


Urejanje in prenos pozicij v Google Earth
Urejanje podatkov v Google Earth programu
Programsko orodje HumView omogoča določanje GPS točk. Seznam teh točk lahko prenesemo v program Google Earth, kjer imamo svojo bazo pozicij. 
Google Earth nam omogoča tudi urejanje točk v svoje podmape, dodajanje dodatnih podatkov in informacij, kot so globina, opis, ikona objekta. 
Programsko orodje omogoča tudi izvoz le določenega segmenta pozicij v poljubne GPS naprave (ročni GPS-i, ploterji, sonarji).
Poleg samega pregleda sonarskega zapisa je mogoče posnetke dna prenesti tudi v program Google Earth. 
Na ta način lahko preko satelitskega posnetka prilepimo posnetke sondiranega dna in si izoblikujemo svoje posnetke.
Na internetu obstaja veliko programov za obdelavo podatkov, med katerimi so tudi takšni za izris 3D oblike dna in objektov (Lake Master, Contour Elite, …), vendar s temi programi nimam izkušenj.


Kako se naprava obnese v praksi?
Potopljene jadrnice v Fiesi
Sam uporabljam sonar Humminbird 998c,ki omogoča bočni zajem slike. V nekaj mesecih sem odkril veliko novih predmetov,za katere sploh nisem vedel, da ležijo na dnu našega morja (cevi, potopljene konstrukcije,
potopljena plovila, splavi, ...).
Uporaba bočnega sonarja je izjemno občutljiva na motnje, kot so kavitacija morja v okolici sonde, valovanje, motnje izvenkrmnega motorja, motnje elise itd.
Za optimalno delovanje naprave je potrebno ločeno napajanje naprave (lastni akumulator), sonda mora biti oddaljena in postavljena na točno določeno pozicijo na zrcalu čolna (sicer sonar določeno stran slike izrisuje nepopolno), ki jo določimo s preizkušanjem, kar pa zna biti neprijetno in naporno. 
Potopljeni splav na Bernardinu
Zanemariti ne smemo vpliva izvenkrmnega motorja (pete motorja). V kolikor se ta nahaja v območju sonde, se na izrisu zaradi mehurčkov pojavljajo bele pike. Optimalna hitrost med bočnim sondiranjem je med 4 in 5 milj na uro (pri večjih hitrostih slika postaja vse manj ostra in neuporabna), vožnja pa naj bo čim bolj ravna in umirjena,
saj smer vožnje vpliva na sam izris morskega dna.




Kaj pa ribe?
Namen sonarja ni le iskanje potopljenih objektov (cevi, potopljena plovila, kamenje,konstrukcije), ampak tudi lociranje rib. V praksi je veliko lažje ribe locirati preko 2D pregleda kot preko bočnega sonarja. Bočni sonar je uporaben za iskanje rib do globine 10-15 metrov. Globlje ni več uporaben, saj se z globino poslabša tudi signal (odboj).
Kljub temu lahko preko bočnega sonarja lociramo potopljeni objekt in prikažemo še sliko nad potopljenim objektom v 2D obliki, kjer so jate rib razvidne iz senc nad objektom (slika spodaj).Bočni pogled (side scan, side imaging) je v uporabi že nekaj let. 
Sonarji in programska oprema se neprestano izpopolnjujejo ter uporabnikom omogočajo učinkovitejše in lažje raziskovanje morskega dna ter konec koncev tudi lažji izlov, kar po eni strani ni pohvalno. Komercialni sonarji (za ribiče) so cenovno dostopni od 1000 EUR naprej za osnovni model, 1500-2000 EUR za model z diagonalo do 10 palcev, nad 2000 EUR pa so
sonarji in tehnologija »touch screen«, kar pa ni primerno za podvodne lovce, saj z mokro roko ne moremo tipkati po ekranu (zato pozor pri izbiri!).
Priporočam sonarje, ki že imajo vgrajen modul za bočno sondiranje. Tako se izognete dodatnim napravam in žicam po plovilu, pa tudi stroškom. Ti sonarji so namenjeni »zabavi«, vendar jih z nekoliko truda lahko uporabimo tudi za kaj drugega kot le za ribolov in iskanja »podvodnih objektov«. 
Za profesionalne stvari pa se cena začne pri 5000 EUR.

petek, 8. november 2013

Čiščenje uplinjača (karburatorja) na izvenkrmnem motorju Yamaha 4ks

Poletje čas za oddih...poletje čas za sonce, plažo in ribolov. Na otok se rad vračam in vedno gledam na morje kje so ribiči, kje kdo lovi ribe.. Kmalu za tem, ko prispem na otok me domačin pokliče na pijačo. Seveda že vem kaj me čaka... Ponavadi je to "štelanje" interneta, servisiranje kakšnega izvenkrmnega motorja, preklop klime iz zimskega v poletni režim itd itd.. Skratka enostavne reči vendar nekaterim komplicirane..
Tokrat me je domačin pričakal s štiri konjskim izvenkrmnim motorjem Yamaha 4..
Kaj je narobe?
"Ne pali.. neće da pali"...
ok.. vzamem motor in ga odpeljem v malo delavnico ki jo imam zraven hiške.

Takoj ko sem dvignil "pokrov" motroja sem videl, da je v velikih količinah prisoten pesek ter sol.
Najprej sem snel cevko goriva ter preveril če po njej prihaja gorivo. Nato sem odvil vijak na šalčki (slika levo), ki je namenjen drenaži "vode". Takoj sem opazil, da je uplinjač "umazan".








Ko sem to prekontroliral sem se lotil uplinjača..
Uplinjač ali po domače "karburator" je na motor pritrjen z dvema vijakoma.
Ta dva vijaka se nahajata pod plastičnim pokrovčkom.
Ko sem vijake odstranil sem odstranil tudi pletenico (zajlo) za dodajanje plina (gas).
Na uplinjaču sem najprej odstranil spodnji del (šalčko) v kateri je bilo kup usedlin (soli in peska).
Z nitro razredčilom in čopičem sem očistil kovinske dele.
Nato sem z izvijačem odvil tudi mešalne igle (šobe) po katerih doteka gorivo v prostor kjer se gorivo zmeša z zrakom in nastane "meglica".





Vse šobe sem spihal "na usta" ter jih očistil v nitro razredčilu. Šobe se nahajajo (slika levo) v tisti sredinjski luknjici ter pod gumijastim pokrovčkom zraven.

Uplinjač sem nato ponovno sestavil in namestil na samo ohišje motorja.
Dvakrat sem "napumpal" gorivo in motor je takoj vžgal.

torek, 11. december 2012

Gotof je...

"Stari znanci in novi obrazi" bi lahko poimenoval igro "Gotof je", original  so leta 1978 razvili na Japonskem pod imenom "Space invaders"..
Cilj igre je zelo enostaven..
V vlogi "osvoboditelja" moramo "eliminirati" nasprotnike, ki nas ogrožajo. Ker je trenutno stanje v Sloveniji "rožnato - posejano s cvetlicami, ki nikoli ne zacvetijo" in ker je kapljico čez rob sprožil g.Kangler sem se pač odločil, da v ta namen priredim igro in jo preimenujem v "gotof je"..
V tej igri nastopajo nekateri "posebneži", ki v veliki meri grenijo, otežujejo, korumpirajo, zavajajo, nategujejo, zlorabljajo slovensko ljudstvo..





Sama igra je narejena za aparate - telefone s operacijskim sistemom Android. Dosegljiva je preko googlove trgovine "http://play.google.com" ali preko direknte povezave "GOTOF JE"..

Novica o igri je bila objavljena tudi na slovenskem radiju 1  , spletnem portalu zurnal24.si  , spletni strani Regional Obala ter lokalnem časopisu "Mandrač" za kar se jim seveda zahvaljujem...

Še nekaj zaslonskih posnetkov prirejene igre "Gotof je"...

četrtek, 29. november 2012

Oceanographic buoy Vida on mobile devices

Due to the broad interest of many users of Java ME applications for displaying the data of Oceanographic buoy Vida (OceanBuoyPiran) and tides (TideKP) and also in text format (WAP), which were released in 2007, I have recently prepared an application for mobile devices running Android.
 Application MBP can be obtained on the Android Market by searching the keywords "mbp" or "mbp vida" or directly on this link: https://play.google.com/store/apps/details?id=org.mbp&feature=search_result#?t=W251bGwsMSwyLDEsIm9yZy5tYnAiXQ
Special thanks also go to Mr. Mavricij Bizjak and the colleagues from Marine Biology Station Piran of the National Institute of Biology for the comments that have been welcomed in the development of the application.

četrtek, 8. november 2012

Android Custom UI: Horizontal Number Spinner

Hello everyone!
I would like to show how to design a custom UI element for Android the "Horizontal Number Spinner".

The final result looks like this:
Background images I have used:

Gray background
Glass background (9-patch)
Arrow

 1.) Create a custom component which extends View and implement GestureDetector.OnGestureListener interface

package si.in2.ui;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;

public class HorizontalNumberSpinner extends View implements
  GestureDetector.OnGestureListener {
 
 public static final int ARROW_BORDER = 10;
 public static final int BOTTOM_BORDER = 10;
 public static final int MARKER_DIFFERENCE = 8;
 public static final int TEXT_BORDER = 10;
 public static final int TEXT_HEIGHT = 10;
 public static final int TOP_BORDER = 10;
 private Bitmap arrowBitmap;
 
 private Paint arrowPaint;
 private Paint fontPaint;
 private Paint markerPaint;
 private Paint backgroundPaint;
 private Paint smallMarkerPaint;
 
 private GestureDetector gestureDetector;
 private OnChangeListener onChangeListener;
 private Scroller scroller;
 
 private Drawable backgroundBitmap;
 private int arrowOffset;
 private int bottomBorder;
 private int currentX;
 private int firstMarkerIndex;
 private String labelFormat;
 
 private int lastMarkerIndex;
 private int markerDifference;
 
 private int markerSpacing;
 private int maxScrollOffset;
 private double maxValue;
 private int minScrollOffset;
 private double minValue;
 
 private int scaleDivisions;
 private double scaleStep;
 private int scrollOffset;
 
 
 private double step;
 private int stepInPixels;
 private int textBorder;
 private int textHeight;
 private int topBorder;
 
 //interface for event handling
 public  interface OnChangeListener {
  public  void onValueChanged(String pString, double pDouble);
 }
 
 public HorizontalNumberSpinner(Context ctx) {
  super(ctx);
  init(ctx);
  
 }

 public HorizontalNumberSpinner(Context ctx,
   AttributeSet pAttributeSet) {
  super(ctx, pAttributeSet);
  init(ctx);
  
 }

 public HorizontalNumberSpinner(Context ctx,
   AttributeSet pAttrSet, int p) {
  super(ctx, pAttrSet, p);
  init(ctx);
  
 }

 
 private void getArrowOffset() {
  this.arrowOffset = (getMeasuredWidth() - this.arrowBitmap.getWidth() - (int) TypedValue
    .applyDimension(1, 10.0F, getResources().getDisplayMetrics()));
 }

 private int getMaxScrollOffset() {
  return (int) Math.ceil((this.maxValue - this.minValue)
    * this.scaleDivisions * this.markerSpacing / this.scaleStep);
 }

 private int getMinScrollOffset() {
  return (int) Math.ceil(this.minValue * this.scaleDivisions
    * this.markerSpacing / this.scaleStep);
 }

 /**
  * init 
  * @param ctx
  */
 private void init(Context ctx) {
  //
  scroller = new Scroller(ctx); //This class encapsulates scrolling
  gestureDetector = new GestureDetector(this);  //Detects various gestures and events using the supplied MotionEvents
  
  topBorder =  (int)TypedValue.applyDimension(1, 10, getResources().getDisplayMetrics());
  bottomBorder = (int) TypedValue.applyDimension(1, 10, getResources().getDisplayMetrics());
  markerDifference = (int) TypedValue.applyDimension(1, 8,getResources().getDisplayMetrics());
  textHeight = (int) TypedValue.applyDimension(1, 10, getResources().getDisplayMetrics());
  textBorder = (int) TypedValue.applyDimension(1, 10, getResources().getDisplayMetrics());
  
  //default values
  minValue = -200000;
  maxValue = 1000000;
  scaleStep = 1000;
  step = 100;
  scaleDivisions = 5; 
  arrowOffset = 200;
  markerSpacing = 16; 
  labelFormat = "%10.3f";
  stepInPixels = (int) ((step *  scaleDivisions *  markerSpacing) / scaleStep);
  maxScrollOffset = getMaxScrollOffset();
  minScrollOffset = getMinScrollOffset();
  
  //Log.i("SPINER", "maxScrollOffset: " + maxScrollOffset);
  //Log.i("SPINER", "minScrollOffset: " + minScrollOffset);
  
  //Here we set the background image
  this.backgroundBitmap = getResources().getDrawable(R.drawable.sp_bg_3);
  
  //copy the arrow bitmap into variable arrowBitmap 
  this.arrowBitmap = BitmapFactory.decodeResource(
    getContext().getResources(), R.drawable.spinner_pointer).copy(
    Bitmap.Config.ARGB_8888, true);
  
  
  //get the arrow offset.
  getArrowOffset();
  
  //Setup paint objects for 
  initPaintObjects();

 }

 private void initPaintObjects() 
 {
  markerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  markerPaint.setColor(Color.WHITE);
  markerPaint.setStrokeWidth(3);
  markerPaint.setStyle(Paint.Style.FILL);
  
  smallMarkerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  smallMarkerPaint.setColor(Color.WHITE);
  smallMarkerPaint.setStrokeWidth(1.0F);
  smallMarkerPaint.setStyle(Paint.Style.FILL);
  smallMarkerPaint.setTextAlign(Paint.Align.CENTER);
  smallMarkerPaint.setTextSize(10);
  
  fontPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  fontPaint.setStyle(Paint.Style.STROKE);
  fontPaint.setColor(Color.WHITE);
  fontPaint.setStrokeWidth(1);
  fontPaint.setAntiAlias(true);
  fontPaint.setTextSize(12);
  fontPaint.setTextAlign(Paint.Align.CENTER);  
  
  backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

  arrowPaint = new Paint();
  
  }

 
 private int measureHeigth(int height) {
  View.MeasureSpec.getMode(height);
  return View.MeasureSpec.getSize(height);
 }

 private int measureWidth(int width) {
  View.MeasureSpec.getMode(width);
  return View.MeasureSpec.getSize(width);
 }

 private void notifyListener() {

  if (this.onChangeListener != null) {
   double d = getValue();
      //fire event 
   onChangeListener.onValueChanged(String.format(this.getFormat(), d), d);
  }
 }

 private void scrollToSelectableValue() { 
  
  int val = Math.round(this.scrollOffset / this.stepInPixels) * this.stepInPixels;
  scroller.startScroll(scrollOffset, 0, val - this.scrollOffset, 0);
  Log.i("SPINER", "scrollToSelectableValue val="+ val +"," + scrollOffset);
  post(new Runnable() {
   public void run() {
    if (!scroller.isFinished()) {
     postDelayed(this, 20);
     invalidate(); //correct the value on the spinner     
     return; 
    }
   }
  });
 }

 

 @Override
 public boolean onDown(MotionEvent paramMotionEvent) {
  Log.i("SPINER", "onDown: ");
  
  scroller.abortAnimation();
  scrollToSelectableValue();
  
  return true;
 }

 @Override
 public void onDraw(Canvas pCanvas) {
  
  //check if the scroller is not finished
  if (scroller.isFinished() == false) 
  {
   scroller.computeScrollOffset();
   scrollOffset = scroller.getCurrX(); //get the scroller currnet x value
   notifyListener(); 
  }

  this.backgroundBitmap.setBounds(0, 0, getMeasuredWidth(),getMeasuredHeight());
  this.backgroundBitmap.draw(pCanvas);
  
  //calcukate curentx, first and last marker index...
  this.firstMarkerIndex = ((-this.scrollOffset - this.arrowOffset) / this.markerSpacing);
  this.firstMarkerIndex -= (this.scaleDivisions - this.firstMarkerIndex) % this.scaleDivisions;
  this.currentX = (this.arrowOffset + this.markerSpacing * this.firstMarkerIndex + this.scrollOffset);
  this.lastMarkerIndex = ((-this.scrollOffset - this.arrowOffset + getMeasuredWidth()) / this.markerSpacing);
  this.lastMarkerIndex += this.scaleDivisions - this.lastMarkerIndex % this.scaleDivisions;

  int chk = firstMarkerIndex;
  while(true) {
   if (chk > lastMarkerIndex) {
    pCanvas.drawBitmap(arrowBitmap,
      arrowOffset - (arrowBitmap.getWidth() / 2), 0.0F,
      backgroundPaint);
    return;
   }

   if (chk % scaleDivisions == 0) {
    double d =  (chk / scaleDivisions) * scaleStep;
    
    //draw the leading line
    pCanvas.drawLine(currentX, topBorder, currentX,
      getMeasuredHeight() - bottomBorder - textHeight
        - textBorder, markerPaint);
   
     //draw text below the leading line
    pCanvas.drawText(String.format(labelFormat, d), currentX, getMeasuredHeight() - bottomBorder, fontPaint);
    
     currentX +=  markerSpacing;
    
   } else {
    //lines between leading lines
    pCanvas.drawLine(currentX, topBorder, currentX,
      getMeasuredHeight() - bottomBorder - textHeight
        - textBorder - markerDifference,
      smallMarkerPaint);
    currentX +=  markerSpacing;
   }
   
   chk++;
  } 

 }

 @Override
 public boolean onFling(MotionEvent pMotionEvent1,
   MotionEvent pMotionEvent2, float x, float y) {
  Log.i("SPINER", "onFling: " + x + ", " + y );
  scroller.fling(this.scrollOffset, 0, (int) x,
    (int) y, -this.maxScrollOffset,
    -this.minScrollOffset, 0, 0);
  //start a thread and after 20msec verify if scroller is finished then repaint object..
  post(new Runnable() {
   public void run() {
    if (!scroller.isFinished()) {
     invalidate();
     postDelayed(this, 20);
    } else {
     scrollToSelectableValue();

    }
   }
  });
  return false;
 }

 
 protected void onMeasure(int w, int h) {
  Log.i("SPINER", "onMeasure: " + w + ", " + h );
  int i = measureHeigth(w);
  int j = measureWidth(h);
  getArrowOffset();
  setMeasuredDimension(j, i);
 }

 public boolean onScroll(MotionEvent paramMotionEvent1,
   MotionEvent paramMotionEvent2, float x, float y) {
  Log.i("SPINER", "onScroll: " + x + ", " + y );
  this.scrollOffset = (int) (this.scrollOffset - x);

  if (this.scrollOffset < -this.maxScrollOffset) {
   this.scrollOffset = -this.maxScrollOffset;
  }
  invalidate();
  notifyListener();
  return true;

 }

 @Override
 public void onShowPress(MotionEvent pMotionEvent) {
  return;
 }

 @Override
 public boolean onSingleTapUp(MotionEvent pMotionEvent) {
  return false;
 }

 @Override
 public boolean onTouchEvent(MotionEvent pMotionEvent) {
  if ((pMotionEvent.getAction() == MotionEvent.ACTION_UP) && (this.scroller.isFinished()))
   scrollToSelectableValue();
  this.gestureDetector.onTouchEvent(pMotionEvent);
  return true;
 }

 public void setBackgroundBitmap(Drawable pDrawable) {
  this.backgroundBitmap = pDrawable;
 }

 public void setFormat(String format) {
  this.labelFormat = format;
 }

 public void setMaxValue(double mValue) {
  this.maxValue = mValue;
  this.maxScrollOffset = getMaxScrollOffset();
 }

 public void setMinValue(double minValue) {
  this.minValue = minValue;
  this.minScrollOffset = getMinScrollOffset();
  this.maxScrollOffset = getMaxScrollOffset();
 }

 public void setOnChangeListener(OnChangeListener pOnChangeListener) {
  this.onChangeListener = pOnChangeListener;
 }

 public void setScaleDivisions(int i) {
  scaleDivisions = i;
  minScrollOffset = getMinScrollOffset();
  maxScrollOffset = getMaxScrollOffset();
 }

 public void setScaleStep(double d) {
  scaleStep = d;
  step = d /  (2 * scaleDivisions);
  stepInPixels = (int) ((step * scaleDivisions *  markerSpacing) / d);
  minScrollOffset = getMinScrollOffset();
  maxScrollOffset = getMaxScrollOffset();
 }

 public void setValue(double d) {
  scrollOffset = (int) ((-d *  (scaleDivisions * markerSpacing)) / scaleStep);
  invalidate();
  scrollToSelectableValue();
 }
 
 public String getFormat() {
  return this.labelFormat;
 }

 public double getMaxValue() {
  return this.maxValue;
 }

 public double getMinValue() {
  return this.minValue;
 }

 public int getScaleDivisions() {
  return this.scaleDivisions;
 }

 public double getScaleStep() {
  return this.scaleStep;
 }

 public double getStep() {
  return this.step;
 }

 public double getValue() {
  return ( (-scrollOffset) * scaleStep)
    /  (scaleDivisions * markerSpacing);
 }

 @Override
 public void onLongPress(MotionEvent arg0) {
  // TODO Auto-generated method stub
 return;
  
 }
}

2.Android Layout file




    
    
    

    
    



3. HorizontalSpinnerActivity file
In the "main" activity we initialize our HorizontalSpinner with some values:

package si.in2.ui;

import android.app.Activity;
import android.os.Bundle;

import android.widget.LinearLayout;
import android.widget.TextView;

public class HorizontalSpinnerActivity extends Activity  {
    /** Called when the activity is first created. */
 
 HorizontalNumberSpinner spinner;
 LinearLayout layout;
 TextView text;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        text = (TextView)  findViewById(R.id.textView1);
       
        spinner = (HorizontalNumberSpinner)findViewById(R.id.spiner);
        spinner.setMaxValue(10000.0); // set the maximum value
        spinner.setMinValue(-10000.0);// set the minimum value
        spinner.setScaleStep(50.0); // step for 50 values
        spinner.setScaleDivisions(5); // set scale division between numbers
        spinner.setFormat("%.000f"); // set the format displayed below leading line
        spinner.setValue(5000.0); // start at value
        
        //event handling
        spinner.setOnChangeListener(new HorizontalNumberSpinner.OnChangeListener()
        {
          public void onValueChanged(String paramString, double paramDouble)
          {
            text.setText("Value: " + paramDouble);
          }
        });
        
      
    }

 
}


ponedeljek, 9. april 2012

Reading applet parameters in Oracle Forms

Reading oracle forms forum I have found out this question:

"Dears
We are working with system with 2 application server IPs and one Real IP (which is able to be accessed through internet)
I would like to know the URL is the user using in order to use his IP to open something else from the system.
Chcking the host name not usesful because if user connect from home using Real IP it return the AS host name which real IP redirct to it.
I need to know how to get the URL of the session from the form or to know whether the user connected using the Real IP or not.
(Oracle forms 10gR2)
"

The simple solution is to write down a java bean component (pjc) for Oracle Forms.
As you know the PJC must extends VBean object and also pass a IHandler instance to it.
From IHandler you can read Applet parameters like Applet.getDocumentBase. The getDocumentBase() method of Applet class returns the URL of the document in which this applet is embedded.

Below is the PJC which can read some user specific datas.
CLIENT_IP return the client ip number,
CLIENT_NAME return the name of local (client) computer
CLIENT_OS return the opetating system name
CLIENT_USER return the user name which is logged to the system
HOST_URL return the URL of the document in which the Oracle Forms application is embedded (run).

package in2.client;
 
import java.io.PrintStream;
import java.net.InetAddress;
import oracle.forms.handler.IHandler;
import oracle.forms.properties.ID;
import oracle.forms.ui.VBean;
 
public class ClientInfo extends VBean
{
  private static final ID CLIENT_IP = ID.registerProperty("CLIENT_IP");
  private static final ID CLIENT_NAME = ID.registerProperty("CLIENT_NAME");
  private static final ID CLIENT_OS = ID.registerProperty("CLIENT_OS");
  private static final ID CLIENT_USER = ID.registerProperty("CLIENT_USER");
  private static final ID HOST_URL = ID.registerProperty("HOST_URL");
  
  private IHandler mHandler;
  private String hostAddress = "";
  private String hostName = "";
  private String hostOs = "";
  private String hostUser = "";
  private String hostURL = "";
 
  public ClientInfo()
  {
    try
    {
      
      System.out.println("+----------------------+");
      System.out.println("+  ClientInfro PJC     +");
      System.out.println("+  Peter Valencic      +");
      System.out.println("+----------------------+\n"); 
 
      this.hostAddress = InetAddress.getLocalHost().getHostAddress();
      this.hostName    = InetAddress.getLocalHost().getHostName();
      this.hostOs      = System.getProperty("os.name");
      this.hostUser    = System.getProperty("user.name");
 
      System.out.println("Host   adress: " + this.hostAddress);
      System.out.println("Host     name: " + this.hostAddress);
      System.out.println("Host op. syst: " + this.hostOs);
      System.out.println("Host username: " + this.hostUser);
      System.out.println("+----------------------+\n");
    }
    catch (Exception e)
    {
      System.out.println("Init error: " + e.toString());
    }
  }
 
  public void init(IHandler handler)
  {
    super.init(handler);
    this.mHandler = handler;
    
  }
 
  public Object getProperty(ID pid)
  {
    if (pid == CLIENT_IP) {
      System.out.println("Client IP: " + this.hostAddress);
 
      return "" + this.hostAddress;
    }
 
    if (pid == CLIENT_NAME) {
      System.out.println("Client Name: " + this.hostName);
 
      return "" + this.hostName;
    }
 
    if (pid == CLIENT_OS) {
      System.out.println("Client os: " + this.hostOs);
 
      return "" + this.hostOs;
    }
 
    if (pid == CLIENT_USER) {
      System.out.println("Clientuser: " + this.hostUser);
 
      return "" + this.hostUser;
    }
    
    if (pid == HOST_URL) {
      hostURL = this.mHandler.getApplet().getDocumentBase();
      System.out.println("Host Url: " + this.hostURL);
      return "" + this.hostURL;
    }
 
    return super.getProperty(pid);
  }
}





torek, 6. marec 2012

Replacing impeller on yamaha F80 outborad engine

Impeller is a critical part in marine engines. It is the main component of the water pump and is used to pump fresh and cold water to the engine cooling system. That is why it is so important to check its condition very often. If your impeller is broken you may permanently damage your engine because of overheating your boat engine. There is an unwritten rule that says that you should replace your impeller on your boat engine every two years even if it's in good condition.


  1. First we unscrew the four screws that are visible on the side of the engines heel.
  2. Next we have to remove the rubber plugin on the narrow end of the engine and we unscrew the zinc protector that is located above the propeler
  3. We remove the zinc protector and we unscrew the number 12 bolt from the bottom side
  4. Next we have to remove the engine heel and remove it from the boat engine
  5. We have to buy original Yamaha impeller
  6. Next we remove the four bolts from the water pump housing
  7. We use a hammer to gently tap on the water pump housing to remove it (Warning: Handle with care!!)
  8. What we see in front of us is a boat engine shaft with a small rubber propeler attached to it => that is impeller
  9. We take the impeller of the shaft (if it won't go of we use a knife on it)
  10. We put our newly bought original impeller on the shaft and put a small metal plugin inside a little hole in the shaft to hold the impeller in place
  11. We put the water pump housing back in place and we have to wrap the impeller blades to fit inside the water pump housing
  12. The blades must be slightly bent inside the water pump housing
  13. We than screw everything back in its place, and before you put your screws in you might consider lubricating them with a water resistant lubricant
You can see/read a complete tutorial in my previous blog (slovene version)  following this link: http://peterv6i.blogspot.com/2012/01/zamenjava-impelerja-na-motorju-yamaha.html