Recreating a 7 segment display with Adafruit GFX & SSD1306 OLED

Git Repository –
7-seg_adagfx_ssd1306.git

I had a project come up recently where I needed to display some numbers that looked like an old 7-segment display. The only problem, I was using a 128×64 OLED driven by a SSD1306 driver. Luckily this display is a lot more capable than the vacuum fluorescent and 7-segment displays of the past, we should be able to overcome this dilemma with some simple code.

First things first before we can write any code, we first have to understand the basic operation of a 7-segment display. Lets start by looking at why they call it a 7-segment display.

It’s pretty obvious when you look at the diagram above where the name came from, any digit 0-9 can be shown by activating the right combination of 7 segments. That’s it, pretty simple right! Now how can we do this pragmatically?

One easy solution is to use something called a truth table, to store our truth table we will need to create a multi-dimensional array. Multi-dimensional arrays may sound scary if you have never used one, however if you are familiar with basic arrays already then you can think of them simply as an array of arrays.

Arduino Reference – Arrays

We will define our array as a const integer since it will never change, we will need 10 unique arrays to store 0-9 values with 7 positions in each array, one for each segment.

// define segment truth table for each digit
static const int digit_array[10][7] = {
  {1, 1, 1, 1, 1, 1, 0},  // 0
  {0, 1, 1, 0, 0, 0, 0},  // 1
  {1, 1, 0, 1, 1, 0, 1},  // 2
  {1, 1, 1, 1, 0, 0, 1},  // 3
  {0, 1, 1, 0, 0, 1, 1},  // 4
  {1, 0, 1, 1, 0, 1, 1},  // 5
  {1, 0, 1, 1, 1, 1, 1},  // 6
  {1, 1, 1, 0, 0, 0, 0},  // 7
  {1, 1, 1, 1, 1, 1, 1},  // 8
  {1, 1, 1, 0, 0, 1, 1}   // 9
};



Lets take a look at the function render_digit() from the example code.

Parameters: render_digit(x_pos, y_pos, digit, color)

The default starting position is x0,y0 which will render in the top left corner of the screen, to add an offset use X_pos/Y_pos. We use a for loop to run through our truth table and check which segments need to be active for the currently selected (digit). We then use a switch case to draw a rectangle to display any active segments. Note this will only display a single digit if called, to display multiple digits check the display_digits() function!

void render_digit(uint8_t pos_x, uint8_t pos_y,
                  uint8_t digit, uint8_t color) {
  // loop through 7 segments
  for (uint8_t i = 0; i < 7; i++) {
    bool seg_on = digit_array[digit][i];
    // if seg_on is true draw segment
    if (seg_on) {
      switch (i) {
        case 0:
          display.fillRoundRect(2 + pos_x, 0 + pos_y, 9, 3, 2, color); // SEG a
          break;
        case 1:
          display.fillRoundRect(10 + pos_x, 2 + pos_y, 3, 9, 2, color); // SEG b
          break;
        case 2:
          display.fillRoundRect(10 + pos_x, 12 + pos_y, 3, 9, 2, color); // SEG c
          break;
        case 3:
          display.fillRoundRect(2 + pos_x, 20 + pos_y, 9, 3, 2, color); // SEG d
          break;
        case 4:
          display.fillRoundRect(0 + pos_x, 12 + pos_y, 3, 9, 2, color); // SEG e
          break;
        case 5:
          display.fillRoundRect(0 + pos_x, 2 + pos_y, 3, 9, 2, color); // SEG f
          break;
        case 6:
          display.fillRoundRect(2 + pos_x, 10 + pos_y, 9, 3, 2, color); // SEG g
          break;
      }
      seg_on = false;
    }
  }
}

So now that we have a grasp on rendering a single digit lets try rendering multiple! We will use the modulo operation to pull each digit from an integer and place each into an array. Then we render that digit in the appropriate location based on the starting position and the character spacing defined.

void render_digits(uint8_t pos_x, uint8_t pos_y,
                   uint8_t spacing_x, uint16_t digit, uint8_t color) {
  uint8_t digit_array[] = {digit / 100 % 10, digit / 10 % 10, digit % 10};
  if (digit > 99) {
    render_digit(pos_x, pos_y, digit_array[0], color);
  }
  if (digit > 9) {
    render_digit(pos_x + spacing_x, pos_y, digit_array[1], color);
  }
  render_digit(pos_x + (spacing_x * 2), pos_y, digit_array[2], color);
}


That’s it, pretty simple right? Fun fact this code could be modified and used to drive a real 7 segment display too, all that would be needed is to configure 7 GPIO pins and toggle them in the render_digit() switch case.

Arduino code!

Adafruit Libraries:
Git-hub Adafruit-GFX Library
Git-hub Adafruit-SSD1306 Library

// EXAMPLE 7 SEGMENT DISPLAY RENDERING FOR ADAFRUIT_GFX & SSD1306
// Copyright 2018 T&A Laboratories LLC
// writen by: Aaron Williams on 9/7/2018
//
// Do not distribute without permission of the author
// !!!!!!!

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define OLED_RESET 11
Adafruit_SSD1306 display(OLED_RESET);

// define segment truth table for each digit
static const int digit_array[10][7] = {
  {1, 1, 1, 1, 1, 1, 0},  // 0
  {0, 1, 1, 0, 0, 0, 0},  // 1
  {1, 1, 0, 1, 1, 0, 1},  // 2
  {1, 1, 1, 1, 0, 0, 1},  // 3
  {0, 1, 1, 0, 0, 1, 1},  // 4
  {1, 0, 1, 1, 0, 1, 1},  // 5
  {1, 0, 1, 1, 1, 1, 1},  // 6
  {1, 1, 1, 0, 0, 0, 0},  // 7
  {1, 1, 1, 1, 1, 1, 1},  // 8
  {1, 1, 1, 0, 0, 1, 1}   // 9
};

void setup()   {
  Serial.begin(9600);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3D (for the 128x64)
  display.clearDisplay();   // Clear the buffer.
  // func params: render_digits(pox_x, pos_y, spacing, color)
  render_digits(0, 0, 18, 999, WHITE);
  display.display();
  delay(2000);
}

void loop() {
  // count up to 999
  for (int cnt = 0; cnt < 1000; cnt++) {
    display.clearDisplay();   // Clear the buffer.
    render_digits(0, 0, 18, cnt, WHITE);
    display.display();
  }
}

// RENDER DIGITS:
// use this funtion to print digits
// valid range range: 0-999
void render_digits(uint8_t pos_x, uint8_t pos_y,
                   uint8_t spacing_x, uint16_t digit, uint8_t color) {
  uint8_t digit_array[] = {digit / 100 % 10, digit / 10 % 10, digit % 10};
  if (digit > 99) {
    render_digit(pos_x, pos_y, digit_array[0], color);
  }
  if (digit > 9) {
    render_digit(pos_x + spacing_x, pos_y, digit_array[1], color);
  }
  render_digit(pos_x + (spacing_x * 2), pos_y, digit_array[2], color);
}

// RENDER DIGIT
// don't use this unless you only need a single digit
// use render_digits() func above instead
void render_digit(uint8_t pos_x, uint8_t pos_y,
                  uint8_t digit, uint8_t color) {
  // loop through 7 segments
  for (uint8_t i = 0; i < 7; i++) {
    bool seg_on = digit_array[digit][i];
    // if seg_on is true draw segment
    if (seg_on) {
      switch (i) {
        case 0:
          display.fillRoundRect(2 + pos_x, 0 + pos_y, 9, 3, 2, color); // SEG a
          break;
        case 1:
          display.fillRoundRect(10 + pos_x, 2 + pos_y, 3, 9, 2, color); // SEG b
          break;
        case 2:
          display.fillRoundRect(10 + pos_x, 12 + pos_y, 3, 9, 2, color); // SEG c
          break;
        case 3:
          display.fillRoundRect(2 + pos_x, 20 + pos_y, 9, 3, 2, color); // SEG d
          break;
        case 4:
          display.fillRoundRect(0 + pos_x, 12 + pos_y, 3, 9, 2, color); // SEG e
          break;
        case 5:
          display.fillRoundRect(0 + pos_x, 2 + pos_y, 3, 9, 2, color); // SEG f
          break;
        case 6:
          display.fillRoundRect(2 + pos_x, 10 + pos_y, 9, 3, 2, color); // SEG g
          break;
      }
      seg_on = false;
    }
  }
}

Android app MySQL connectivity with PHP and JSON

In this blog post we will discuss adding database connectivity to your Android application. We will look at how to make a HTTP POST request from your android application to a server side PHP script, we will also take a look at how to parse the returned JSON encoded results using Volley.

The following tools will be used for this tutorial:

  • cPanel
  • Android Studio
  • MySQL
  • PHP


    Let’s get started by configuring a new test database in cPanel, find the Databases tab on the menu.

    Then use the MySQL Database Wizard to configure a new database called “testdb”.

    Create a user for your new database.

    Set user privileges, select “ALL PRIVILEGES” to select all.

    If all went well you should have a database called “testdb” now setup on your hosted server.

    Now we will use phpMyAdmin to create a table with some information in our blank database. Let’s get started by making a new table named “test_table” with 2 columns in it, one for an auto increment unique ID and one for some data.

    We will name our first column “id” and give it an auto increment INT value for an index. Our second column will be named “cust_name” and will be assigned a type of VARCHAR and a length of 45.

    (Note the picture shows INT for the 2nd data type it should show VARCHAR)

    Make sure to set the id field as the primary index key!

    Once everything is set up you should have a functional MySQL database, now let’s write a simple PHP script to interact with it.

    <?php
     
    // Create connection
    $con=mysqli_connect("localhost","admin","12345678","testdb");
     
    // Check connection
    if (mysqli_connect_errno())
    {
      echo "Failed to connect to MySQL: " . mysqli_connect_error();
    }
     
    // Define input variable(s)
    $name = $_REQUEST["custname"];
    //$somename = $_REQUEST["someinput"];

    // This SQL statement selects ALL from the table 'test_table'
    $sql = "SELECT * FROM test_table WHERE cust_name = '$name'";
     
    // check result
    if ($result = mysqli_query($con, $sql)){
        $rowcount = mysqli_num_rows($result);

                // if no results add entry
            if ($rowcount == 0) {
                $sql = "INSERT INTO test_table (cust_name)
                SELECT * FROM (SELECT '$name') AS tmp
                WHERE NOT EXISTS (SELECT cust_name FROM test_table WHERE cust_name = '$name') LIMIT 1"
    ;
            }
        }  
     
    // Check if there are results
    if ($result = mysqli_query($con, $sql))
    {    
        while($row = $result->fetch_object())
        {
        echo json_encode($row);
        }
     
    }

    This PHP script handles database queries on the server end. It also handles authentication with the database and like any other type of server side script, its source code will remain hidden from the end user. We use the customer name from the HTTP request as a variable, the php script will then check the database to see if any rows match its input. If the row count returns 0 a new database entry will be created, if the row count returns 1 then the entry already exists and the values will be returned. Notice we put a limit of one entry per cust_name, this won’t allow us to add a duplicate record to the table, so for example we could not have 2 of any specific name.

    By sending a HTTP request in any browser we should see an echo from the script if things are working right. Remember the first request will not produce a response since the database doesn’t contain anything yet, so once you send a new name you will have to refresh the page and send the request again to get the results.

    http://www.yourdomain.com/test.php?custname=anyname

    A quick check in phpMyAdmin shows the database entry was created successfully!

    Now we have to have our app send a database request and parse the results, let’s start a new Android Studio project.

    Select empty activity.

    Leave the default name of MainActivity

    We will need to add Volley to our app dependency list in the app gradle build file, we can do that by adding this line.

    implementation 'com.android.volley:volley:1.0.0'

    We also have to add permission in the Manifest.xml for internet access.

    <uses-permission android:name="android.permission.INTERNET" />
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.ta_labsllc.mysql">

        <uses-permission android:name="android.permission.INTERNET" />

        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity android:name=".MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />

                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>

    </manifest>

    Setup an EditText and InputText fields as well as a Button for the send action in the activity_main.xml layout.

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
        <EditText
            android:id="@+id/editText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="84dp"
            android:layout_marginLeft="85dp"
            android:layout_marginRight="84dp"
            android:layout_marginStart="85dp"
            android:layout_marginTop="16dp"
            android:ems="10"
            android:gravity="center_horizontal"
            android:inputType="textPersonName"
            android:text="@string/enter_name"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/sendButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="148dp"
            android:layout_marginLeft="148dp"
            android:layout_marginRight="148dp"
            android:layout_marginStart="148dp"
            android:layout_marginTop="16dp"
            android:text="@string/send"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/resultText" />

        <TextView
            android:id="@+id/resultText"
            android:layout_width="185dp"
            android:layout_height="42dp"
            android:layout_marginEnd="99dp"
            android:layout_marginLeft="100dp"
            android:layout_marginRight="99dp"
            android:layout_marginStart="100dp"
            android:layout_marginTop="16dp"
            android:gravity="center_vertical|center_horizontal"
            android:text="@string/result"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/editText" />

    </android.support.constraint.ConstraintLayout>

    In the main activity we will setup a method called checkDatabase(), we will set up an onClickListener for the send button to call this method anytime the button is pushed. The checkDatabase() method will form a HTTP POST request using params.put() and the string from the inputText field. The request will be sent to the server where the server side php script will process it and make the actual database query. Once finished the php script encodes the results in a JSON format and sends back a reply. When the response listener receives that reply it is parsed using jObject.getString() and the individual row values are accessible.
    MainActivity.java

    package com.ta_labsllc.mysql;

    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.TextView;
    import com.android.volley.Request;
    import com.android.volley.RequestQueue;
    import com.android.volley.Response;
    import com.android.volley.VolleyError;
    import com.android.volley.toolbox.StringRequest;
    import com.android.volley.toolbox.Volley;
    import org.json.JSONException;
    import org.json.JSONObject;
    import java.util.HashMap;
    import java.util.Map;


    public class MainActivity extends AppCompatActivity {

        EditText inputText;
        TextView outputText;
        Button sendBtn;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            inputText = findViewById(R.id.editText);
            outputText = findViewById(R.id.resultText);
            sendBtn = findViewById(R.id.sendButton);
            sendBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    checkDatabase();
                }
            });
        }

        public void checkDatabase() {

            RequestQueue queue = Volley.newRequestQueue(this);

            final String url = "http://yourIPhere.com/test.php"; // location of php script

            // send HTTP POST request
            StringRequest postRequest = new StringRequest(Request.Method.POST, url,
                    new Response.Listener<String>() {
                        @Override
                        public void onResponse(String response) {
                            // On response
                            Log.d("PHP Response", response);
                            try {
                                JSONObject jObject = new JSONObject(response);
                                // Pulling items from the array
                                String id = jObject.getString("id"); // parse id
                                String custName = jObject.getString("cust_name"); // parse name
                                Log.d("JSON ID", id);
                                Log.d("JSON CUST_NAME", custName);
                                String output = ("Results: " + id + "|" + custName);
                                outputText.setText(output);

                            } catch (JSONException e) {
                                // error
                                Log.d("JSON PARSE", "ERROR");
                            }
                        }
                    },
                    new Response.ErrorListener()
                    {
                        @Override
                        public void onErrorResponse(VolleyError error) {
                            // error
                            Log.d("PHP Error.Response", "error");
                        }
                    }
            ) {
                @Override
                protected Map<String, String> getParams()
                {
                    // encode inputText
                    String input = inputText.getText().toString();
                    Log.d("User Input", input);
                    Map<String, String>  params = new HashMap<String, String>();
                    params.put("custname", input);
                  //params.put("somerow", "input");
                    return params;
                }
            };
            queue.add(postRequest);
            }

        }

    When everything is up and working you should be able to send a name to the database, if the entry exists already it will be returned with its unique id, if it is not it will be added and returned on the next request.

    Thanks for checking out this tutorial on using MySQL with Android!

  • LOREX LNZ4001 PTZ ipcam control with Raspberry Pi

    Controlling and streaming a LOREX PTZ ipcam using Raspberry pi with Flask, Motion and AJAX.

    You will need

    • 1 PTZ network cam
    • 1 Raspberry pi
    • 1 USB Wi-Fi dongle
    • 1 Cat5 Ethernet cord

    The principle of this tutorial should remain the same for different ipcam manufacturers however the commands used may change. I used wireshark to sniff network traffic as I made requests from a browser to the ipcameras built in webserver.
    https://www.wireshark.org/download.html

    Commands like this may change…

    http://192.168.1.xxx/cgi-bin/operator/ptzset?move=upleft&move=repeat
    
    

    Network Layout

    First we need to set the ip of the camera to static through its built in web interface follow manufactures instructions for this. We will use the address 192.168.2.9

    Now in the network interfaces config, the USB wifi dongle will be assigned a static IP of 192.168.1.10 and the pi’s ethernet port will go to 192.168.2.1

    $ sudo nano /etc/network/interfaces
    
    

    This is what my /etc/network/interfaces looks like.

    auto lo
    iface lo inet loopback

    auto eth0
    allow-hotplug eth0
    iface eth0 inet static
    address 192.168.2.1
    netmask 255.255.255.0
    network 192.168.2.1
    broadcast 192.168.2.255

    auto wlan0
    allow-hotplug wlan0
    iface wlan0 inet manual
    wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
    iface wlan0 inet static
    address 192.168.1.10
    netmask 255.255.255.0
    network 192.168.1.0
    broadcast 192.168.1.255
    gateway 192.168.1.1

    Next we need to install motion https://github.com/Motion-Project/motion

    $ sudo apt-get install motion
    
    

    Change this in the /etc/motion/motion.conf

    $ sudo nano /etc/motion/motion.conf
    • daemon on
    • stream_localhost off
    • netcam_url value http://192.168.2.9/video.mjpg (change to the path of your cams video stream)
    • netcam_userpass user:pass (username:password)

    Install Flask http://flask.pocoo.org/

    $ sudo apt-get install python-pip
    $ sudo pip install Flask
    
    

    Remove ifplugd so the pi doesn’t disable wifi when the ethernet jack is plugged in https://github.com/BillTompkins/pi-spiexpansion/wiki/Remove-ifplugd

    $ sudo apt-get remove ifplugd 
    
    

    Allow IP forwarding

    $ sudo nano /etc/sysctl.conf
    
    
    • Find and uncomment –  net.ipv4.ip_forward=1

    Run this command so we dont have to reboot.

    $ sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
    
    

    Modify IP tables and NAT to allow traffic from wlan to ethernet.

    $ sudo iptables -t nat -A POSTROUTING -o wlan0 -j MASQURADE
    
    
    $ sudo iptables -A FORWARD -i wlan0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT
    
    
    $ sudo iptables -A FORWARD -i eth0 -o wlan0 -j ACCEPT
    
    

    Don’t know if this is 100% necessary but I will include it anyways.

    $ sudo apt-get install dnsmasq
    
    
    $ sudo nano /etc/dnsmasq.conf
    
    
    interface=eth0      # Use interface eth0
    listen-address=192.168.2.1 # listen on
    # Bind to the interface to make sure we aren't sending things
    # elsewhere
    bind-interfaces
    server=8.8.8.8       # Forward DNS requests to Google DNS
    domain-needed        # Don't forward short names
    # Never forward addresses in the non-routed address spaces.
    bogus-priv
    # Assign IP addresses between 192.168.2.2 and 192.168.2.100 with a
    # 12 hour lease time
    dhcp-range=192.168.2.2,192.168.2.100,12h

    Now that all the back end should be up and working lets write some code.

    First create a new folder

    $ sudo mkdir ipcam_control
    
    

    Navigate into that folder

    $ cd ipcam_control
    
    

    Create a new Python script

    ~/ipcam_control $ sudo nano webcam.py
    
    

    Copy and paste this code into the webcam.py file
    Change the ip address and command of the get request to match your camera, admin:admin is default username and password for the LOREX.
    ctrl+O to save ctrl+X to exit back to the command line.

    from flask import Flask
    from flask import render_template, request
    from requests.auth import HTTPBasicAuth
    import requests
    app = Flask(__name__)
    print &quot;Done&quot;

    @app.route(&quot;/&quot;)
    def index():
        return render_template('index.html')

    #PTZcam
    @app.route('/ptzUpleft')
    def ptzUpleft():
        requests.get('http://192.168.2.9/cgi-bin/operator/ptzset?move=upleft&amp;amp;move=repeat', auth=HTTPBasicAuth('admin', 'admin'))
        return 'true'

    if __name__ == &quot;__main__&quot;:
     print &quot;Start&quot;
     app.run(host='0.0.0.0',port=8000)

    Now create a new sub directory called templates, flask looks for the index.html file in this folder.

    $ sudo mkdir templates
    
    

    Change to templates directory

     ~/ipcam_control $ cd templates
    
    

    Create new html file.

    $ sudo nano index.html
    
    

    Copy and paste this code into index.html

    <div
    style="-moz-user-select: none; -webkit-user-select: none; -ms-user-select:none; user-select:none;-o-user-select:none;"
    unselectable="on"
    onselectstart="return false;"
    onmousedown="return false;">


    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>

    <br>PTZ CONTORLS</br>

    <img src="http://yourip:8081>

    <table style="width: 306px;">
    <tbody>
    <tr>
    <td style="text-align: right"><button href="#" id="ptzLF" style="font-size: 20px; text-decoration: none;">&#8598;</button></td>
    </tr>
    </tbody>
    </table>
    </div>
     
    <script>

    $( document ).ready(function(){
        $("#ptzRR").on("mousedown", function() {
        $.get('/ptzRR');
        }).on('mouseup', function() {
        $.get('/ptzStop');
        });
    });

    </script>

    Reboot the pi for good measure

    $ sudo reboot
    
    

    Now when the command line is available again navigate back to your project folder created earlier.

    $ cd ipcam_control
    
    

    Now launch your application

    ~/ipcam_control $ sudo python webcam.py
    
    

    Once that is up and running open a browser on any computer or device connected to the 192.168.1.x network, go to the pi’s IP address and port 8000, for this tutorial we use http://192.168.1.10:8000. You should see a video stream and a single button linked to command the camera, similar to the ones pictured below.

    Thanks for reading!