In memorial: Domotiga

Spijtig genoeg voor iedereen die met Gambas3 en Raspberry Pi werkt, is Domotiga al een tijdje verdwenen, ik schat van ongeveer 2015. De website www.domotiga.nl ging ergens in de zomer van 2015 (mei?) over in de site van CyberJunky’s Blog, alter ego van een zekere Ron “RDNZL”, de auteur van Domotiga. Die naam herken je zeker als je op de Gambas mailinglist ingeschreven was, daar was hij erg aktief meen ik mij te herinneren, en daarmee ook een aanwinst voor Gambas gebruikers in het algemeen.
Op CyberJunky vond je nog links naar Domotiga en DomotiYii, maar ondertussen lijkt er enkel een wordpress reklamesite van één of ander netwerk op de domeinnaam domotiga.nl te staan.

Ergens las ik dat de maker van Domotiga zelf overstapte op een ander systeem voor zijn domotica toepassingen (ok, dat klinkt erg onwetenschappelijk) – ik zie op de cyberjunky github pagina allerlei “Home-assistant” werk.. dat terug te volgen is tot december 2016. Home Assistant draait op Python.

Je vindt nog wel resten van Domotiga terug, bv in web.archive.org, waar je op de about pagina kan terugvinden dat Domotiga startte als een eigen versie van ‘Misterhouse”.

De laatste updates aan de Domotiga github repository vermelden (op dit moment) “May 16, 2019”, dus mogelijk draait het hier en daar nog wel…

Gambas 3.14 start niet

openSUSE 15.1 + Education repository
De oorspronkelijke Gambas 3.10 werkt normaal.
Na een upgrade en wijziging van repository naar Education en Gambas 3.14 start gambas niet.

Op de commandolijn:

gambas

gbx3: unable to find startup file

En op een andere manier, met het hele pad:

/usr/bin/gambas3.gambas

~> /usr/bin/gambas3.gambas 
ERROR: #27: Cannot load component 'gb.jit': cannot find component

** 
** OOPS! INTERNAL ERROR. Program aborting, sorry! :-(
** Unable to find JIT._Abort() method
** 
** ERROR: Unable to find method _Abort in class Jit. Symbol not found
** 
** Please send a bug report to the gambas bugtracker [1] or to the gambas mailing-list [2].
** [1] http://gambaswiki.org/bugtracker
** [2] https://lists.gambas-basic.org/listinfo/user
** 

Daar staat alles in wat we nodig hebben:

..
Error: #27: Cannot load component ‘gb.jit’: cannot find component
..
Unable to find JIT._Abort() method

In Yast, software management, zoek “gambas3” zie je dat gb-jit niet aangevinkt staat in de lijst van gambas3 programma-onderdelen.
Dus bij-installeren:

gambas3-gb-jit

Klaar!

ps: er stonden, behalve al de debug packages, nog een paar paketten niet aangevinkt …

– gb-gtk3
– gb-inotify
– gb-media
– gb-option
– gb-scanner

Veilige verbinding met de databankserver door SSL, TLS

Eigen verbinding controleren:

login als mysql gebruiker op de server.
Checke met \s hoe de gebruikte verbinding verloopt; kijk naar “SSL”:

MariaDB [(none)]> \s
--------------
mysql Ver 15.1 Distrib 10.0.32-MariaDB, for Linux (x86_64) using readline 5.1

Connection id: 33
Current database:
Current user: copyleft@192.168.1.222
SSL: Not in use
Current pager: less
Using outfile: ''
Using delimiter: ;
Server: MariaDB
Server version: 10.0.32-MariaDB SLE 12 SP1 package
Protocol version: 10
Connection: 192.168.1.111 via TCP/IP
Server characterset: utf8
Db characterset: utf8
Client characterset: utf8
Conn. characterset: utf8
TCP port: 3306
Uptime: 32 min 34 sec

Er wordt dus geen veilige verbinding gebruikt (SSL: Not in use)

Log in op de server als root.
Kijk naar de instellingen ivm ssl :

MariaDB [(none)]> show variables like '%ssl';
+---------------+----------+
| Variable_name | Value |
+---------------+----------+
| have_openssl | YES |
| have_ssl | DISABLED |
+---------------+----------+

De SSL mogelijkheden zijn mee gecompileerd, maar niet in gebruik.

Volgende stappen:
– SSL keys instellen met juiste user, en server herstarten om SSL gebruik te aktiveren.
– Server zo instellen dat hij van buitenaf enkel SSL verbindingen beantwoordt (van binnenuit kan het nog via unix sockets dan)

Engelstalig artikel (toegepast op ubuntu) op:

https://www.digitalocean.com/community/tutorials/how-to-configure-ssl-tls-for-mysql-on-ubuntu-16-04

Luie programmeurs: automatische naam voor button

Als je aan het programmeren bent en je snel wat knoppen in je vensters zet, is het belangrijk om die knoppen een relevante naam te geven.

Bv een nieuwe knop Button1 krijgt de naam buttonQuit of in mijn geval btQuit (ik kort de prefix voor het schermobjecttype af tot 2 of 4 lettertekens zoals bt, lsbx, cmbx, enz).

De knoppen hebben ook een functie naar de gebruiker, je moet ze dus ook een etiket geven voor de gebruiker, de Text property: Button1.Text of bij mij: btQuit.Text=”Quit”.

Zie je de luiheid al opkomen? In de nieuwere versies van Gambas (Gambas3) wordt de Button1.Name ook weergegeven op de knop als je zelf nog geen tekst erin zette; dit is bijzonder handig tijdens het programmeren.

Maar als die tekst eens ineens kan blijven staan als goede tekst? In mijn geval zou btQuit wel de opdruk “Quit” moeten krijgen, dus de twee letterjes prefix van het type moeten eraf. Right(Button1.Text, -2) als het ware.

Daar gaan we dus:

In een steeds in mijn projecten voorkomende schermfunctiesverzameling plant ik deze:

MForm

Public Sub doNameButtons(hMainObject As Object)
  '
  Dim hObject As Object
  Dim hButton As Button
  Dim iChildrenCount As Integer
  '
  Debug hMainObject.Name & " " & TypeOf(hMainObject)
  '
  Try iChildrenCount = hMainObject.Children.Count
  '
  If Not Error 
    '
    For Each hObject In hMainObject.Children
      '
      Try hButton = hObject
      '
      If Not Error
        If Left(hObject.Name, 2) = "bt" And hButton.Text = ""
          Debug "CHANGE TO " & Right(hObject.Name, -2)
          hButton.Text = Right(hObject.Name, -2)
        Else
          ' not named with naming convention of buttons, this is a way to let you exclude empty-text buttons from this auto-name 
          ' name not empty, so it stays as it is - and can be translated 
          Debug "NO CHANGE: " & hObject.Name
        Endif
      Else
        ' recursion
        Debug hObject.Name & " is no button; check for .children : "
        doNameButtons(hObject)
      Endif        
    Next
  Else
    Debug "Error hMainObject.Children.Count " & hMainObject.Name & " " & iChildrenCount
  Endif  
End
'

In mijn FMain (en alle andere schermen die ik snel in elkaar flikker) roep ik dat zo aan voor iedere container met buttons in (soms nog eens in een container – dus recursief):

FMain

Public Sub Form_Open()
'
  MForm.doNameButtons(HBox1)
' ...
End

Alle knoppen in de Form die al een opschrift hebben, houden die. De anderen krijgen één, afgeleid van de knopnaam.

Let op!
Dit is alleen handig tijdens het programmeren, als de knoppen nog van functie en naam veranderen enz.

Eens het project zo ver af is dat je het gaat vertalen zul je toch de Text properties van de knoppen moeten invullen, anders werkt het vertaalsysteem niet.

Check eerst of het een button is:

TypeOf(hObject) = TypeOf(hButton)

Dit is geen oplossing, omdat er niet zo’n fijn onderscheid wordt gemaakt tussen objecten in TypeOf; beiden zijn “16” (een constante voor gb.Object); ook een HBox of VBox behoort daartoe.

Ik vond geen andere methode dan het child object proberen toe te wijzen aan een button; bij fout is het geen button, anders wel.

Zelfs tussen buttons kan er een ander object staan zoals een leeg tekstveld om als “spacer” te gebruiken; tekst leeg maar expand=TRUE zodat het een knop aan de rechterkant naar rechts tot aan het einde van de container (bv HBox) duwt.

Hernoemen uitvoeren op button object met naam beginnend met “bt”, de anderen worden overgeslagen.

Recursie
Als het object geen button was, kan het altijd nog een container zijn die ook weer buttons bevat, dus wordt dat gecheckt en kan de hernoem-test opnieuw beginnen.
Daarom wordt de procedure zelf terug opgeroepen.

Ik vond ook geen mooie manier om te testen of het een container is, en als er geen children objecten zijn, crasht

For Each hObject In hMainObject.Children

Daarom test ik eerst of een Children.Count wel kan:

Try iChildrenCount += hMainObject.Children.Count

If Not Error

(het optellen heeft niet echt zin)

Upd 13092017: bug fix: crash bij ander object dan button en helemaal herwerkt.

Vormgevingconventies doorheen app

Om een concistente applicatie te krijgen moet je overal dezelfde conventies toepassen.

bv: titels (grootte), foutmeldingen, kleuren bij invulvelden, …

Form Textlabel Title : font, size, color, em.

Field Textlabel Title: font, size, color, bgcolor, em.

Field content: font, size, bgcolor
– vaste tekst : zwart
– invulveld (niet invul/wijzigbaar) color dark gray, background light gray
– invulveld (vast): color black
– wijzigbaar: color blue,
– gewijzigd: color light blue (darkcyan)
– bewaard na wijziging: dark blue
– geweigerde invoer: red
– nog niet bewaard: orange
– voorbeelden, help: green

Het handigst is in MForm een aantal procedures te maken voor het instellen van eigenschappen van velden en die van overal te gebruiken.

Foutenbeheer en log via Error klasse

Ik wil in mijn programma:

  • een vaste manier om met foutmeldingen te werken
  • nadien de foutmeldingen kunnen nazien

In allerlei Classes, Modules en Forms kunnen fouten ontstaan, ik hou altijd de laatste ter plaatse bij in een variabele sLastError.


PRIVATE sLastError AS String

PUBLIC SUB getLastError() AS String
  
  RETURN sLastError
  
END

In mijn FMain heb ik een procedure waar ik alle foutmeldingen naartoegooi:

FMain


PUBLIC SUB SetError(sTextline AS String) 
  
  lsbxError.Add(Str$(iErrCounter) & ": " & sTextline, MCommon.insertTop)
  IF MMain.bErrorFile 
    MMain.hError.writeError(Str$(iErrCounter) & ": " & sTextline)
  ENDIF 
  INC iErrCounter
  
END

Fouten zakken weg naar onder in het zichtbaar meldingen-schermdeel in de toepassing, de ListBox lsbxError.
De constante in MCommon die aangeeft dat bovenaan in de lijst wordt toegevoegd is misschien wat overdreven:

MCommon

PUBLIC CONST insertTop AS Integer = 0

In MMain heb ik mijn error object, wat alle foutmeldingen opvangt:

MMain

... 
hError = NEW CError  
  IF hError.sLastError
    bErrorFile = FALSE
    sLastError = "Error handling to file: " & hError.sLastError
  ELSE 
    bErrorFile = TRUE
  ENDIF 
...

Mijn foutklasse heet CError:

CError

PRIVATE $hErrorFile AS File
PUBLIC sLastError AS String
PRIVATE sErrorFileName AS String


PUBLIC SUB _new(OPTIONAL sFileName AS String)
  
  IF sFileName
    sErrorFileName = sFileName
    openErrorFile(sErrorFileName)
  ELSE 
    sErrorFileName = System.User.Home & "/.config/gambas" & "/" & Application.Name & "-error.txt"
    DEBUG sErrorFileName
    openErrorFile(sErrorFileName)
  ENDIF 
  
END

PUBLIC SUB getErrorFileName() AS String
  
  RETURN sErrorFileName

END


PUBLIC SUB openErrorFile(sErrorFile AS String) AS Boolean
  
  IF Exist(sErrorFile)
    'TRY $hErrorFile = OPEN sErrorFile FOR WRITE APPEND ' cannot append without maintenance
    TRY $hErrorFile = OPEN sErrorFile FOR WRITE CREATE  
  ELSE 
    TRY $hErrorFile = OPEN sErrorFile FOR WRITE CREATE 
  ENDIF 
  
  IF ERROR 
    sLastError = Error.Text
    RETURN FALSE
  ELSE 
    writeError("*** CError uses file: " & sErrorFileName)
    writeError("*** on: " & System.Host & " " & System.User.Name & " " & Application.Name & " " & Application.Version & " from " & Application.Path)
    sLastError = ""
    RETURN TRUE
  ENDIF 
  
END

PUBLIC SUB closeErrorFile()
  
  TRY CLOSE $hErrorFile
  TRY $hErrorFile.Close()
  
END


PUBLIC SUB writeError(sErrorText AS String) AS Boolean
  
  TRY PRINT #$hErrorFile, Date & "@" & Time & ":" & sErrorText
  IF ERROR 
    RETURN FALSE
  ELSE 
    RETURN TRUE
  ENDIF 
  
END

Upd 2018-04: ondertussen iets aangepaste versie:

' Gambas class file

' CError - help with formatting for display and storing to an error log

Public bLogToFile As Boolean

Private sPreviousError As String
Private sLastError As String
Private SErrorText As String

Private $hErrorFile As File
Private iErrorCount As Integer

Public Sub _new()
  
  Dim s As String
  
  ' rotate error log file
  If Exist(getErrorFileName() & "~")
    Try Kill getErrorFileName() & "~"
  Endif
  Try Copy getErrorFileName() To getErrorFileName() & "~"
  Try $hErrorFile = Open getErrorFileName() For Write Create
  If Not Error
    bLogToFile = True
    Exec ["gambas3", "-V"] To s
    s = "Error file for " & Application.Name & " " & Application.Version & " for " & System.User.Name & "@" & System.Host & ", gambas " & s
    Print #$hErrorFile, s 
    Print #$hErrorFile, " ----------------------------------------------------------------------------------------" 
    Try Close $hErrorFile
  Else
    Debug "try to open error file " & getErrorFileName() & ": " & Error.Text
    bLogToFile = False
  Endif
  iErrorCount = 0
  
End

Public Sub getErrorFileName() As String
  
  If Not Exist(System.User.Home &/ ".local/log")
    Mkdir System.User.Home &/ ".local/log/"
  Endif
  Return System.User.Home &/ ".local/log/" & Application.Name & "-err.txt"
  
End

Public Sub shiftError(sNewError As String)
 
  sPreviousError = sLastError
  sLastError = Stamp(sNewError)

End



Public Sub Stamp(s As String) As String
  
  Return Now() & ";" & Str(iErrorCount) & ": " & s
  
End


Public Sub setError(sError As String, Optional bForceLogToFile As Boolean) As String
  
  SErrorText = sError
  Inc iErrorCount
  shiftError(sError)
  If (bForceLogToFile Or bLogToFile)
    LogToFile(sLastError)
  Endif
  Return iErrorCount & " " & sError & " @" & Time(Now())
  
End


Public Sub getPreviousError() As String
  
  Return sPreviousError
  
End

Public Sub LogToFile(s As String)
  
  Try $hErrorFile = Open getErrorFileName() For Write Append
  If Error
    Debug Error.Text
  Endif
  Try Print #$hErrorFile, s
  If Error 
    Debug Error.Text
  Endif
  Try Close $hErrorFile
  If Error
    Debug Error.Text
  Endif
  
End

Foutafhandeling (TRY – IF ERROR)

ps: Lijst van foutmeldingen gambas2 zijn hier nog terug te vinden:
http://files.allbasic.info/Gambas/help/help/error.html

Gambas heeft de TRY – IF ERROR combinatie.
Als de kans groot is dat zich een fout voordoet kan je de lijn beginnen met TRY. Als zich dan een fout voordoet crasht de applicatie niet, maar wordt akte genomen van de fout en loopt de applicatie door. Om waarschijnlijk verder toch in de problemen te komen?
Daarom is er de tweede stap: IF ERROR …
Deze instructie zet je na de lijn die begint met TRY.
Als er een fout is opgetreden, wordt de IF ERROR lijn uitgevoerd.

Dat is handig want je kan hier twee zaken mee doen:

  1. De foutmelding gaan ophalen, en er iets zinnigs mee doen, als loggen/opslaan, tonen, …
  2. De loop van het programma aanpassen aan het zich voordoen van de fout; kans geven om iets opnieuw te proberen, toestand van buttons / menu’s wijzigen zodat geen verdere akties genomen kunnen worden die de applicatie zouden doen crashen, een procedure starten om de fout effectief op te lossen, …

1. De foutmelding

De foutmelding zit in dat geval in Error.Text; je kan ze opvragen:

IF ERROR
  DEBUG Error.Text
ELSE
   ...
ENDIF

Terwijl je bezig bent met programmeren is het nuttig en nodig om de foutmeldingen te zien te krijgen. Als het programma crasht zie je ze. Soms wil je echter voortwerken aan een ander stuk van het programma, waarvoor deze fout niet belangrijk is. Je kan die dan “wegmoffelen” door voor de programmalijn die de fout levert, TRY te zetten. Het programma loopt na de fout door, maar: je krijgt de foutmelding niet meer te zien! Je ziet dus bv niet of ze verandert, zich niet meer zou voordoen, enz. Je vergeet ze misschien en dat levert laten een “bug” op.

Met deze DEBUG zie je ze tenminsten nog onderaan op je terminal-achtige venster in Gambas. Maar misschien is het beter ineens een procedure te maken om die fout ook in het lopende programma bij de gebruiker te gebruiken, want na compilatie zijn de DEBUG’s weg.

Eigenlijk is het minimum dat je ze opslaat in een tekstvariabele:

IF ERROR
  sLastError = Error.Text
...

Je kan dat lokaal doen, bv in elke module, Class, enz, lokaal een variabele maken om de fout op te slaan, en een procedure om ze op te vragen:

PRIVATE sLastError AS String
 
PUBLIC SUB getLastError() AS String
  RETURN sLastError
END


IF ERROR
  sLastError = Error.Text
...

De aanroepende code moet dan nog wel te weten komen dat er een fout was. Dat wordt meestal gedaan door
– een extreem andere waarde terug te geven als een waarde verwacht wordt (return value), bv 0 of -1.
– als geen return value nodig is, geef dan toch een TRUE als alles ok is, en FALSE terug als zich een fout voordeed.

In de aanroepende code:

IF module1.DoSomething(..)
  ...
ELSE
  Message.Error(module1.getLastError())
ENDIF

of, bv als je -1 als teken van fout gebruikt:

IF module1.CalcSomething(..) < 0
  Message.Error(module1.getLastError())
ELSE
  ...
ENDIF

Enkele ideeën:

  • Maak een procedure om de fouten op te vangen in je hoofdprogramma; bv doError(s AS String), die je de foutmelding laat weergeven in een speciaal foutmeldingsvenster in de app, en/of laat opslaan in een logbestand
  • Maak een class om de foutafhandeling te doen. Dan moet wel extra informatie door gegeven worden over waar de fout vandaan kwam.

Klasse met een “embedded array”

Bij het maken van de klasse, met verschillende types van eigenschappen, kan je op een punt komen dat je een array wil gebruiken, waarbij je niet op voorhand weet hoe lang die gaat worden. En je wil dat die bereikbaaar is van buitenaf, m.a.w. de array is public. (bv Objecten die een array bevatten)

Het object dat van de klasse afgeleid is, krijgt “data” te slikken die het in die array voert.

De nodige geheugenruimte voor een object ligt vast door het type van de eigenschappen van het object. Maar met deze array, waaraan steeds elementen kunnen toegevoegd worden dezearray.Add(iets), kan onvoorspelbaar groot worden, wat in tegenstelling is met de afgelijnde grootte van het object.


Ik vermoed dat dit het probleem is dat men aankaart met de vermelding van “embedded arrays”.
Je kan extern een array aanmaken, die je aan het object toespeelt. Het ruimtebeslag gebeurt dan buiten het object.
In een beschrijving van OOP voor Java zag ik dat een array altijd een object is. Dan is de plaats die het in beslag neemt die van de verwijzing naar een object, en is er toch geen probleem?

In een andere (Duitstalige) beschrijving blijkt dat de “embedded array” altijd op voorhand gedefinieerd is, bv

PUBLIC embeddedArray[2, 2] As Integer

De dimensies liggen vast. Hij wordt niet geïnitialiseerd. Het gebruik ervan is uitzonderlijk.

De andere arrays zouden dan “objecten” zijn, en toch gewoon gebruikt kunnen worden.

Klik en slepen beheren met een object? (Drag’n drop object)

In een bepaalde toepassing wilde ik meer controle krijgen over de klik en sleep akties in een form, omdat er verschillende mogelijke bronnen en bestemmingen waren. Ik weet niet of het een goed idee is, maar ik maakte er een apart object voor; zeg maar door een Class CDragDrop.

Daarin definieerde ik de nodige constanten.
Ik hield bij waar de klik en sleep vandaan kwam, en in geval van een tabel bv ook de coordinaten dmv rij en kolom in die tabel (row, col).

Ik definieerde een aantal mogelijke ontvangers in een array.
(deze zijn redelijk specifiek voor het scherm (Form) waarin ik werk)
bv:


' Gambas class file
' CDragDrop - to manage drag n drop
'
PUBLIC CONST Employee AS String = "employee"
PUBLIC CONST DestinationOne AS String = "destination1"
PUBLIC CONST DestinationTwo AS String = "destination2"
'
PRIVATE arrReceiver AS String[] = [Employee, DestinationOne, DestinationTwo]

Klik en sleep toestanden kan je bijhouden door methodes als:
Door procedures als

  • setOrigin
  • setReceiver
  • getOrigin
  • getReceiver

(deze zijn op zich redelijk universeel, invulling verschilt).
Ook kan je de bron en bestemmeling checken in de klasse.

Ik hergebruik het object van deze klasse:

PUBLIC SUB clear()

waarin ik alles leeg maak; maar je kan ook een nieuwe instantie maken en de gebruikte bijhouden als referentie (fifo lijstje?), als je nadien moet weten wat je vorige klik-en sleep aktie was.

English summary:
I created my own class to manage drag’n drop in my app. I can keep code away from my Form, and have all drag’n drop information at hand in one place. I could even keep the last action archived by keeping the previous instance(s) (in a FIFO array?).