Tuesday, May 24, 2016

SHT11 + Particle Photon (running matrixSSL) + InitialState.com


Now days microcontroller platform for IoT devices are becoming more and more powerful so much so that they can now sport an HTTPS stack and hence send telemetry to an HTTPS capable data broker like initialstate.com. Which means no need for any linux based gateway/hub - you can do away with your Raspberry Pi as a go between your microcontroller node and the internet.

You ask why would some one want to do that? Simple - lesser the number of components in your system, lesser the points of failure.

So, I wanted to explore if it was possible to use a Particle Photon to send data directly to initialstate.com and recently it just became possible when a few good folks ported an SSL library to Spark Photon.
My Particle Photon with SHT11



Steps:
  1. Get a Particle Photon and connect it to SHT11. SHT11 is a digital temperature and humidity sensor. It works at 3.3V logic levels and can be powered by the photon itself. The photon will draw power from any nearby device (computer, smartphone charger or router) which has a spare USB port. Here are the connections:
    Connection schematic
  2. Install the Particle app on your android phone and use it to create an account for yourself and link your particle photon to your home WiFi network.
  3. Get an initialstate.com account and create a bucket. Make sure to note down the two API keys (Access key and Bucket Key)
    Noting down your initialstate.com access keys
  4. Now open https://build.particle.io and login with your particle account. Create a new App. add two libraries to it: HTTPSCLIENT-PARTICLE and SHT1X
  5. Write the following code into the .ino file that has been created by default. Make sure to modify the code so that it has your own initialstate.com access keys.

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    #include "SHT1x/SHT1x.h"
    #include "httpsclient-particle/httpsclient-particle.h"
    
    
    
    //Initial State.com and HTTPS configuration START---------------->
    #define INITIALSTATE_ACCESS_KEY "243434gdfsgt43f43rfwp3"
    #define INITIALSTATE_BUCKET_KEY "GHDER34RF3NE"
    
    const bool g_https_trace = false;  // This controls debug info print to Serial
    const char host [] = "groker.initialstate.com";
    //const char ad_endpoint [] = "/api/events";
    const char se_endpoint [] = "/api/events";
    const int g_port = 443; // Don't change this, unless you know what you are doing
    static unsigned int freemem;
    bool g_https_complete;
    uint32 g_bytes_received;
    
    TCPClient client;
    
    // Replace XXXX...XXX with base64 encoding if your gf username:password
    // If you don't know how to generate the base64 encoding go here:
    //    http://www.tuxgraphics.org/toolbox/base64-javascript.html
    // CAUTION: Do NOT remove/replace the word Basic from the string above,
    //          it's part of http standard.
    unsigned char httpRequestContent[] = "POST %s HTTP/1.0\r\n"
     // "User-Agent: curl/7.38.0 \r\n"
      "User-Agent: MatrixSSL/" MATRIXSSL_VERSION "\r\n"
      "Host: groker.initialstate.com\r\n"
      "Accept: */*\r\n"
      "Content-Type: application/json\r\n"
      "X-IS-AccessKey: " INITIALSTATE_ACCESS_KEY "\r\n"
      "X-IS-BucketKey: " INITIALSTATE_BUCKET_KEY "\r\n"
      "Accept-Version: 0.0.4\r\n"
      "Content-Length: %d\r\n\r\n%s";
    
    //<----------------Initial State.com and HTTPS configuration END
    
    
    //SHT11 Temperature and Humidity Sensor START---------------->
    // Channel 1 is Temperature (deg C)
    // Channel 2 is Humidity (%)
    
    #define dataPin  D0
    #define clockPin D1
    
    SHT1x sht1x(dataPin, clockPin);
    //<----------------SHT11 Temperature and Humidity Sensor END
    
    
    void setup() {
      if (g_https_trace) {
        Serial.begin(9600);
      }
      httpsclientSetup(host, se_endpoint);
    }
    
    
    unsigned int nextTime = 0;    // Next time to contact the server
    int g_connected;
    char jsonBufARR[300] = {0};
        
    void loop() {
        String jsonBuf;
        unsigned int t = millis();
        if (nextTime > t) return;
    
        float temperature = sht1x.readTemperatureC();
        float humidity = sht1x.readHumidity();
        jsonBuf = "[";
        jsonBuf += "{";
        jsonBuf += " \"key\": \"Inside_Temperature\",";
        String Temperature_C(temperature, 4);
        jsonBuf += " \"value\": \"" + Temperature_C + "\"";
        jsonBuf += " },";
        jsonBuf += " {";
        jsonBuf += "\"key\": \"Inside_Humidity\",";
        String Humidity_Percent(humidity, 4);
        jsonBuf += " \"value\": \"" + Humidity_Percent + "\"";
        jsonBuf += " }";
        jsonBuf += "]";
        jsonBuf.toCharArray(jsonBufARR, 300);
        
    
        g_connected = client.connect(host, g_port);
        if (!g_connected) {
            client.stop();
            // If TCP Client can't connect to host, exit here.
            return;
        }
        g_https_complete = false;
        g_bytes_received = 0;
        int rc;
        httpsclientSetPath(se_endpoint);
        if ((rc = httpsClientConnection(httpRequestContent, jsonBuf.length(), jsonBufARR)) < 0) {
            // Failure
            if (g_https_trace) {
                Serial.print("httpsClientConnection Returned:\n");
                Serial.println(rc);
            }
            httpsclientCleanUp();
            return;
        } else {
            if (g_https_trace) {
                Serial.println("HTTPS Transaction Successful");
            }
            
        }
        
        client.stop();
        if (g_https_trace) {
            Serial.println("<<<<----------------------------------------New HTTPS Transaction ENDS\n\n");
        }
        nextTime = millis() + 40000;
    }
    

    This is how your Web IDE should look like.
    Note the place where you need to add your own initialstate.com Access keys.

  6. Download the firmware (using the Web IDE - over the air) into your photon and wait for a few seconds then open your initialstate.com tiles view, you note the data being uploaded every 40 seconds.
Here is what my lines app view on initialstate.com looks like, the "Inside_Temperature" and "Inside_Humidity" show the data sent by my Particle Photon to my Home bucket which also includes streams from various other IoT devices that I have installed in my home.


My Particle Photon and SHT11 is installed inside my house.
This is the lines app view showing temperature and humidity data graphed over the past two month

This lines app view shows only the "Inside_Temperature" stream.
I have been out on vacation and my house has been unoccupied since 5/7/2016.
Note how the temperature curve over latest 2 weeks is a smooth wave with
no dips and downward spike - probably a dynamic state of equilibrium established since
there is no one entering or leaving the house and the doors and windows are closed!


Notes:
  1. For background on how hubs are used to do HTTPS/TLS, read Your Very Own Raspberry Pi Smart Home Hub
  2. Webhooks when used with your Particle devices can bypass the use of Raspberry Pi kinda gateway/hub as well - AND THAT WOULD DEFINITELY BE A BETTER WAY to send data to initialstate.com than the one I have specified above because of one main reason - HTTPS stack uses up precious code space on your particle microcontroller which is not ideal, you need that re
  3. Arduino Yun has a microcontroller and a microprocessor (running Linux) on it. You can use that to send DHT11 readings to initialstate.com as well.
  4. I tested the above code on Particle Cores - The web IDE threw compilation errors. In case of Particle Core, you can use webhooks like so.
  5. Forum post regarding matrixSSL ported to Particle Photon by glowfi.sh team is here
  6. Github repository for https library for Particle Photon is here.

Friday, May 20, 2016

Configuring your OpenWRT router to use ExpressVPN to provide unrestricted internet access to all your devices

Problem statement:
I am in China. I have subscribed to ExpressVPN access to check my gmail and facebook. I have a pocket router (TL-MR3020). How can I use that VPN for all my devices (laptop, mobile, tab, Android TV box). TL-MR3020 can run OpenWrt. It has a USB port, an Ethernet port and 802.11bg WiFi

Here are the steps. They involve downloading packages to your OpenWrt router. OpenWrt repositories are blocked in China as well. So complete these steps in your home country before you fly to China. (If you are already in China, you can install the ExpressVPN client on your laptop, and share that VPN Connection with your OpenWrt Router. Refer to this blog post on steps on how to do that)

  1. Install OpenWrt on your TL-MR3020 router. (Refer to this blog post on how to do that - the latest version is a bit big and requires an external flash drive for extra memory size)
  2. Connect your OpenWrt router to unrestricted internet. Make sure you can access the router's console over SSH or serial (using PuTTY)
  3. Install OpenVPN packages on your TL-MR3020, issue the following commands using the console:
    # opkg update
    # opkg install openvpn-openssl luci-app-openvp ca-certificates
  4. Reboot the router.
  5. After this point, your router does not neet unrestricted internet access, you can now connect the TL-MR3020 router to the restricted network - just make sure your laptop and your router are on the same network. In my case I connected my TL-MR3020 to my set-top box which has a builtin WiFi router. The set top box has been provided by my Chinese ISP.
    My TL-MR3020 connected to my Chinese ISP's set top box
  6. Using a web browser, login to your ExpressVPN account and download the OpenVPN configuration file corresponding to the VPN server that you want to connect to. These .ovpn files are specific to your account and do not require modifications (like editing password or login). They will work as long as you keep renewing your subscription.

    I downloaded "my_expressvpn_uk_-_berkshire_-_2_udp.ovpn" for my use
    Downloading the .ovpn files
  7. Use WinSCP  to connect to your router and upload this .ovpn file to /etc/openvpn folder. You can upload more .ovpn files in case you think that you will need to switch servers in the future.
    uploading the .ovpn files to TL-MR3020 using WinSCP
  8. Configure the various files using nano editor over PuTTY SSH:

    /etc/config/wireless
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    config wifi-device 'radio0'
     option type 'mac80211'
     option hwmode '11g'
     option path 'platform/ar933x_wmac'
     option htmode 'HT20'
     option disabled '0'
     option channel '4'
     option txpower '15'
     option country 'US'
    
    config wifi-iface
     option device 'radio0'
     option mode 'ap'
     option ssid 'YOUR_SSID'
     option network 'wifi'
     option encryption 'psk2'
     option key 'YOUR_PASSWORD'
    
    /etc/config/network
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    config interface 'loopback'
     option ifname 'lo'
     option proto 'static'
     option ipaddr '127.0.0.1'
     option netmask '255.0.0.0'
    
    config globals 'globals'
     option ula_prefix 'fd59:92ba:e14c::/48'
    
    config interface 'lan'
     option ifname 'eth0'
     option proto 'dhcp'
    
    config interface 'wifi'
     option _orig_ifname 'wlan0'
     option _orig_bridge 'false'
     option proto 'static'
     option ipaddr '172.16.0.1'
     option netmask '255.255.255.0'
    
    config interface 'EXP_VPN'
     option proto 'none'
     option ifname 'tun0'
    

    /etc/config/firewall
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    config defaults
     option syn_flood '1'
     option input 'ACCEPT'
     option output 'ACCEPT'
     option forward 'REJECT'
    
    config zone
     option name 'lan'
     option input 'ACCEPT'
     option output 'ACCEPT'
     option forward 'ACCEPT'
     option network 'wifi'
    
    config include
     option path '/etc/firewall.user'
    
    config zone
     option name 'VPN_FW'
     option network 'EXP_VPN'
     option input 'REJECT'
     option output 'ACCEPT'
     option forward 'REJECT'
     option masq '1'
     option mtu_fix '1'
    
    config forwarding
     option dest 'VPN_FW'
     option src 'lan'
    
    config zone
     option name 'wan'
     option input 'ACCEPT'
     option forward 'REJECT'
     option output 'ACCEPT'
     option network 'lan'
    
  9. Issue the following commands set Google DNS

    # uci add_list dhcp.lan.dhcp_option="6,209.222.18.222,209.222.18.218"
    # uci commit dhcp
    # reboot
  10. Using web login to LuCI on your OpenWrt router, configure a startup task instructing OpenVPN to initiate a VPN connection at bootup using the following lines:

    openvpn --cd /etc/openvpn --config /etc/openvpn/my_expressvpn_south_korea_udp.ovpn
    Configuring a startup task

    Please note that OpenVPN will try to connect to VPN server only once failing which it will
    stop trying. So you may need to reboot your router manually by power cycling if you arent able to access the internet.

  11. Now connect your devices to TL-MR3020's wifi signal and try accessing the internet.



Some more screenshots of LuCI's configuration pages - these correspond to the settings in the configuration files present in /etc/config








References:
  1. https://www.robertkehoe.com/2015/08/setup-openvpn-using-openwrt/
  2. https://www.loganmarchione.com/2014/10/openwrt-with-openvpn-client-on-tp-link-tl-mr3020/
  3. https://wiki.openwrt.org/doc/howto/vpn.client.openvpn.tun


Share your Laptop's ExpressVPN connection with your embedded development device while in China

Imagine you have an embedded development device (TL-MR3020 running OpenWrt in my case) and you are in China.  You want your embedded device to access unrestricted internet so that you can develop some apps for your router. You can use your laptop as a go between your home WiFi and your router. The laptop will initiate VPN connection and provide the internet access to your development device (TL-MR3020 in my case)
Our Chinese set top box which also provides internet over Wifi - it has a Wifi router builtin.

Here is my topology:


  • My laptop: Runs Windows 10 x64, ExpressVPN client and Tiny DHCP
  • My development device: Tplink TL-MR3020 running OpenWrt Chaos Calmer with rootfs mounted on external flash drive
  • Name of my WiFi adapter on my Windows PC: Wireless Network Connection
  • Name of my Ethernet adapter that I connect to my TL-MR3020: Local Area Connection
  • Name of the virtual TAP adapter created by ExpressVPN that I will share with my TL-MR3020: Ethernet (and NOT ExpressVPN - I know it might appear in some screenshots below, but that's not what we want to share)

My TL-MR3020 connected to my laptop's Ethernet port.
I want my TL-MR3020 (runs OpenWrt) to access internet unrestricted.
  1. Get a paid ExpressVPN account.
  2. Connect your Windows laptop to your home WiFi router - in my case the WiFi Router is built into the Cable TV set top box.
  3. Download and install ExpressVPN client. Login and test if VPN works on your laptop.
    ExpressVPN Windows client
  4. The VPN connects through WiFi. Our laptop's ethernet port is free. So connect a straight LAN cable between your TL-MR3020 router and laptop.
  5. Make sure the router is configured to receive an IP address over Ethernet using DHCP.
  6. Share your VPN connection. Express VPN creates a virtual TAP Ethernet adapter. We need to share that internet connection.
    Right click the TAP adapter
    Share it through the "Local Area Network" which is the ethernet port to which our TL-MR3020 is connected
  7. Run TinyDHCP server on your laptop. This will turn your laptop into a DHCP server and assign IP address to your TL-MR3020 when it requests it.
  8. Power on your TL-MR3020, wait for a while, then SSH into it over serial (if you have soldered a FT232RL widget to it) or using PuTTY over TCP/IP and check if it is able to access the internet using ping (# ping www.google,com).

    When the TL-MR3020 turns on, it requests the laptop for an dynamic IP address.
    That's when TinyDHCP server pops up a window into which we enter the appropriate settings
    to send to TL-MR3020.

  9. If the above step does not work, configure your development device to use Google's public DNS servers by editing /etc/resolv.conf 
    Editing resolv.conf to add entries for Google's Public DNS servers.

Sunday, May 15, 2016

Internet Connected Energy Meter

This is the final post in a series of post that explains how I installed an electrical energy meter in my house and connected  it to the internet.

Here are the steps involved:

  1. Installing a 3 phase energy meter in your home [Blog post here]
    (I used Selec MFM383C Modbus capable energy meter, connected it to my router using USB<>RS485 interface based on FT232RL and MAX485)
    Modbus capable energy meter
  2. Installing OpenWrt 15.05 Chaos Calmer on TP-LINK TL-MR3020 [Blog post here]
    (required adding a USB hub to it, a USB flash drive to hold the rootfs, an FT232RL widget  for getting console access to the router over serial and another FT232RL based USB<>RS485 UART for interfacing to the energy meter using modbus. Instead of TL-MR3020, you can use TL-MR3040 as well which has its own battery pack in it.)

    TL-MR3040 (White color) with a USB hub sporting an 8GB Flash drive and USB<>RS485
    convertor.

    A TL-MR3020 with a USB hub (Flash drive in one of the ports) and
    an FT232RL used to gain console access.
  3. Compiling the binaries for libmodbus and mfm383c [Blog post here]
      
  4. Install drivers for FT232RL based RS485 bridge, curl and some other important stuff on your router:
    # opkg update#  opkg install kmod-usb-serial-ftdi kmod-usb-acm kmod-usb-serial curl coreutils-stty usbutils libmodbus bash nano grep getopt ip-full syslog-ng
      
  5. Installing CA Certificates on your TL-MR3020 running OpenWrt
    We need HTTPS support to upload datapoints to initialstate.com, so to avoid the "(51) Cert verify failed: BADCERT_NOT_TRUSTED" error thrown by cURL, we need to install the certificates, do this by issuing the commands to your router:

    # opkg update
    # opkg install ca-certificates


  6. Sign up for an Initial State account.
      
  7. Write script to execute mfm383c.out, read all the registers from the energy meter over modbus and use cURL
    Script is appended below, place it in /root of your router. Make sure to use your own access key and bucket key from your initial state account and add them to the script accordingly.
      
  8. Finally add it to the crontab - use the web GUI and set the script to execute once every minute.
Configuring the router to run the script every minute

This is what my initial state tiles view looks like.
It not only has data coming in from my energy meter but also from my weather station.



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#!/bin/bash

export SSL_CERT_DIR=/etc/ssl/certs


# InitialState Configuration:
INITIALSTATE_ACCESS_KEY="YOUR ACCESS KEY"
INITIALSTATE_BUCKET_KEY="YOUR BUCKET KEY"

register_names=( "KWH" "V1N" "V2N" "V3N" "I1" "I2" "I3" "FREQ" "KW_TOTAL" "KVA_TOTAL" )
array_len=${#register_names[@]}

logger 'Reading Registers from Selec MFM383C over RS485/Modbus'
echo 'Reading Registers from Selec MFM383C over RS485/Modbus'
echo 'Baud 9600 | Data bits 8 | Parity bits N | Stop bits 1 | Port /dev/ttyUSB0'
echo 'Slave ID 1 | Response timeone 1 second | Byte timeout 1 second'
echo '--------------------------------------------------------------------------'
echo ''


# ------ Initial State -------------------------------------------------------------

# Prepare cURL HTTP PUT data
DATA_JSON="["

for (( i=0; i<${array_len}; i++ ));
do
 # Send two Ping request packets and 4 seconds timeout
 register_value=$(/root/mfm383c.out -b 9600 -r ${register_names[$i]})
 return_value=$?
 case $return_value in
  0)  #Register read successfully
   logger "Selec MFM383C register read successfully ${register_names[$i]} = $register_value"
   DATA_JSON="$DATA_JSON"$'\n'"     { \"key\" : \"Electricity_${register_names[$i]}\","
   DATA_JSON="$DATA_JSON"$'\n'"      \"value\" : \"$register_value\""
   if [ $((i)) = $((array_len-1)) ]; then
    DATA_JSON="$DATA_JSON"$'\n'"    }"
   else
    DATA_JSON="$DATA_JSON"$'\n'"    },"
   fi
   ;;
  1)  #Reading register failed: Incorrect command line parameters
   logger "Selec MFM383C register read error: Incorrect command line parameters"
   ;;
  2)  #Reading register failed: Modbus library error
   logger "Selec MFM383C register read error: Modbus library error"
   ;;
  3)  #Reading register failed: Modbus serial error
   logger "Selec MFM383C register read error: Modbus serial error"
   ;;
  *)  #Reading register failed
   logger "Selec MFM383C register read error"
   ;;
 esac
done
DATA_JSON="$DATA_JSON"$'\n'"  ]"

echo $DATA_JSON

# execute cURL
curl --max-time 5 \
--include \
--request POST \
--header "Content-Type: application/json" \
--header "X-IS-AccessKey: $INITIALSTATE_ACCESS_KEY" \
--header "X-IS-BucketKey: $INITIALSTATE_BUCKET_KEY" \
 --header "Accept-Version: 0.0.4" \
 --data-binary "$DATA_JSON"  \
'https://groker.initialstate.com/api/events'

Friday, May 13, 2016

Cross compile .ipk including lib dependences for OpenWRT on Ubuntu 15.10 x64

In my efforts to smartify my house, one of the first things I did was to install an energy meter and connect it to the internet. I connected a TP-link TL-MR3020 to a Modbus capable energy meter to do this.
Here are my efforts this far:
The next involved writing and compiling some native code to interface with the energy meter using Modbus. The Selec MFM383C energy is connected to TL-MR3020 over UART. My TL-MR3020 has a USB hub connected to it. One of the slots has a 8GB USB Flash drive in it with the rootfs on it. Another slot has FT232 and MAX485 based USB-UART bridge. I use this USB-UART communicate with my mfm383c using Modbus.

------------

Problem statement: Cross compile C code into an OpenWrt ipkg in case which requires fetching and compilation of dependent libraries as well.

Detailed problem statement:
  • Cross compile a piece of software (native, C program)
  • The software depends on a library whose repository is maintained online - a tar archive of which can be fetched during make/
  • Target: TL-MR3020 running OpenWRT 15.05 Chaos Calmer
  • Development machine: Ubuntu x64 15.10
  • Output should be an installable ipkg. We should be able to use opkg to install the software as well as its dependency.
  • The software and the library it depends on should be complied using a single make command.
  • During make, the sources for the library will be fetched from its online repository automatically.

Bit more details about our software:
Its a C program that uses libmodbus to communicate with a modbus capable energy meter (Selec MFM383C) over USB <> UART (FT232RL) to read specified registers depending on the parameters passed to it via command line.


  1. Preparation Step 1 - Setting up Ubuntu 15.10 x64 Virtual Machine on your Windows PC
    1. Download Ubuntu 15.10 x64 virtual machine image from osboxes.org
    2. Import it into your VirtualBox
    3. Make sure to share a couple of folder between Ubuntu and Windows. Also share the clipboards. (click here to know how todo this http://www.electronicsfaq.com/2013/04/sharing-folders-between-ubuntu-12041.html)
    4. Install git and build tools on your VM
      sudo apt-get install autoconf build-essential
  2. Preparation Step 2 - Install toolchain, prepare router, install Chaos Calmer on Router.Click here to kno how to perform all of the following steps: http://www.electronicsfaq.com/2015/12/openwrt-1505-chaos-calmer-on-tl-mr3020.html)
    1. Install the toolchain
    2. Open your router and solder a USB - > serial bridge
    3. Mount an external USB flash drive
    4. Finally compile and install chaos calmer on it
  3. Now we need to figure out the folders in which we need to download (git clone) the library (library) and where to place the source files for our own application. We also need to know what should go into the makefiles and where to place them. So here are the details:

    1. You need to place your source and Makefiles in subfolders created in:
      ~/OpenWrt-SDK-15.05-ar71xx-generic_gcc-4.8-linaro_uClibc-0.9.33.2.Linux-x86_64/package
    2. There will already be a Makefile (last modified 25 July 2015) in there. No need to touch it.
    3. Create a subfolder in the packages folder for your software (I called mine "mfm383c") and place your .c source file and Makefile in sub-sub-folder named "src" in it.
    4. Create another subfolder in the packages folder for the library that is going to be downloaded and place your Makefile in it. No need to download any .c file. Instructions for cloning the git are already present in the Makefile. I named this folder "libmodbus"
      Folder tree of the makefiles and sources
  4. The contents of these C files and Makefile are appended below. The relevant section that take care of downloading and ensuring dependencies are highlighted.
  5. Lets compile the stuff
    1. Lets export the variables
      export PATH=~/OpenWrt-SDK-15.05-ar71xx-generic_gcc-4.8-linaro_uClibc-0.9.33.2.Linux-x86_64/staging_dir/toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2/bin:$PATH

      export STAGING_DIR=~/OpenWrt-SDK-15.05-ar71xx-generic_gcc-4.8-linaro_uClibc-0.9.33.2.Linux-x86_64/staging_dir
    2. cd to ~/OpenWrt-SDK-15.05-ar71xx-generic_gcc-4.8-linaro_uClibc-0.9.33.2.Linux-x86_64
    3. And issue the make command
    4. The .ipk files will be placed in:~/OpenWrt-SDK-15.05-ar71xx-generic_gcc-4.8-linaro_uClibc-0.9.33.2.Linux-x86_64/bin/ar71xx/packages/base
    5. Location of the output .ipk files
  6. Install the packages on the router:
    1. Copy the two .ipk files from Linux VM to Windows and then transfer them to /root of your router using WinSCP
    2. Then SSH into your router using PuTTY and install the packages:
      1. opkg install libmodbus_3.0.6-1_ar71xx.ipk
      2. opkg install mfm383c_1.0.0-1_ar71xx.ipk
  7. And Done!

Sources - Lets look at the 3 Makefiles first

These Makefiles are based on the ones supplied by OpenWrt on their wiki:
https://wiki.openwrt.org/doc/devel/packages

/package/libmodbus/Makefile

As you can note, there isn't much here. We are just telling the build system from where to fetch the tar ball of the sources from the internet, extract them and compile them. In this Makefile we are also specifying where to place the .so files (shared library files - they are like the .dlls on Windows) within the root filesystem (/usr/lib) of the router when the .ipk is installed on it. There are two .ipk files that are going to be generated - one for libmodbus and the other for our program that we have written (mfm383c)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# Copyright (C) 2006-2015 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#

include $(TOPDIR)/rules.mk

PKG_NAME:=libmodbus
PKG_VERSION:=3.0.6
PKG_RELEASE:=1

PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=http://libmodbus.org/releases/
PKG_MD5SUM:=c80f88b6ca19cabc4ceffc195ca07771

PKG_MAINTAINER:=Michael Heimpold <mhei@heimpold.de>

PKG_LICENSE:=GPL-3.0+ LGPL-2.1+
PKG_LICENSE_FILES:=COPYING COPYING.LESSER

PKG_FIXUP:=autoreconf
PKG_INSTALL:=1

include $(INCLUDE_DIR)/package.mk

define Package/libmodbus
  SECTION:=libs
  CATEGORY:=Libraries
  URL:=http://www.libmodbus.org
  TITLE:=libmodbus
endef

define Package/libmodbus/description
  A Modbus library for Linux, Mac OS X, FreeBSD, QNX and Win32.
endef

CONFIGURE_ARGS += --without-documentation
TARGET_CFLAGS += $(FPIC)

define Build/InstallDev
 $(INSTALL_DIR) $(1)/usr/include
 $(CP) $(PKG_INSTALL_DIR)/usr/include/modbus $(1)/usr/include/
 $(INSTALL_DIR) $(1)/usr/lib
 $(CP) $(PKG_INSTALL_DIR)/usr/lib/libmodbus.{so*,la} $(1)/usr/lib/
 $(INSTALL_DIR) $(1)/usr/lib/pkgconfig
 $(CP) $(PKG_INSTALL_DIR)/usr/lib/pkgconfig/libmodbus.pc $(1)/usr/lib/pkgconfig/
endef

define Package/libmodbus/install
 $(INSTALL_DIR) $(1)/usr/lib
 $(CP) $(PKG_INSTALL_DIR)/usr/lib/libmodbus.so* $(1)/usr/lib/
endef

$(eval $(call BuildPackage,libmodbus))

/package/mfm383c/src/Makefile

This makefile specifies how to build the mfm383c.c and mfm383c.h files contained in the src folder.
Notice the LIBS variable specifying that these source have a dependencies on libmodbus.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# build mfm383c executable when user executes "make"
LIBS=-lmodbus

mfm383c: mfm383c.o
 $(CC) $(LDFLAGS) $(LIBS) mfm383c.o -o mfm383c
mfm383c.o: mfm383c.c
 $(CC) -c $(CFLAGS) mfm383c.c

# remove object files and executable when user executes "make clean"
clean:
 rm *.o mfm383c


/package/mfm383c/Makefile

This Makefile is the most important of the three - it tells the build system to build the libmodbus library before building mfm383c to ensure that the dependencies are met. We also need to make sure that the modbus.h file included in mfm383c.c  can be found in the right place.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
##############################################
# OpenWrt Makefile for mfm383c program
#
#
# Most of the variables used here are defined in
# the include directives below. We just need to
# specify a basic description of the package,
# where to build our program, where to find
# the source files, and where to install the
# compiled program on the router.
#
# Be very careful of spacing in this file.
# Indents should be tabs, not spaces, and
# there should be no trailing whitespace in
# lines that are not commented.
#
##############################################

include $(TOPDIR)/rules.mk

# Name and release number of this package
PKG_NAME:=mfm383c
PKG_VERSION:=1.0.0
PKG_RELEASE:=1

TARGET_CFLAGS=-I$(STAGING_DIR)/usr/include/modbus
TARGET_LDFLAGS=-L$(STAGING_DIR)/usr/include/modbus
PKG_BUILD_DEPENDS:=libmodbus

# This specifies the directory where we're going to build the program.
# The root build directory, $(BUILD_DIR), is by default the build_mipsel
# directory in your OpenWrt SDK directory
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)

include $(INCLUDE_DIR)/package.mk

# Specify package information for this program.
# The variables defined here should be self explanatory.
define Package/mfm383c
 SECTION:=utils
 CATEGORY:=Utilities
 DEPENDS:=+libmodbus
 TITLE:=MFM383C -- prints a snarky message
endef

define Package/mfm383c/description
 If you can't figure out what this program does,
 you're probably brain-dead and need immediate
 medical attention.
endef

# Specify what needs to be done to prepare for building the package.
# In our case, we need to copy the source files to the build directory.
# This is NOT the default.  The default uses the PKG_SOURCE_URL and the
# PKG_SOURCE which is not defined here to download the source from the web.
# In order to just build a simple program that we have just written, it is
# much easier to do it this way.
define Build/Prepare
 mkdir -p $(PKG_BUILD_DIR)
 $(CP) ./src/* $(PKG_BUILD_DIR)/
endef

define Build/Configure
  $(call Build/Configure/Default,--with-linux-headers=$(LINUX_DIR))
endef

define Build/Compile
  $(MAKE) -C $(PKG_BUILD_DIR) \
 CFLAGS="$(TARGET_CFLAGS)" \
 LDFLAGS="$(TARGET_LDFLAGS)" \
 $(TARGET_CONFIGURE_OPTS)
endef

# We do not need to define Build/Configure or Build/Compile directives
# The defaults are appropriate for compiling a simple program such as this one

# Specify where and how to install the program. Since we only have one file,
# the mfm383c executable, install it by copying it to the /bin directory on
# the router. The $(1) variable represents the root directory on the router running
# OpenWrt. The $(INSTALL_DIR) variable contains a command to prepare the install
# directory if it does not already exist.  Likewise $(INSTALL_BIN) contains the
# command to copy the binary file from its current location (in our case the build
# directory) to the install directory.
define Package/mfm383c/install
 $(INSTALL_DIR) $(1)/bin
 $(INSTALL_BIN) $(PKG_BUILD_DIR)/mfm383c $(1)/bin/
endef

# This line executes the necessary commands to compile our program.
# The above define directives specify all the information needed, but this
# line calls BuildPackage which in turn actually uses this information to
# build a package.
$(eval $(call BuildPackage,mfm383c,+libmodbus))


Sources - .c and .h



Finally the sources for mfm383c.c and mfm383c.h placed in\package\mfm383c\src:



mfm383c.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#include <stdio.h> //Defines core input and output functions: printf(), scanf(), getchar(), puchar(), gets(), puts() etc.
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>  //Required for isprint()

#include <modbus.h>
#include "mfm383c.h"

#define MFM383C_CONFIG_BAUDRATE_DEFAULT  9600    //Default
#define MFM383C_CONFIG_DATA_BITS   8
#define MFM383C_CONFIG_PARITY_BITS   'N'
#define MFM383C_CONFIG_STOP_BITS   1
#define MFM383C_CONFIG_PORT     "/dev/ttyUSB0"
#define MFM383C_CONFIG_SLAVE_ID    1
#define MFM383C_CONFIG_RESPONSE_TIMEOUT_S 1   //Default is 0
#define MFM383C_CONFIG_RESPONSE_TIMEOUT_US 500000  //Default is 500000
#define MFM383C_CONFIG_BYTE_TIMEOUT_S  1   //Default is 0
#define MFM383C_CONFIG_BYTE_TIMEOUT_US  500000  //Default is 500000

#define RETURN_SUCCESS     0
#define RETURN_FAIL_PARAM    1
#define RETURN_FAIL_MODBUS_LIB   2
#define RETURN_FAIL_MODBUS_SERIAL  3

struct timeval response_timeout = {MFM383C_CONFIG_RESPONSE_TIMEOUT_S,MFM383C_CONFIG_RESPONSE_TIMEOUT_US};
struct timeval byte_timeout = {MFM383C_CONFIG_BYTE_TIMEOUT_S,MFM383C_CONFIG_BYTE_TIMEOUT_US};

modbus_t *ctx;

int main(int argc, char **argv) {
 uint16_t register_set[2]={0};
 int reg_addr = 0;
 char *register_name = NULL;
 int passed_flag;
 int baud_rate = MFM383C_CONFIG_BAUDRATE_DEFAULT;
 int debug_flag = 0;

 while ((passed_flag = getopt(argc, argv, "db:r:")) != -1) {
  switch (passed_flag) {
   case 'd': {
    debug_flag = 1;
   } break;
   case 'b': {
    baud_rate = atol(optarg);
    switch(baud_rate) {
     case 300   :
     case 600   :
     case 1200  :
     case 2400  :
     case 4800  :
     case 9600  :
     case 19200 :
      //These baud rates are supported by Selec MFM383C
      break;
     default:
      fprintf(stderr, "Unsupported or invalid baud rate value passed: %d\n", baud_rate);
      fprintf(stderr, "Using default value for baud rate: %d bps\n", MFM383C_CONFIG_BAUDRATE_DEFAULT);
      baud_rate = MFM383C_CONFIG_BAUDRATE_DEFAULT;
      break;
    };
   } break;
   case 'r': {
    register_name = optarg;
   } break;
   case '?': {
    fprintf(stderr, "Unknown option character `\\x%x'.\n", optopt);
    return RETURN_FAIL_PARAM;
   } break;
   default: {
    fprintf(stderr, "Invalid command line argument.\n");
    return RETURN_FAIL_PARAM;
   } break;
  };
 }

 if(register_name == NULL) {
  fprintf(stderr, "Option -r requires an argument.\n");
  return RETURN_FAIL_PARAM;
 }
  
 ctx = modbus_new_rtu(MFM383C_CONFIG_PORT, baud_rate, MFM383C_CONFIG_PARITY_BITS, MFM383C_CONFIG_DATA_BITS, MFM383C_CONFIG_STOP_BITS);// Creates new Modbus RTU connection
 if (ctx == NULL) {
  fprintf(stderr, "Unable to create the libmodbus context\n");
  return RETURN_FAIL_MODBUS_SERIAL;
 }
 
 if(debug_flag == 1) {
  modbus_set_debug(ctx, TRUE); 
 }
 
 if (modbus_connect(ctx) == -1) {
         fprintf(stderr, "Modbus serial connection failed: %s\n", modbus_strerror(errno));
         modbus_free(ctx);
         return RETURN_FAIL_MODBUS_LIB;
    } else {
  
  modbus_set_byte_timeout(ctx, &response_timeout);
  modbus_set_response_timeout(ctx, &byte_timeout);
  
  //modbus_get_byte_timeout(ctx, &byte_timeout);
  //modbus_get_response_timeout(ctx, &response_timeout);
  //printf("\nByte Timeout: %d seconds %d microseconds",byte_timeout.tv_sec, byte_timeout.tv_usec);
  //printf("\nResponse Timeout: %d seconds %d microseconds\n",response_timeout.tv_sec, response_timeout.tv_usec);
  
  if(modbus_set_slave(ctx, MFM383C_CONFIG_SLAVE_ID)==-1){
      fprintf(stderr, "Modbus modbus_set_slave() failed: %s\n", modbus_strerror(errno));
         modbus_free(ctx);
   return RETURN_FAIL_MODBUS_LIB;
  } else {   
   if(strcmp(register_name,"V1N") == 0) {
    reg_addr = MFM383C_REGR_V1N;
   } else if(strcmp(register_name,"V2N") == 0) {
    reg_addr = MFM383C_REGR_V2N;
   } else if(strcmp(register_name,"V3N") == 0) {
    reg_addr = MFM383C_REGR_V3N;
   } else if(strcmp(register_name,"VLN_AVG") == 0) {
    reg_addr = MFM383C_REGR_VLN_AVG;
   } else if(strcmp(register_name,"V12") == 0) {
    reg_addr = MFM383C_REGR_V12;
   } else if(strcmp(register_name,"V23") == 0) {
    reg_addr = MFM383C_REGR_V23;
   } else if(strcmp(register_name,"V31") == 0) {
    reg_addr = MFM383C_REGR_V31;
   } else if(strcmp(register_name,"VLL_AVG") == 0) {
    reg_addr = MFM383C_REGR_VLL_AVG;
   } else if(strcmp(register_name,"I1") == 0) {
    reg_addr = MFM383C_REGR_I1;
   } else if(strcmp(register_name,"I2") == 0) {
    reg_addr = MFM383C_REGR_I2;
   } else if(strcmp(register_name,"I3") == 0) {
    reg_addr = MFM383C_REGR_I3;
   } else if(strcmp(register_name,"I_AVG") == 0) {
    reg_addr = MFM383C_REGR_I_AVG;
   } else if(strcmp(register_name,"KW1") == 0) {
    reg_addr = MFM383C_REGR_KW1;
   } else if(strcmp(register_name,"KW2") == 0) {
    reg_addr = MFM383C_REGR_KW2;
   } else if(strcmp(register_name,"KW3") == 0) {
    reg_addr = MFM383C_REGR_KW3;
   } else if(strcmp(register_name,"KVA1") == 0) {
    reg_addr = MFM383C_REGR_KVA1;
   } else if(strcmp(register_name,"KVA2") == 0) {
    reg_addr = MFM383C_REGR_KVA2;
   } else if(strcmp(register_name,"KVA3") == 0) {
    reg_addr = MFM383C_REGR_KVA3;
   } else if(strcmp(register_name,"PF1") == 0) {
    reg_addr = MFM383C_REGR_PF1;
   } else if(strcmp(register_name,"PF2") == 0) {
    reg_addr = MFM383C_REGR_PF2;
   } else if(strcmp(register_name,"PF3") == 0) {
    reg_addr = MFM383C_REGR_PF3;
   } else if(strcmp(register_name,"PF_AVG") == 0) {
    reg_addr = MFM383C_REGR_PF_AVG;
   } else if(strcmp(register_name,"FREQ") == 0) {
    reg_addr = MFM383C_REGR_FREQ;
   } else if(strcmp(register_name,"KWH") == 0) {
    reg_addr = MFM383C_REGR_KWH;
   } else if(strcmp(register_name,"KVAR1") == 0) {
    reg_addr = MFM383C_REGR_KVAR1;
   } else if(strcmp(register_name,"KVAR2") == 0) {
    reg_addr = MFM383C_REGR_KVAR2;
   } else if(strcmp(register_name,"KVAR3") == 0) {
    reg_addr = MFM383C_REGR_KVAR3;
   } else if(strcmp(register_name,"KW_TOTAL") == 0) {
    reg_addr = MFM383C_REGR_KW_TOTAL;
   } else if(strcmp(register_name,"KVA_TOTAL") == 0) {
    reg_addr = MFM383C_REGR_KVA_TOTAL;
   } else if(strcmp(register_name,"KVAR_TOTAL") == 0) {
    reg_addr = MFM383C_REGR_KVAR_TOTAL;
   } else {
    fprintf(stderr, "%s is not a valid register name\n",register_name);
    modbus_free(ctx);
    return RETURN_FAIL_PARAM;
   }
   
   //Read the specified register
   if(modbus_read_input_registers(ctx, reg_addr, 2, register_set)==-1){
    fprintf(stderr, "Slave Read failed: %s\n", modbus_strerror(errno));
    modbus_free(ctx);
    return RETURN_FAIL_MODBUS_SERIAL;        
   } else {
    //No byte swapping needed on TL-MR3040
    fprintf(stdout,"%f\n", *((float*)&register_set[0]));
    modbus_free(ctx);
    return RETURN_SUCCESS;
   }
   
   modbus_free(ctx);
   return RETURN_FAIL_MODBUS_SERIAL;  
  }    
 }
}
mfm383c.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#ifndef _MFM383C_H_
#define _MFM383C_H_

#include <inttypes.h>

//Each Modbus Register is 16 bits in length

//Writeable Parameters (4xxxxx) //Register Address Offset  // Min Value Max Value Length  Data Structure
#define MFM383C_REGW_CT_PRIMARY (1)        // 5   5000  1   Integer
#define MFM383C_REGW_RESET_KWH (2)        // 0   1    1   Integer

//Readable Parameters (3xxxxx) //Register Address Offset  // Min Value Max Value Length  Data Structure
#define MFM383C_REGR_V1N  (1)        // 0   350.0  2   Float
#define MFM383C_REGR_V2N  (3)        // 0   350.0  2   Float
#define MFM383C_REGR_V3N  (5)        // 0   350.0  2   Float
#define MFM383C_REGR_VLN_AVG (7)        // 0   350.0  2   Float
#define MFM383C_REGR_V12  (9)        // 0   607.0  2   Float
#define MFM383C_REGR_V23  (11)       // 0   607.0  2   Float
#define MFM383C_REGR_V31  (13)       // 0   607.0  2   Float
#define MFM383C_REGR_VLL_AVG (15)       // 0   607.0  2   Float
#define MFM383C_REGR_I1   (17)       // 0   5000.0  2   Float
#define MFM383C_REGR_I2   (19)       // 0   5000.0  2   Float
#define MFM383C_REGR_I3   (21)       // 0   5000.0  2   Float
#define MFM383C_REGR_I_AVG  (23)       // 0   5000.0  2   Float
#define MFM383C_REGR_KW1  (25)       // -1750.00 1750.00  2   Float
#define MFM383C_REGR_KW2  (27)       // -1750.00 1750.00  2   Float
#define MFM383C_REGR_KW3  (29)       // -1750.00 1750.00  2   Float
#define MFM383C_REGR_KVA1  (31)       // 0   1750.00  2   Float
#define MFM383C_REGR_KVA2  (33)       // 0   1750.00  2   Float
#define MFM383C_REGR_KVA3  (35)       // 0   1750.00  2   Float
#define MFM383C_REGR_PF1  (37)       // -0.99  1.00  2   Float
#define MFM383C_REGR_PF2  (39)       // -0.99  1.00  2   Float
#define MFM383C_REGR_PF3  (41)       // -0.99  1.00  2   Float
#define MFM383C_REGR_PF_AVG  (43)       // -0.99  1.00  2   Float
#define MFM383C_REGR_FREQ  (45)       // 0   65.0  2   Float
#define MFM383C_REGR_KWH  (47)       // 0   99999999.9 2   Float
#define MFM383C_REGR_KVAR1  (49)       // -1750.00 1750.00  2   Float
#define MFM383C_REGR_KVAR2  (51)       // -1750.00 1750.00  2   Float
#define MFM383C_REGR_KVAR3  (53)       // -1750.00 1750.00  2   Float
#define MFM383C_REGR_KW_TOTAL (55)       // -5250.00 5250.00  2   Float
#define MFM383C_REGR_KVA_TOTAL (57)       // 0   5250.00  2   Float
#define MFM383C_REGR_KVAR_TOTAL (59)       // -5250.00 5250.00  2   Float
#define MFM383C_REGR_STATUS  (61)       // X   X   1   Integer

//Status Bits
#define MFM383C_STATUS_PHASE1_CT_REV 0x0001
#define MFM383C_STATUS_PHASE2_CT_REV 0x0002
#define MFM383C_STATUS_PHASE3_CT_REV 0x0004

#endif //_MFM383C_H_