Showing posts with label Qt. Show all posts
Showing posts with label Qt. Show all posts

Friday, 30 December 2022

TrafficControl: Exploring Qt WebAssembly

I've seen that it is possible to create executables that can be run from within a web browser. This is very interesting for my pet project Traffic Control and I'll spend some time exploring this.

What is web assembly and why bother?


Desktop applications are a bit inconvenient for the end user. He/she needs to find an installation package that goes with the particular operating system - that requires some competence and also some administrator rights. Further, there is a risk of viruses. After using the software, the software lies on the computer until removed. It will take quite a bit of persuation to make people install my software. 

With a Web Assembly app, it is different. The program will be linked from a simple web page that loads the executable to a sandboxed environment inside the web browser the user is using. This will be independent on the hardware architecture and operating system. No installation and no cleanup.

Qt supports web assembly - this is the key point of Qt: that it is a programming framework that can be used for different platforms. If I'm able to port TrafficControl to web assembly and deploy on a web server, the program will be able to run from any connected device with one of the bigger web browsers.

What would that mean for me? 

For me, the program would be deployable to any unit that runs one of the major web browsers. To deploy the software, I would need to set up a web server, or use an existing platform that hosts the binary. 

Traffic Control would need some refactoring:

  • The program depends on python for converting files. That needs to be in C++
  • I need to consider how to access local files and examples
  • I need to look into the user interface - should I migrate to QML for all user interface?

Step-by-step

  1. Install an updated Python version and Emscripten 
  2. Setup environment for Qt Web Assembly
    1. Specify C and C++ compiler
    2. Other settings?
  3.  Configure for simple C++/QML application - QML Demo
    1. Download from GitHub
    2. Compile for desktop
    3. Compile for Qt Web Assembly
    4. Open in Web Assembly
  4. Adopt TrafficControl to WebAssembly
    1. Find out how to store the TNF/TNM/TNO files when using webAssembly? Initially, use read-only and disable the conversion features?
    2. Handle loading of traffic network files
    3. Port the conversion from KML to Traffic Map File/Traffic Network file to either
      1. Python in-project code, or
      2. C++ Code
  5. Deploy application on web server

Emscripten is used for compiling to webAssembly and it is based on Python and needs a late Python version. Installing Python is straight forward. 

I installed Emscripten from the git repo:

Yes, I'm using a screen shot.
If you're copying commands, you'd better copy from the formal documentation.
As I'm using Qt 5.15.2, I need to install the corresponding Emscripten version (1.39.8) and activate it permanently (use the same version for each instance:


However, after activating emsdk, the python path is missing. I found out that I need to run the shell as an administrator.

In Qt, I currently use the auto-detected "Local PC" as a target for the binaries. I added a web assembly device where I pointed to the Emscripten folder.


Once the device is configured, I need to set up a kit for the compilation. 

Finally, I specified that the project should have the web assembly option in the "Projects" tab.

This time, I used an old QML demo project that I have on my Github account. In that project, I explored some basic QML features and how to interact from C++. 

During the build process, I saw a warning - my installation of Qt was actually built with Emscripten 1.39.7, not 1.39.8 as the documentation said. The build was still successful.

After the build was completed, I started it by loading the local web site:
There it is! A C++/QML application that is running inside a web browser. The html page can be embedded into another web page on a web server. For my simple QML application, the binary used ~20 MB for the release build and ~27 MB for the debug build.

In the next blog post, I'll explore if it is possible to port TrafficControl to web assembly. For Qt 5.15.2, Qt Location seems not to be supported for web assembly. For Qt6, QtLocation and QtPositioning didn't work out of the box, maybe it is possible to find a workaround.












Saturday, 1 October 2022

Qt: Upgrading from Qt 5.12.2 to Qt 6.3

After a couple of years exploring Machine Learning, IoT and Python, I'll return to TrafficControl. The current version of my project is Qt 5.12 - more than three years old. 

Upgrading from Qt 5.12 to Qt 6.3.2 seems to be easier said than done. It is recommended to start upgrading to Qt 5.15 and after that upgrading to Qt 6.3.2.

The steps for this project are:

Build and test the program as it is, with the installed version of Qt. 

There are sometimes changes to external services, such as map provider. I may also have done changes that I forgot about several years ago. 

Further, my antivirus program scans the binary that I compile, and that takes some time.

https://forum.qt.io/topic/90617/avast-alarm-to-qt-5-10-1-static-build/4

I solved the issue by adding an exception to the anti virus program. 

Push the current version of the program to GitHub. 

I saw that I had some staged changes that I had to check. After generating an access token, I was finally able to push the changes to GitHub.

Test program for Qt 5.15

The recommended way to port an application from Qt5 to Qt6 is to upgrade to/compile for the latest Qt5 version (Qt 5.15) first, and resolve any issues. After that, one can upgrade to Qt6.

I started with disabling any Qt modules that was deprecated between Qt 5.15 by adding the following line to the pro file:

DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x050F00

After some minor fixes, I got the program to run and almost all tests to pass.

Upgrade Qt 5.12 to Qt 6.3.2

I used the online installer and I found some strange issues when trying to run the program. 



The issue was that the module QtLocation (a module that is enabling maps on Qt applications) hasn't yet been ported to Qt 6. The work is ongoing and can be tracked here.




This means that I need to postpone the migration to Qt 6 and use Qt 5.15 instead.

Using Qt 5.15 - a new crash

After installing Qt 5.15, I got a software crash when running the application. 

The program worked fine in Qt 5.12 so I assume there are changes in Qt between 5.12 and 5.15 that made my code fail.

My classes trainListModel, stationListModel and trackListModel are Model/View implementations for the network and are used to simplify the data presentation. They all inherit the trafficDataModel, which in turn inherits the QAbstractTableModel, which inherits QAbstractItemModel, which inherits QObject.


There is a flaw when I call the insertRows() function, where the position where I want to add rows is calculated as a number that I just incremented. When changing the position to the actual list of the train/track/stationListModel, the program works fine. 

The next step will be to review the code and push it to GitHub. After that, I'll investigate what to implement next.

Saturday, 11 January 2020

2019 in Retrospect


This year, I've had some progress on my pet projects. The progress rate was slower in Q4 as we have bought a house. We'll move there in 2020.

Maintaining a house will require some work and learning activities. I expect that the pace of my pet projects will be slower than before.

2019
  • TrafficControl (C++ / Qt) - Paused
    • No work on this project in 2019.
  • StockReader/StockAnalyzer (C# / SQL / MSVC) - Current focus
    • Creating a C# program that scans a folder for result files, extract stock records and parses the data to a database. I've got some experience with Microsoft Visual Studio by now and I'm still learning...
    • Next step will be to fix issues with data conversion to SQL
    • After that, I'll explore some Machine Learning algorithms and check if I can predict stock price changes based on key numbers.
  • ApartmentPredictor (Python) - Done
    • A Python script that made Linear Regression Analysis of past apartment prices in an area. The predictions were based on the apartment size and the monthly cost. 
  • Machine Learning Course at Stanford (Octave / Matlab)  - Done
    • I successfully attended and finished the course in Machine Learning from Stanford University.
    • I applied some machine learning for a pet project for a friend. The simulations indicated that the result was random to a very high degree,
  • TravelTimeCalculator (Java / SQLite / Android Studio) - Done
    • I've completed the project now. TravelTimeCalculator allows the user to compare travel time/cost between several positions on a map. They can also customize the travel cost to consider both travel time, economic costs and environmental cost.



  • Work (C / Python / 4G / 5G) At work, I've focused on test and some coding for 5G with focus on antenna calibration.
To Do in 2020:
Prio 1: The New House!
Prio 2: StockReader/StockAnalyzer

Saturday, 21 July 2018

TrafficControl: Minor Fixes for Network Designer and User Interface

As mentioned earlier, the default path to the network files were hardcoded. This made TrafficControl dependent on having that particular folder on the particular computer, which is bad design. Instead, I used QStandardPaths to make the program look for the files in the user's home directory.


Further, I've modified the code for generating the TNM file. It is possible to select either a KML file, or a Traffic Network Map/File. In the latter case, the file is opened and parsed directly to the text field.

TrafficControl is setting up the UI and has many signal/slot definitions. I moved those to a separate function.

Finally, I deleted some objects in the trafficControl destructor in order to prevent memory leaks.

In the future, I'll need to add tet cases and error handling.

Saturday, 14 July 2018

TrafficControl: NetworkDesigner Class for Designing Networks

Before proceeding with the network designer, I need to plan where to add the new functionality,

One use case will be:
  1. User selects a complex but valid KML file.
  2. The python script trackStationConnector.py converts the file and displays the output on trafficNetworkDescriptionEdit.
  3. If there are issues seen, the program should inform the user
  4. If there are no blocking issues, the user can save the file and/or continue with using the train network that is now loaded.
Initially, I'll assume that the input from the user is correct. I'll add error checks later.



How it's done:
The button for selecting the KML file is associated with the action actionImportKmlFile. That action activates a slot in trafficControl that allows the user to select the KML file to import.


I have separated the code for selecting a file and the code that is opening the file for testability reasons. This way, I'll be able to open files in automated testing.
Here, the file path is hard coded. I need to have a generic default directory, such as the user's home folder.

Once the file is selected, it is opened from networkDesigner:
The program calls the python interpreter on the system, and parses the path to the python script as the first argument and the KML file as the second argument. This approach is very system-dependent and anyone that fetches my GIT repo must adapt this part. I need to fix tis issue in a later blog post.

All output from the python script is saved and parsed to trafficControl::updateTnmTextBox that is setting the text. It also parses the commands to networkControl so that the corresponding elements appears on the map. For testability, this needs to change so that the networkDesigner can communicate directly to networkControl using signals/slots.

Finally, the program saves the contents of the two text fields to one file.

This solution is hardcoded for my system. I need to make it more generic in future versions. Now, the program works only if puthon is installed on the computer, and the python path must be "c:/users/gusbra...".



Saturday, 30 June 2018

TrafficControl: NetworkDesigner - First Step

For this summer, I'll focus on making TrafficControl demonstrable. During the last year, I've made a lot of improvements on the program:
  • I made the map useful, with symbols of tracks, trains and stations.
  • I refined the user interface, adding dock widgets and toolbars.
  • I added test cases using Google Test
  • I presented the system load and the simulated time to the program
The process of creating the input files is still manual and complex:
  1. The user must create a KML file using Google Earth. Each track (polyline) must start and end at a station/junction (placemark). 
  2. The user must run a python script that extracts the coordinates for the tracks and stations and connects them.
  3. The user must save the output to a file (TNM - Traffic Network Map). 
  4. The user must append information about the trains to that file. (TNO - Traffic Network Operations). The combined file is named TNF -  Traffic Network File.
TNM is generated from the KML map. The TNF file is a combination of the TNM and TNO files. The user won't need to keep track of the different files.

I'll simplify and clarify this process in the program the new dockWidget Design Window. 

The first step is to add an action that will let the user specify which KML file to convert to TNM format. Once that file is selected, TrafficControl will call a Python script that converts the file to the TNM format. The result is presented in the left textbox.

Until the user has imported a file, there is an explaining text in the text field.
After the script has been run successfully, the information about the stations and tracks has been created.

Future work:
  • Add error checking of TNF file and add TNO handling. 
  • The solution is dependent on a Python installation on the users computer. Having an embedded Python interpreter would be better. This seems to be difficult to implement
  • The program is using some hard coded absolute paths to the script. I need to specify the file in relation to the program's working directory.
  • To be testable, the network designer must be independent from the user interface. I'll refactor the code and add signals/slots to communicate. For example, a signal will be trigged once the python script has converted the KML file to TNM format, so that the user inter face will be updated.

Saturday, 5 May 2018

TrafficControl: Refactoring and User Interface Fixes

After adding a toolBar for hiding/showing the different windows of the program, I need to review and refactor my code.

Before adding a widget that helps the user to create a network file, I need to review/refactor the code and fix some bugs. One of them is that when resizing the dockWidgets for tracks/trains/stations, those widgets reset their sizes immediately.

Another bug is that the buttons in the toolbar aren't changed when the windows are closed using the "x" button on the top right. Fix: Connect the signal visibilityChanged to the same slot as the buttons are connected to. In that slot, update both buttons and 


In this code review, I'll focus on variable names, documentation of functions and whether there is redundant code or not. I'll also omvestogate the need of new test cases.

Saturday, 28 April 2018

TrafficControl: Understanding Qt Sizepolicy and a Floating Map

Making Qt set the size of the dockWidgets has been a challenge for me. I truly need to understand the sizing policies to make the dockwidgets behave as I want.

The map dock widget should have a small minimum size and it should expand as much as possible when there is room. Achieving this when docked is difficult, will add complexity to my program and it will be of limited benefit for the end user. It seems that it is hard to resize dockWidgets in main window.

According to some blog posts, it is possible to re-implement sizeHint to control its behaviour. See also this blog post.

In this case, I'll simply make mapDockWidget floating so that the user can resize it later. The user can also dock the widget to the main window if required.



I've seen that the zoom and pan of the map is reset once the map is resized. I need to investigate that.

Saturday, 21 April 2018

TrafficControl: Adding Menu, Toolbar and Statusbar

After adding button icons to the user interface, I've moved them to a menu and a toolbar.



Adding buttons to a toolbar is quite straight forward. I added the spinbox to the toolbar in the source code.

I have disabled the button "step one second" when the simulation is running.
The text related to the action/button changes when the state changes between running and paused,
The messages (time and time consumption) were added to the statusbar from the source code too.

The next step will be to ad a second toolbar that will control the different widgets.


Saturday, 14 April 2018

TrafficControl: Adding Button Icons to the User Interface

The first button I added is replacing the "Continue with ticks" checkbox.

A QPushbutton was added and made checkable using setCheckable property, I connected the signal checked(bool) to a slot that is changing the icon of the button when it is clicked.

Left to right: Import Network button, Play/Pause and Step one second.

When the simulation is running, the play button is transformed to pause.
In the next blog posts. I'll add a menu, status bar and a tool bar to the program. Using that tool bar, the user should be able to hide and show the different dock widgets.

Icon Credits: https://icons8.com/

Sunday, 1 April 2018

TrafficControl: Displaying System Load

I aadded an indicator of the system load, defined as the time needed for simulating one second.

The time is measured in networkControl::stepTimeForNetwork as the time used between start and end of that function. The system load is calculated by dividing the computation time by the interval time.

For each tick, the results are shown in a textLabel on the user interface.

When running on my system (i5-8250, 8 GB RAM), three trains, the load is:

  • Debug version, PC in battery saving mode, average 5 ms, occasionally up to 15 ms.
  • Debug version, PC in performance mode, average 2 ms.
  • Release version, PC in battery saving mode, average 2 ms
  • Release version, PC in  performance mode, average 3 ms

This means that there is room for bigger networks in the program

Thursday, 15 March 2018

TrafficControl: Analysing TrafficClock

As I mentioned in the last blog post, there are some refactoring and UI fixes that I need to do for the trafficClock.

The first step is to analyze the behaviour of qThread and qTime. I want to know how accurate the timing is (does the thread wake up after the specified time) and how much time the train/track/station operations take. I've done this by adding some qDebug statements:


For an interval of 100 ms, the output is:


The time difference between "BEFORE EMIT" and "AFTER EMIT" is almost always 0 ms, so the estimated time consumption for emitting stepTimeSignal command can be neglected.

The time between "BEFORE SLEEP" and "WOKE UP" is between 1 ms up to 15 ms more than expected. This is a minor issue, but the target time tends to drift as the small delays accumulate. The solution to this behaviour is the following code change:

After printing the time at the start and end of tcNetworkControl::stepTimeForNetwork(), I am able to track the time consumption for three trains in a small network.
In this case, the interval is 100 ms.
The system configured for low performance and the build is debug.

In this case, the interval is 20 ms.
  The computer is set to have high performance and the build is release, the operations take 2-3 ms.
It seems that the time intervals are irregular.

In this case, the interval is 20 ms, but the operations take 30 ms.
The next signal is trigged as soon as the former is done.
The behaviour is expected. 
Finally, I tested without QML.
The time consumption is far below one millisecond. This means that QML will be critical for optimizations.
In the next blog post, I'll make three changes in the user interface:

  1. Display the simulated time
  2. Show the time needed for stepTimeForNetwork, as a percentage of the intervals
  3. Replace the two time spinboxes with one spinbox for fast forward factor and add a button that will toggle between "play" and "pause".


Further, I need to investigate if it is possible to make the QML operations run without affecting the performance of the train operations.

Thursday, 22 February 2018

TrafficControl: Crash for QQuickItem in QDockWidget after Update

After an update of Qt 5.10, TrafficControl crashes when it is setting up the QtQuick/QML widget in the user interface. The program worked fine before the update (2018-01-17) and I have isolated the issue by removing other parts of TrafficControl.

I suspect strongly that changes in Qt 5.10 has made my application crash. After installing Qt 5.9.3, the program works fine with that version.

If I remember, I'll post updates on this topic when there are news/fixes about this issue.

Saturday, 6 January 2018

TrafficControl: Locking Tracks for Trains - Tests and Visualization

After adding locks for tracks, I need to both visualize them and test them.

The tests are quite straight-forward, but they require some code.
  • The first test case, trainListAddRmUpstream verifies that is it possible to add two trains in upstream on the same track, with a distance of more than 3000 m between the trains. It also verifies that the first train is removed first.
  • The second tedst case, trainListAddDownstream verifies that a train that wants to enter a used track, where a train is moving in the opposite direction, will wait until the first train has left the track.
For the visualization, I'll change both model/view and the QML part.

Model/View:
A new column will be added for each track: Lock. The different states will be: Free, Start, End and Locked.

When something is changed for a track, the method Track::emitChangedSignal(trackID) is called. That method compiles a QStringList that contains the messages that shall be sent to the datamodel. The lock status is added as a string to the string list that is sent to the track data model. Also, a column is added for the data model.

Three tracks are locked. For example, dHja_Lun_E is locked at the end station (LundC). This means that trains can enter at Hjarup but not at LundC.
The next blog post will describe how I added the locks in the map.

Saturday, 28 October 2017

TrafficControl: Protecting the Code With Asserts

Asserts are checking whether a condition is met. If the condition isn't met, the program will throw an error message and break. They are used to ensure that something never ever happens in the program (asserts are only used when debug flag is enabled).

TrafficControl is based on three lists of objects (trainList, trackList and stationList), The elements in those lists are called directly, so the entire logic of the program is based on a direct relation between the ID's of the elements and their position in the lists. Removing elements will not be allowed.

For example: Add three trains to the network. The result is a trainList:
trainList.at(0) has ID: 0
trainList.at(1) has ID: 1
trainList.at(2) has ID: 2
totalNbrOfTrains = 3

Remove the second train and reduce the totalNbrOfTrains:
trainList.at(0) has ID: 0
trainList.at(1) has ID: 2 
totalNbrOfTrains = 2

Now, there is a mismatch between the ID for the second train in the trainList and its position. 

To detect when it happens, I've added an assert that fires if there is such a mismatch for the last items in the lists.
The trainList must always be empty, or its last element' ID must match the length of the list.
If the assert fails, a dialog pops up and the user can decide whether to retry, abort or ignore the error.
The button are using the language specified by the operating system. In this case, they are: Abort, Retry and Ignore.
As a side note, I've discovered two bugs that I need to fix. I'll address the issues in future blog posts:

The program asserts when using a network without coordinates. It happens when the train is running on tracks and is trying to calculate its coordinates.

Solution: Check if the train and track has coordinates when updating the position for a train on the track.

The program is slower when importing network file. Before refactoring it, it took the program 5 seconds to scan the file. Now, it takes 25 seconds.

Solution: Scan all lines and send as a stringlist to new NetworkControl::parseNetworkCommands(QStringList). 

Saturday, 14 October 2017

TrafficControl: Using Test Fixtures for Unit Tests

For many unit tests, there are some similarities that can be used to reduce some redundant code.

For example, the datamodels for the trafficDataModels (for train, station and track), a pointer to the QML part of the program and the networkControl object.

Google Test offers test fixtures, that makes it possible to setup the same parameters for a set of tests. The test code will be cleaner and each test case will have (more or less) the same setup.


A test fixture is a class where some pointers to some objects are defined.

In SetUp, the data models are defined, a null pointer is set for the QML part and a networkControl object is created.

The TearDown method will free up the allocated memory and clean up the test environment.


The test case is then defined using TEST_F, where the appendix indicates that a test fixture will be used. The test case is networkTest, that was defined above.

In the test case, the networkControl, the needed elements are already defined. The cleanup will be done automatically by TearDown when the test is done.

The concept isn't water-proof, however. The traffic elements (for example trains) has a static int variable to count the total number of trains/stations/tracks. I didn't reset that parameter initially, which caused some test cases to fail. 
  1. Before the first test with the test fixture, that parameter was 0. 
  2. After the first test case, where a train was created, the parameter was set to 1. 
  3. When deleting the train object (called in the networkControl destructor), the static parameter wasn't affected.
  4. In the second test case, the totalNbrOfTrains was 1, but it should have been 0. This caused the test to fail since the test checked the totalNbrOfTrains parameter. 

To fix that issue, the totalNbrOfTrains is set to 0 in the destructor for networkControl.

Saturday, 30 September 2017

TrafficControl: Refactoring trafficControl Class

This week-end, I've refactored the code to make it more testable and more agnostic.

The class trafficControl contains currently both GUI elements and logic for defining the traffic networks. Having both user interface and program logic in the same class will make automated testing difficult, and I need to refactor the code so that the user interface is separated from the logic.

The current structure of the program is:
  • main.cpp includes 
    • trafficControl.h, which includes
      • ui_trafficControl.h (this is autogenerated at run-time which causes problems when unit testing)
      • tcTrack
      • tcTrain
      • tcStation
      • trafficDataModel
      • trafficClock
I want to have a slightly different structure where the ui and the traffic logic is separated.
  • main.cpp includes
    • trafficControl which includes
      • ui_trafficControl.h (autogenerated)
      • tcNetworkControl which includes
        • tcTrack
        • tcTrain
        • tcStation
        • trafficDataModel
        • trafficClock
Steps taken for refactoring:
1. Remove ui references in tcTrack/tcTrain/tcStation/trafficDataModel/TrafficClock
2. Create tcNetworkControl files
3. Initiate tcNetworkControl
3. Move everything except ui elements to tcNetworkControl and update the references to it.
4 Copy reference to station/train/trackListModel to tcNetworkControl and also handleQMLObject
5. Ensure that the program works both when testing and in normal mode.

I also made some changes to the code that imports a file with network commands. Previously, that code opened a file, scanned it for all lines and interpreted them to commands. The new code scans a file, and parses the lines to another method in networkControl. With this change, I'll be able to define the network from other sources, such as test case and user input at runtime.

Monday, 25 September 2017

TrafficControl: Integrating Google Test Into TrafficControl

After some struggle, I was finally successful with integrating Google Test into TrafficControl.

The steps are similar to the QtTest Integration blog post and the previous blog post:

I add two files to the "test" folder:
  • mainTest.cpp that is calling the test code and
  •  tst_tcUnitTests.h, where I have the test code.
In the pro file, I replace the QtTest directives with Google Test directives:

The mainTest.cpp is identical to the one in the Google Test example. The results can be seen below.

With Google Test, it is easy to sort test cases into different categories. That will be necessary as the number of test cases grow.

In the near future, I'll port the existing QtTest test cases to Google Test and implement the other test cases. I'll keep the existing QtTest files for now, even if I won't use them.