Friday, 11 May 2018

GPS Motorcycle Logger

Objective:
Build a standalone GPS based speed logger and throw in lean angle too.. because.. MOTORBIKE!
Achieved. 
WIP, write data to sdcard as a KML, 90%

Objective2: WIP, hack into the CANBus via OBD to extract info like RPM, Gear, etc directly from bike ECU, initial attempts quite successful. WIP 50%




UPDATE: CANBUS ARDUINO feed added, the data ported to my gopro



The build hardware:

1. Arduino UNO. - LINK- Price: AUD $11.
(but if you can spring for it and have space, go Mega2560 to bypass the irritation of setting up software serial. if not, DO NOT USE standard software serial, its sucks donkey bum..)

2. USBasp programmer  -LINK-  Price AUD $4.
If you use an UNO or Nano, with only one serial port, you will have the ovious issue of it not connecting to the pc via RX/TX because the GPS or Screen will use that, so you need to write directly via an AVR programmer.

3. UBlox6 GPS module -LINK- Price AUD $8
if you spend a few $ more you can buy them with header presoldered. By default these will spit out UBX protocol,and the arduino library available for that woudl not compile, so download the free uCentre app for UBlox, and follow the manual instructions for setting it to NMEA output, you can disable all GPS sentence except for GGA and RMC, set the update frequency to 10hz, and baud rate to 115200, this will allow you to take valid reading in 20ms or less, critical as a delay here messes with accelerometer readings.

4. 32PTU Display -LINK- Price AUD $61

5. GyroScope -LINK- Price  AUD $2 - $10
(Any accelerometer will do, I would have preferred a 9DOF unit but jaycar don't sell them anymore, the unit i used is analog with no jitter control, so up to you,just change pins in the code to suit)

Build notes:
As all Arduino based projects, the libraries at your disposal will be poorly documented, the hardware will likely be upscaled from 3.3 or actually still at 3.3v, be carefull, upscaled analog means you have to throw readings above 530 (1~1024) out the window.. allocate in your code if you go analog.

I have my own 3dprinters, and plan to print in ABS once I have finalised the design in Rhino, but you can buy several units from jaycar that will house it for under $5 each.

Code Notes:
I scrapped the screen reset pin from my design, as its not required, I setup the screen and the arduino to boot to my splash screen, wait the 5 seconds it takes for comms to go live then dump intothe main display.

In the Visi side of thing the various buttons are coded into the Visi builder so that the AR side is not bogged down reading button push status update, increase in speed is significant. The 4dsystems visi and 4Ds language is quite easy to learn if you are already au faix with C#, but has a few kinks to learn.

Visi Screens:



Arduino Side Code:
//hardware
//  MEGA 2560
//  UBX6 gps on serial2, configured via Ucentre for:
//    NMEA @ 115200, 10hz and ONLY rmc and gga sentences
//  uLCD 220rd on serial1, configured in 4d WS @9600
//  Accelerometer MMA7361, Y axis on pin A9

#include <genieArduino.h>
#include <TinyGPS++.h>
#include <AltSoftSerial.h>
#define RESETLINE 5
#define GPSPOWER 2


TinyGPSPlus gps;
Genie genie;
// Board              TX       RX     PWM Unusable
// -----          --------  -------   ------------
// Arduino Uno        9         8         10

AltSoftSerial Display;
unsigned long writeKMLold = 0, writeKMLCurrent = 0;
const int jittercorrect = 20; //poll period for bump averaging
float gpsLatitude, gpsLongitude;
double Speed, speedMax;
int AccY = A0, leanMax = 0, leanCurrent = 0, gpsLock = 0;
int gpsDirection = 0, gpsAltitude = 0;
int currentForm = 0;
bool header = false, record = true;

void setup() {

  Display.begin(9600); genie.Begin(Display);
  //pinMode(RESETLINE, OUTPUT);
  //digitalWrite(RESETLINE, 1); delay(100);
  //digitalWrite(RESETLINE, 0); delay (35000);
  //power the gps
  pinMode(GPSPOWER, OUTPUT);
  digitalWrite(GPSPOWER, HIGH);
  genie.AttachEventHandler(checkScreen);
  Serial.begin(115200);
  delay(3500);
  SetForm(0);
}

void loop() {
  genie.DoEvents();
  gpsUpdate();
  genie.DoEvents();
  readAcc();
  genie.DoEvents();
  updateMax();
}

void updateMax() {
  int LeanMath = leanCurrent;
  if (LeanMath < 0)LeanMath *= -1;
  if (LeanMath > leanMax)leanMax = LeanMath;
  if (Speed > speedMax)speedMax = Speed;
  switch (currentForm) {
    case 0:
      genie.WriteObject(GENIE_OBJ_LED_DIGITS, 3, (int)speedMax);
      genie.WriteObject(GENIE_OBJ_LED_DIGITS, 4, leanMax);
      break;
    case 1:
      genie.WriteObject(GENIE_OBJ_LED_DIGITS, 6, (int)speedMax);
      break;
    case 2:
      genie.WriteObject(GENIE_OBJ_LED_DIGITS, 2, (int)leanMax);
      break;
  }
}

void gpsUpdate() {
  unsigned long start = millis();
  do
  {
    while (Serial.available())gps.encode(Serial.read());
  }
  while (millis() - start < 25); //grab 20ms of gps info
  Speed = gps.speed.kmph(); if (Speed > 2)Speed = 0;
  gpsDirection = (int)gps.course.deg();
  gpsLatitude = gps.location.lat(); gpsLongitude = gps.location.lng();
  gpsLock = gps.satellites.value(); gpsAltitude = (int)gps.altitude.meters();

  if (gpsLock > 2) {

    switch (currentForm) {
      case 0:
        genie.WriteObject(GENIE_OBJ_COOL_GAUGE, 0, (int)Speed);
        genie.WriteObject(GENIE_OBJ_GAUGE, 5, gpsLock);
        break;
      case 3:
        genie.WriteStr(0, String(gpsLatitude, 6));
        genie.WriteStr(1, String(gpsLongitude, 6));
        genie.WriteStr(2, gpsAltitude);
        genie.WriteObject(GENIE_OBJ_GAUGE, 4, gpsLock);
        break;
    }
  }
}


void readAcc() {   // RAW: L = 200 C = 385 R = 545
  int jitter = 0;
  for (int a = 0; a < 5; a++) {
    jitter += map(analogRead(AccY), 200, 545, -90, 90);
    delay(jittercorrect);
  }
  leanCurrent = (int)jitter / 5; //poll a few times and average
  if (leanCurrent < -90)leanCurrent = -90; if (leanCurrent > 90)leanCurrent = 90;

  switch (currentForm) {
    case 0:
      if (leanCurrent < 0) {
        int lean = leanCurrent *= -1;
        genie.WriteObject(GENIE_OBJ_GAUGE, 2, lean);
        genie.WriteObject(GENIE_OBJ_GAUGE, 3, 0);
      } else {
        genie.WriteObject(GENIE_OBJ_GAUGE, 2, 0);
        genie.WriteObject(GENIE_OBJ_GAUGE, 3, leanCurrent);
      }
      break;
    case 2:
      if (leanCurrent < 0) {
        int lean = leanCurrent *= -1;
        genie.WriteObject(GENIE_OBJ_GAUGE, 0, lean);
        genie.WriteObject(GENIE_OBJ_GAUGE, 1, 0);
        genie.WriteObject(GENIE_OBJ_LED_DIGITS, 0, lean);
        genie.WriteObject(GENIE_OBJ_LED_DIGITS, 1, 0);
      } else {
        genie.WriteObject(GENIE_OBJ_GAUGE, 0, 0);
        genie.WriteObject(GENIE_OBJ_GAUGE, 1, leanCurrent);
        genie.WriteObject(GENIE_OBJ_LED_DIGITS, 0, 0);
        genie.WriteObject(GENIE_OBJ_LED_DIGITS, 1, leanCurrent);
      }
      break;
  }
}

void checkScreen() {
  genieFrame Event;
  genie.DequeueEvent(&Event);
  if (Event.reportObject.cmd == GENIE_REPORT_EVENT) {
    if (Event.reportObject.object == GENIE_OBJ_FORM) {
      currentForm = (int)Event.reportObject.index;
    }
    if (Event.reportObject.object == GENIE_OBJ_WINBUTTON) {
      int pressed = (int)Event.reportObject.index;
      switch (pressed) {
        case 25: record = true; break;
        case 26: record = false; break;
        case 27: leanMax = 0; break;
        case 28: speedMax = 0; break;
      }
    }
  }
}

void SetForm(int form) {
  currentForm = form;
  genie.WriteObject(GENIE_OBJ_FORM, form, 0);
}

void StartKMLWrite() {
  if (header == false) {
    if (gpsLock > 4)
      //String filename1(gps.time.value());
      //String filename = filename1 + ".kml";
      //filename.toCharArray(fileNameChar, 13);
      //open the file on SD
      //write the header info
      writeHeaderKML("Teststring.kml"); //replace with filenamechar above
    header = true;
  }
}

void UpdateKML(long interval) {
  writeKMLCurrent = millis();
  if (writeKMLCurrent - writeKMLold >= interval) {
    writeKMLold = writeKMLCurrent;
    writeRecordKML(Speed, leanCurrent, gpsAltitude, gpsLatitude, gpsLongitude);
  }
}

void writeHeaderKML(String Filename) {
  //myFile = SD.open(fileNameChar, FILE_WRITE);
  Serial.println  (F("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"));
  Serial.println  (F("<kml xmlns=\"http://earth.google.com/kml/2.1\">"));
  Serial.println  (F("<Document>"));
  Serial.print    (F("<name>")); Serial.print(Filename); Serial.println(F("</name>"));
  Serial.println  (F("<visibility>1</visibility>"));
  Serial.print    (F("<description>")); Serial.print(F("CeeBee Datalogger")); Serial.println(F("</description>"));
  //myFile.close();
}
void writeFooterKML() {
  //myFile = SD.open(fileNameChar, FILE_WRITE);
  Serial.println  (F("</Document>"));
  Serial.println  (F("</kml>"));
  //myFile.close();
}
void writeRecordKML(int Speed, int LeanAngle, int Altitude, float Latitude, float Longitude) {
  //myFile = SD.open(fileNameChar, FILE_WRITE);
  Serial.println  (F("  <Placemark>"));
  Serial.print    (F("    <name>")); Serial.print(Speed); Serial.println(F("</name>"));
  Serial.println  (F("    <ExtendedData>"));
  Serial.println  (F("      <Data name=\"Lean Angle Degrees\">"));
  Serial.print    (F("        <value>")); Serial.print(LeanAngle); Serial.println(F("</value>"));
  Serial.println  (F("      </Data>"));
  Serial.println  (F("      <Data name=\"Speed KMPH\">"));
  Serial.print    (F("        <value>")); Serial.print(Speed); Serial.println(F("</value>"));
  Serial.println  (F("      </Data>"));
  Serial.println  (F("    </ExtendedData>"));
  Serial.println  (F("    <Point>"));
  Serial.print    (F("      <coordinates>"));
  Serial.print    (Longitude, 6); Serial.print(F(","));
  Serial.print    (Latitude, 6); Serial.print(F(","));
  Serial.print    (Altitude, 6);
  Serial.println  (F("</coordinates>"));
  Serial.println  (F("    </Point>"));
  Serial.println  (F("  </Placemark>"));
  //myFile.close();
}
Visi Side Code:
platform "uLCD-220RD"


// generated 12/05/2018 3:23:00 PM

#MODE FLASHBANK_0

#inherit "4DGL_16bitColours.fnc"

#inherit "VisualConst.inc"


#inherit "visicodedConst.inc"

#inherit "CLPrintStrings.inc"

#constant IPDatasize 22

#CONST
    CMDLenMAX   80
    seroutX     $serout
    serinX      $serin
#END

#CONST
    ColorBGimage     0x0020
    ACK         0x06
    NAK         0x15
    ReadCmd     0x80
    WriteCmd    0x00
//  IPD_TYPE    0 // offsets are doubled as FLASH is byte addressable
    Ofs_IPD_P1      2
    Ofs_IPD_P2      4
    Ofs_IPD_P3      6
    Ofs_IPD_P4      8
    Ofs_IPD_P5      10
    Ofs_IPD_P6      12
    Ofs_IPD_P7      14
    Ofs_IPD_DOWN    16
    Ofs_IPD_RELEASE 18
    Ofs_IPD_OBJVIDX 20
// object indexes
    tDipSwitch      0
    tKnob           1
    tRockerSwitch   2
    tRotarySwitch   3
    tGSlider        4
    tTrackbar       5
    tWinButton      6
    tAngularmeter   7   // need to implement use of this, inputs must be ordered first
    tCoolgauge      8
    tCustomdigits   9
    tForm           10
    tGauge          11
    tImage          12
    tKeyboard       13  // this is a special input, does not need to be at front
    tLed            14
    tLeddigits      15
    tMeter          16
    tStrings        17  // also need output strings code
//    tStringUNI      0x3f | 0x40
//    tStringANSII    0x3f
    tThermometer    18
    tUserled        19
    tVideo          20
    tStaticText     21
// Remove, check for non visual objects instead
//    MaxVisObjects   21  // objects that have a visual component
    tSounds         22
    tTimer          23
    tSpectrum       24
    tScope          25
    tTank           26
    tUserImages     27
    tPinOutput      28
    tPinInput       29
    t4Dbutton       30    // more inputs
    tAniButton      31
    tColorPicker    32
    tUserButton     33
    tMagicObject    34
    MaxTotObjects   33 // objects in objects array
//    OT_DISPLAY      22
    OT_REPORT       100
    OT_SETCONST     101
    OT_SETANOTHER   102
    OT_ACTIVATE     103
    OT_NEXTFRAME    104
    OT_PREVFRAME    105
    OT_NEXTSTRING   106
    OT_PREVSTRING   107
    OT_MAGIC        108
// other OT_s Form activate,
// Indexes into LedDigits and CustomDigits arrays
    Ofs_Digits_Left           0
    Ofs_Digits_Digits         2
    Ofs_Digits_MinDigits      4
    Ofs_Digits_Widthdigit     6
    Ofs_Digits_LeadingBlanks  8
// indexes to Strings arrays
    Ofs_String_StartH        0
    Ofs_String_StartL        2
    Ofs_String_Size          4
    Ofs_String_x1            6
    Ofs_String_y1            8
    Ofs_String_x2            10
    Ofs_String_y2            12
    Ofs_String_FGColor       14
    Ofs_String_BGColor       16
    Ofs_String_FontAttribs   18
    Ofs_String_Transparent   20 // bit transparent should 'refresh' background, otherwise rectangle out
    Ofs_String_Ansi          22 // bit defines write/draw routine
    Ofs_String_Form          24 // form this string can be seen in
// Command codes
    READ_OBJ        0
    WRITE_OBJ       1
    WRITE_STR       2
    WRITE_STRU      3
    WRITE_CONTRAST  4
    REPORT_OBJ      5
    REPORT_EVENT    7
    WRITE_MAGIC_BYTES 8
    WRITE_MAGIC_DBYTES  9
    REPORT_MAGIC_EVENT_BYTES 10
    REPORT_MAGIC_EVENT_DBYTES 11
// End P1.inc
    nObjects        10
    nInputs         0
    nAniTimers      0
    nStrings        6
#END

#DATA
    word FormStartIndex 0
    word FormEndIndex 10
    word InputControls 0
    word InputData 0
    word iStrings0  Strings0StartH, Strings0StartL, Strings0Size, 112, 193, 162, 206, YELLOW, BLACK, 0, 1, 1, 0
    word iStrings1  Strings1StartH, Strings1StartL, Strings1Size, 68, 132, 86, 144, YELLOW, BLACK, 0, 1, 1, 0
    word iStrings2  Strings2StartH, Strings2StartL, Strings2Size, 65, 148, 86, 159, YELLOW, BLACK, 0, 1, 1, 0
    word iStrings3  Strings3StartH, Strings3StartL, Strings3Size, 54, 162, 86, 172, YELLOW, BLACK, 0, 1, 1, 0
    word iStrings4  Strings4StartH, Strings4StartL, Strings4Size, 61, 176, 86, 187, YELLOW, BLACK, 0, 1, 1, 0
    word iStrings5  Strings5StartH, Strings5StartL, Strings5Size, 72, 10, 144, 22, YELLOW, BLACK, 0, 1, 1, 0
    word oDipSwitchs 0
    word oKnobs 0
    word oRockerSwitchs 0
    word oRotarySwitchs 0
    word oGSliders 0
    word oTrackbars 0
    word oWinButtons 0
    word oAngularmeters 1, iAngularmeter0
    word oCoolgauges 1, iCoolgauge0
    word oCustomdigitss 0
    word oForms 1, -1
    word oGauges 0
    word oImages 0
    word oKeyboards 0
    word oLeds 0
    word oLeddigitss 1, iLeddigits0
    word oMeters 0
    word oStringss 6, iStrings0, iStrings1, iStrings2, iStrings3, iStrings4, iStrings5
    word oThermometers 0
    word oUserleds 1, iUserled0
    word oVideos 0
    word oStaticTexts 5, iStatictext0, iStatictext1, iStatictext2, iStatictext3, iStatictext4
    word oSpectrums 0
    word oScopes 0
    word oTanks 0
    word oUserImagess 0
    word oPinInputs 0
    word o4Dbuttons 0
    word oAniButtons 0
    word oColorPickers 0
    word oUserButtons 0
    word oMagicObjects 0
    word oSmartGauges 0
    word oSmartSliders 0
    word oSmartKnobs 0
    word oTimers 0
    word oSoundss 0
    word oPinOutputs 0
    word FormBGcolors 0x0000
    word kKeyboardKeystrokes -1
    word rKeyboardRoutines -1
    word oLedDigitsn 92, 3, 1, 35, 1
#END

var hFonts[6] ;
var stringsCV[6] := [0, 0, 0, 0, 0, 0], hstrings ;
// Start P2.inc
var oObjects[MaxTotObjects+1] ;                 // address of objects
var CurrentForm, oldn, ImageTouched ;
var TouchXpos, TouchYpos ;
var InputType, TouchState, CurInputData, pInputIndex ;
var comRX[40], cmd[CMDLenMAX] ;

var InputCS, OutputCS ;



func seroutCS(var op)
    serout(op) ;
    OutputCS ^= op ;
endfunc

func nak0()
    serout(NAK) ;
    InputCS := 0 ;
endfunc

func seroutOcs()
    serout(OutputCS) ;
    OutputCS := 0 ;
endfunc

func SendReport(var id, var objt, var objn, var val)
    seroutCS(id) ;
    seroutCS(objt) ;
    seroutCS(objn) ;
    seroutCS(val >> 8) ; // first 8 bits
    seroutCS(val) ;
    seroutOcs() ;
endfunc


func ReadObject(var ObjectType, var ObjectIdx)
    var j, k, Objects ;
    Objects := *(oObjects+ObjectType) ;

    j := 2 + ObjectIdx * 2 + Objects ;
     if (ObjectType == tForm)
        k := CurrentForm ;
    else if ((ObjectType == tCustomdigits) || (ObjectType == tLeddigits))
        k := img_GetWord(hndl, *j, IMAGE_TAG2);
    else if (ObjectType == tStrings)
        k := stringsCV[ObjectIdx];
        else
        k := img_GetWord(hndl, *j, IMAGE_INDEX);
    endif
    SendReport(REPORT_OBJ, ObjectType, ObjectIdx, k) ;
endfunc


func WriteObject(var ObjectType, var ObjectIdx, var NewVal)
    var i, j, k, Objects ;
    ObjectType &= 0x3f ;
    if (ObjectType == tForm)
        ActivateForm(ObjectIdx) ;
    else
        Objects := *(oObjects+ObjectType)+ObjectIdx*2+2 ;
        i := *(Objects) ;
         if (ObjectType == tLeddigits)
            img_SetWord(hndl, i , IMAGE_TAG2, NewVal); // where state is 0 to 2
            ledDigitsDisplay(i, oLeddigitss, oLedDigitsn) ;
        else if (ObjectType == tStrings)
            PrintStrings(ObjectIdx, NewVal, 0);
        else
            img_SetWord(hndl, i , IMAGE_INDEX, NewVal); // where state is 0 to 2
            img_Show(hndl, i) ; // will only display if form is current
        endif
    endif
endfunc



// WARNING, this code will crash if newval exceeds maximum displayable number
func ledDigitsDisplay(var imgidx, var typeptr, var setptr)
    var i, j, k, l, lb, newval, num[4] ;
    if (!((img_GetWord(hndl, imgidx, IMAGE_FLAGS) & I_ENABLED))) return ;  // ;img_GetWord(hndl, imgidx, IMAGE_TAG2) ;if diabled then exit
    newval := img_GetWord(hndl, imgidx, IMAGE_TAG2) ;
    i := -1 ;
    j := *(typeptr) ;
    repeat
        typeptr += 2 ;
        i++ ;
        until (*(typeptr) == imgidx);
    j := setptr + i*10 ;

    l := 0x500a | (*(j+Ofs_Digits_Digits) << 8) ;       // UDECxZ
    to(num) ;
    putnum(l, newval) ;
    imgidx++ ;
    lb := *(j+Ofs_Digits_LeadingBlanks) ;
    l := str_Ptr(num) ;
    for (i := 0; i < *(j+Ofs_Digits_Digits); i++)
        k := str_GetByte(l++) & 0x0f ;
        if ( lb && (i < *(j+Ofs_Digits_Digits) - *(j+Ofs_Digits_MinDigits)) )
            if (k == 0)
                k := 10 ;
            else
                lb := 0 ;
            endif
        endif
        img_SetWord(hndl, imgidx, IMAGE_INDEX, k);
        img_SetWord(hndl, imgidx, IMAGE_XPOS, *(j+Ofs_Digits_Left)+i* *(j+Ofs_Digits_Widthdigit)) ;
        img_Show(hndl, imgidx);
    next
endfunc


func ActivateForm(var newform)
    var i, j, *p ;

    if (CurrentForm != -1) // deactivate old form, by disabling all inputs
        for (i := FormStartIndex[CurrentForm]; i <= FormEndIndex[CurrentForm]; i++)
            if (img_GetWord(hndl, i, IMAGE_TAG))
                img_Disable(hndl,i) ;
            endif
        next
    endif
    CurrentForm := newform ;
    // display newform image or clear to image color
    if (FormBGcolors[CurrentForm] != ColorBGimage)
        gfx_Set(BACKGROUND_COLOUR,FormBGcolors[CurrentForm]);
        gfx_Cls() ;
        DoGFXObjects() ;                                    // display GFX 'widgets'
    endif

    // enable inputs
    for (i := FormStartIndex[CurrentForm]; i < FormEndIndex[CurrentForm]; i++)
        j := img_GetWord(hndl, i, IMAGE_TAG) ;
        if (j)
            j-- ;
            img_SetAttributes(hndl, i, I_STAYONTOP+I_ENABLED);        // make sure this is on top of form, if applicable
            //if (j != tKeyboard)
            if ((j <= tWinButton) || (j >= t4Dbutton) )               // enable inputs
                img_ClearAttributes(hndl, i, I_TOUCH_DISABLE);        // ensure touch is enabled
            endif
            img_Show(hndl,i) ; // show initialy, if required
            if (j == tForm)
                DoGFXObjects() ;                                    // display GFX 'widgets' for image backgruobds
            else if (j == tLeddigits)
                ledDigitsDisplay(i, oLeddigitss, oLedDigitsn) ;
            endif
        endif
    next
    for (i := 0; i < nStrings; i++)
        if (stringsCV[i] != -1)
            WriteObject(tStrings, i, stringsCV[i]) ;
        endif
    next

endfunc

func UpdateObjects(var newval)
    var IPidx, otherOBJ ;
    if ( ( img_GetWord(hndl, *(pInputIndex), IMAGE_INDEX) != newval) || (TouchState == Ofs_IPD_RELEASE) ) // only bother if values changed, or release

        img_SetWord(hndl, *(pInputIndex), IMAGE_INDEX, newval);
            img_Show(hndl, *(pInputIndex));      // only shows on current form
        IPidx := *(CurInputData+TouchState) ;
        while(IPidx != 0)
            otherOBJ := IPidx + InputData;
            if (*(otherOBJ) == OT_REPORT)
        SendReport(REPORT_EVENT, InputType, *(otherOBJ+Ofs_IPD_OBJVIDX), newval) ;
            else if (*(otherOBJ) == OT_MAGIC)
                IPidx := *(otherOBJ+Ofs_IPD_P5) ;
                IPidx(newval) ;
            else if (TouchState == *(otherOBJ+Ofs_IPD_P4))
                if (*(otherOBJ) == OT_ACTIVATE)
                    ActivateForm(*(otherOBJ+Ofs_IPD_P2) ) ;
                    InputType := tForm ;
                else if (*(otherOBJ) == OT_SETCONST)
                    newval := *(otherOBJ+Ofs_IPD_P3) ;
                    WriteObject(*(otherOBJ+Ofs_IPD_P1), *(otherOBJ+Ofs_IPD_P2), newval) ;
                else if (*(otherOBJ) == OT_SETANOTHER)
                    WriteObject(*(otherOBJ+Ofs_IPD_P1), *(otherOBJ+Ofs_IPD_P2), newval) ;
                else if (*(otherOBJ) == OT_PREVFRAME)
                    if (img_GetWord(hndl, *(otherOBJ+Ofs_IPD_P6), IMAGE_INDEX))
                        WriteObject(*(otherOBJ+Ofs_IPD_P5),*(otherOBJ+Ofs_IPD_P2),img_GetWord(hndl, *(otherOBJ+Ofs_IPD_P6), IMAGE_INDEX)-1) ;
                    endif
                    newval := img_GetWord(hndl, *(otherOBJ+Ofs_IPD_P6), IMAGE_INDEX) ;
                else if (*(otherOBJ) == OT_NEXTFRAME)
                    if (img_GetWord(hndl, *(otherOBJ+Ofs_IPD_P6), IMAGE_INDEX) < *(otherOBJ+Ofs_IPD_P3))
                        WriteObject(*(otherOBJ+Ofs_IPD_P5),*(otherOBJ+Ofs_IPD_P2),img_GetWord(hndl, *(otherOBJ+Ofs_IPD_P6), IMAGE_INDEX)+1) ;
                    endif
                    newval := img_GetWord(hndl, *(otherOBJ+Ofs_IPD_P6), IMAGE_INDEX) ;
                else if (*(otherOBJ) == OT_PREVSTRING)
                    if (stringsCV[*(otherOBJ+Ofs_IPD_P2)])
                        WriteObject(tStrings,*(otherOBJ+Ofs_IPD_P2),stringsCV[*(otherOBJ+Ofs_IPD_P2)]-1) ;
                    endif
                else if (*(otherOBJ) == OT_NEXTSTRING)
                    if (stringsCV[*(otherOBJ+Ofs_IPD_P2)] < *(otherOBJ+Ofs_IPD_P3)) // fix IPD_P2 not filled in yet
                        WriteObject(tStrings,*(otherOBJ+Ofs_IPD_P2),stringsCV[*(otherOBJ+Ofs_IPD_P2)]+1) ;
                    endif
                endif
            endif
            IPidx := *(otherOBJ+TouchState) ;
        wend
    endif
endfunc

// End P2.inc
func DoGFXObjects()
endfunc

// Start P3.inc
func main()
    var comTX[50], cmdi, i, j, TouchStatus ;


    gfx_ScreenMode(PORTRAIT) ;

    putstr("Mounting...\n");
    if (!(file_Mount()))
        while(!(file_Mount()))
            putstr("Drive not mounted...");
            pause(200);
            gfx_Cls();
            pause(200);
        wend
    endif
//    gfx_MoveTo(0, 0);
//    print(mem_Heap()," ") ;
//    gfx_TransparentColour(0x0020);
//    gfx_Transparency(ON);

    // open image control
    hndl := file_LoadImageControl("VISICO~1.dat", "VISICO~1.gci", 1);

    // init 'constants'
// End P3.inc

    oObjects[tDipSwitch] := oDipSwitchs ;
    oObjects[tKnob] := oKnobs ;
    oObjects[tRockerSwitch] := oRockerSwitchs ;
    oObjects[tRotarySwitch] := oRotarySwitchs ;
    oObjects[tGSlider] := oGSliders ;
    oObjects[tTrackbar] := oTrackbars ;
    oObjects[tWinButton] := oWinButtons ;
    oObjects[tAngularmeter] := oAngularmeters ;
    oObjects[tCoolgauge] := oCoolgauges ;
    oObjects[tCustomdigits] := oCustomdigitss ;
    oObjects[tForm] := oForms ;
    oObjects[tGauge] := oGauges ;
    oObjects[tImage] := oImages ;
    oObjects[tKeyboard] := oKeyboards ;
    oObjects[tLed] := oLeds ;
    oObjects[tLeddigits] := oLeddigitss ;
    oObjects[tMeter] := oMeters ;
    oObjects[tStrings] := oStringss ;
    oObjects[tThermometer] := oThermometers ;
    oObjects[tUserled] := oUserleds ;
    oObjects[tVideo] := oVideos ;
    oObjects[tStaticText] := oStaticTexts ;
    oObjects[tSounds] := oSoundss ;
    oObjects[tTimer] := oTimers ;
    oObjects[tSpectrum] := oSpectrums ;
    oObjects[tTank] := oTanks ;
    oObjects[tUserImages] := oUserImagess ;
    oObjects[tPinOutput] := oPinOutputs ;
    oObjects[tPinInput] := oPinInputs ;
    oObjects[t4Dbutton] := o4Dbuttons ;
    oObjects[tAniButton] := oAniButtons ;
    oObjects[tColorPicker] := oColorPickers ;
    oObjects[tUserButton] := oUserButtons ;
    hFonts[0] := FONT3 ;
    hFonts[1] := FONT3 ;
    hFonts[2] := FONT3 ;
    hFonts[3] := FONT3 ;
    hFonts[4] := FONT3 ;
    hFonts[5] := FONT3 ;
// Start P4.inc
    hstrings := file_Open("VISICO~1.txf", 'r') ; // Open handle to access uSD strings, uncomment if required
    // init comms
    com_Init(comRX,CMDLenMAX,0);
    com_SetBaud(COM0,960);
    com_TXbuffer(comTX, 100, 0);
    // tag 'real' objects
    for (i := 0; i <= MaxTotObjects; i++)
        if (   (i != tSounds)
            && (i != tTimer)
            && (i != tPinOutput)
            && (i != tPinInput) )
            TouchXpos := oObjects[i] ;
            TouchYpos := *(TouchXpos) ;
            for (ImageTouched := 1; ImageTouched <= TouchYpos; ImageTouched++)
                oldn := *(TouchXpos+ImageTouched*2) ;
                img_SetAttributes(hndl, oldn, I_TOUCH_DISABLE);  // ensure touch is enabled
                if (oldn != -1)
                    img_SetWord(hndl, oldn, IMAGE_TAG, i+1);
                    img_Disable(hndl, oldn) ;
                endif
            next
        endif
    next

    // display initial form
    CurrentForm := -1 ;
// End P4.inc
// Start P5.inc
    ActivateForm(0) ; // need to change this according to first actual form

// End P5.inc
// Start P6.inc
    touch_Set(TOUCH_ENABLE);                            // enable the touch screen
    oldn := -1 ;
    repeat

        // check comms for command, how to NAK invalid command
        if (com_Count() != 0)
            i := serin() ;
            InputCS ^= i ;               // update checksum
            if (   (cmdi > 2)
                && (cmd[0] == WRITE_STRU) )
                j := (cmdi-1) >> 1 + 2 ;
                if (j == CMDLenMAX)    // max length exceeded
                    nak0() ;
                    cmdi := -1 ;
                else if (cmdi & 1)
                    cmd[j] := i ;
                    if (cmd[2] == 0)    // if string complete
                        if (InputCS)
                            nak0() ;
                        else
                            if (cmd[0] == WRITE_STRU)
                            cmd[j] := 0 ;                     // terminate it
                            PrintStrings(cmd[1], &cmd[3], 1) ;
                            serout(ACK) ;
                            else
                            endif
                        endif
                        cmdi := -1 ;
                    endif
                else
                    cmd[j] := cmd[j] << 8 + i ;
                    cmd[2]-- ;          // dec length
                endif
                cmdi++ ;
            else // not unicode string
                cmd[cmdi++] := i ;
                 if (cmd[0] == WRITE_STR)                  // Ansi String
                    if (cmdi == CMDLenMAX)      // max length exceeded
                        nak0() ;
                        cmdi := 0 ;
                    else if (cmdi > 2)
                        if (cmd[2] == -1)
                            if (InputCS)
                                nak0() ;
                            else
                                if (cmd[0] == WRITE_STR)
                                cmd[cmdi-1] := 0 ;                     // terminate it
                                PrintStrings(cmd[1], &cmd[3], 1) ;
                                serout(ACK) ;
                                else
                                endif
                            endif
                            cmdi := 0 ;
                        else
                            cmd[2]-- ;          // dec length
                        endif
                    endif
                else if (   (cmd[0] == READ_OBJ)
                         && (cmdi == 4)         )
                    if (InputCS)
                        nak0() ;
                    else
                        ReadObject(cmd[1], cmd[2]) ;
                    endif
                    cmdi := 0 ;
                else if (   (cmd[0] == WRITE_OBJ)    // 6 byte write command (gen option)
                         && (cmdi == 6)          )
                    if (InputCS)
                        nak0() ;
                    else
                        WriteObject(cmd[1], cmd[2], cmd[3] << 8 + cmd[4]) ;
                        serout(ACK) ;
                    endif
                    cmdi := 0 ;
                else if (   (cmd[0] == WRITE_CONTRAST)
                         && (cmdi == 3)         )
                    if (InputCS)
                        nak0() ;
                    else
                        gfx_Contrast(cmd[1]) ;
                        serout(ACK) ;
                    endif
                    cmdi := 0 ;
                else if (cmdi == 6)    // we have 6 bytes and we've gotten here -> something wrong
                    nak0() ;
                    cmdi := 0 ;
                endif
            endif   // not unicode string
        endif   // a character is available


    // touch code processing

        TouchStatus := touch_Get(TOUCH_STATUS);               // get touchscreen status
        ImageTouched := img_Touched(hndl,-1) ;
        if ((TouchStatus == TOUCH_PRESSED) || (TouchStatus == TOUCH_RELEASED) || (TouchStatus == TOUCH_MOVING))
            if ((TouchStatus != TOUCH_RELEASED) && (ImageTouched != oldn) && (oldn != -1))
                TouchStatus := TOUCH_RELEASED ;       // simulate release if we move off object
            endif
            if (TouchStatus != TOUCH_RELEASED)        // if not released
                if (oldn != -1)
                    ImageTouched := oldn ;
                else
                    if (oldn != ImageTouched)
                oldn := ImageTouched ;
                        TouchStatus := TOUCH_PRESSED ;
                    endif
                endif
                TouchXpos  := touch_Get(TOUCH_GETX);
                TouchYpos  := touch_Get(TOUCH_GETY);
                TouchState := Ofs_IPD_DOWN ;
            else
                ImageTouched := oldn ;                     // simulate release of what we touched
                oldn := -1 ;                    // prevent double release
                TouchState := Ofs_IPD_RELEASE ;
            endif
            if (ImageTouched != -1)
                        CurInputData := InputControls[ImageTouched] + InputData;
                        InputType := *(CurInputData) ;
                        i := InputType ;
                        if (InputType >= t4Dbutton) i -= 23 ; // adjust to ensure next in gosub
                        gosub (i), (cDipswitch, cKnob, cRockerswitch, cRotaryswitch, cSlider, cTrackbar, cWinbutton, c4DButton, cAniButton, cColorPicker, cUserButton) ;
            endif
        endif
 //       if ((n != -1) && (oldn == -1)) oldn := n ;    // save what we touched in case we move off it

        sys_EventsResume() ;
    forever

cDipswitch:

cKnob:

cRockerswitch:

cRotaryswitch:

cSlider:
cTrackbar:

c4DButton:
cUserButton:
cWinbutton:
gbutton:

cAniButton:

cColorPicker:

endfunc
// End P6.inc