Playing around with some IoT stuff i wanted to understand a few things: how cheap can you build something? what does a industrial / production ready IoT look like? what works for my personal "needs"? I think these are questions best learned by doing and experiencing first hand what works and what not. So i started with one of the easiest: how cheap can you build something? Of course for any example, a PI server is the target platform for the data

Of course this is nothing new. Butch Payne already wrote a nice few blog posts on this: Butch Payne's Blog But maybe this slightly different angle gives some ideas. It is that cheap that you can leave one at every customer you go to... If you link it to your cellphone Wifi, it can send data whenever you are around.

Warning: i''m not a programmer, so forgive me for my clumsy programming...

 

 

Let's first define IoT. In this case it's not the platform where you analyze all the data (we have PI for that...), but all that is needed to get data from the field to your PI server. PI Interfaces are too heavy or expensive to run for just a single measurement, and are difficult, if not impossible to operate across the internet. IoT also implies that the devices must connect through the internet to my PI eventually. For the sake of testing this in my own house, or anywhere i go, i wanted to have a Wifi solution. Using our corporate Wifi, my home Wifi, or my cellphone Wifi Hotspot to work where ever i did not have access to the resident WiFi network.

 

So let's look at the components needed:

 

  1. Data Hub: where the data is held in Transit. Must live in the cloud of course.
  2. Hardware: The as-cheap-as-possible piece linking reality to the internet.
  3. PI Reader: getting the data from the data Hub and pushing into PI

 

Data Hub:

As HTTPS / SSL was out of the question (ready why in the hardware section), any industrial-grade hub like Azure EventHubs was out. So i settled for ThingSpeak (Internet Of Things - ThingSpeak ). Create a channel within your account, and for free you can send and receive data through that. When you channel is private, you need read and write API keys to access the channel. So if your device or channel is compromised due to the use of HTTP or because the device is insecure, retire the channel. If the channel is public, anyone can read or write. Lookup mine here: IoT_Test - ThingSpeak

 

 

Hardware:

Cheap and Wifi. And of course a programmable microcontroller. So you quickly end up with the ESP8266 module:

esp8266.jpg

 

They cost about 2 euro each. Yes, it's more expensive to read this again than to buy one. Run on 3v so on two AA batteries for some time.

 

As these mini boards require some periphals to program, do power conversions 5v -3v etc. i made it easy for myself and spent a whopping 7 euro on an extensive development board: NodeMCU

 

SKU226184-2.jpg

The microcontroller supports the Lua scripting interpreter, so load that onto the board and get going doing some programming: nodemcu (zeroday) · GitHub

Being to lazy to do wiring, i did not connect any sensors, so i just used a random value.

 

The major drawback of the hardware is the limited processing capacity. In other words: it does not support HTTPS. The price point does make this a short-term throwaway option anyway, so by the time someone compromised the solution just retire the unit and deploy a new one.

That limited the options for Data Hubs too, mainly ruling out Azure Event Hubs. So now for the key lines of code in the Lua scripting language:

 

--CONST
pin_LED = 0
upload_msdelay = 30000 --upload every 30000ms
port = 80
URL = "api.thingspeak.com"
HTTPcall = "/update"
APIkey = "api_key=XXXXXXXXX"
Value1 = "field1="
--------------------------------------------------------------------------------
function led_off()
    gpio.write(pin_LED, gpio.HIGH)
    ledstate = 0
end


function led_on()
    gpio.write(pin_LED, gpio.LOW)
    ledstate = 1
end


function led_toggle()
    if ledstate == 0 then
        led_on()
        ledstate = 1
    else
        led_off()
        ledstate = 0
    end
end


function send_randvalue()
    --Get random value between 0 and 100
    randvalue = math.random(0,100)
    print("Send Value: "..randvalue)
    HTTPstring = HTTPcall.."?"..APIkey.."&"..Value1..randvalue
    print("HTTP: "..HTTPstring)
    --init the connection
    conn = nil
    conn = net.createConnection(net.TCP,0)


    --receiver
    conn:on("receive", function(conn, payloadout)
        if (string.find(payloadout, "Status: 200 OK") ~= nil) then
            print("Posted OK");
        end
    end)    


    --connect
    conn:on("connection", function(conn, payloadout)
        conn:send("GET "..HTTPstring
        .. " HTTP/1.1\r\n"
        .. "Host: api.thingspeak.com\r\n"
        .. "Connection: close\r\n"
        .. "Accept: */*\r\n"
        .. "User-Agent: Mozilla/4.0 (compatible; esp8266 Lua; Windows NT 5.1)\r\n"
        .. "\r\n")
    end)    


    --disconnect
    conn:on("disconnection", function(conn, payloadout)
        conn:close();
        collectgarbage();
    end)
    
    --do the connection
    conn:connect(port,URL)
end


function periodic()
    led_toggle()
    send_randvalue()
    led_toggle()
end
--------------------------------------------------------------------------------
--init
print("--thingspeak.lua")
--Start loop timer
tmr.alarm(1, upload_msdelay, 1, periodic) --repeating alarm

 

The microcontroller is single-threaded, so that's why we need timers to run stuff. There is also an init and startup routine that do the connection to the Wifi, but that's all pretty standard stuff for the NodeMCU so i did not include that.

 

 

PI Reader:

Nothing special here, just some lines of C# / NET code in a console app to read the Thingspeak API through HTTPS.

 

Reading the public stream data for field1, last value only, using XML (JSON is another option): https://api.thingspeak.com/channels/75174/fields/1.xml?results=1

 

Reading thinkspeak data:

        public static IoT_Data ReadThingSpeak(string strURL)
        {
            HttpWebRequest ThingspeakReq;
            HttpWebResponse ThingspeakResp;
            StreamReader reader;
            XmlDocument xmlDoc;


            IoT_Data results;


            //Init
            results = new IoT_Data();
            //Create request to fetch latest value
            ThingspeakReq = (HttpWebRequest)WebRequest.Create(strURL);
            ThingspeakResp = (HttpWebResponse)ThingspeakReq.GetResponse();
            //Check response
            if (ThingspeakResp.StatusCode != HttpStatusCode.OK)
            {
                throw new Exception(string.Format("ERROR! In calling ThingSpeak: {0}: {1}", ThingspeakResp.StatusCode, ThingspeakResp.StatusDescription));
            }
            else
            {
                //read the datastream
                reader = new StreamReader(ThingspeakResp.GetResponseStream());
                xmlDoc = new XmlDocument();
                xmlDoc.LoadXml(reader.ReadToEnd());
                //loop the feeds list
                XmlNodeList feeds = xmlDoc.SelectNodes("/channel/feeds/feed");
                foreach (XmlNode feed in feeds)
                {
                    //Get values from node and writeout
                    results.timestamp = feed["created-at"].InnerText;
                    results.value = feed["field1"].InnerText;
                }
            }
            return results;
        }

(yes, still need to fix the loop....its crappy only retaining the last. Not an issue as the URL only retrieves a single measurement though)

 

Sending to PI:

        public static void Write2PI(IoT_Data IoT_eventdata, string strAFattribute)
        {
            AFAttribute myAFAttribute;
            AFValue myAFVal;
            AFTime myAFTime;
                        
            //First create a PIValue object
            myAFTime = new AFTime(Convert.ToDateTime(IoT_eventdata.timestamp), CultureInfo.InvariantCulture);
            myAFVal = new AFValue(Convert.ToInt16(IoT_eventdata.value),myAFTime);
            
            //Find Attribute
            myAFAttribute = AFAttribute.FindAttribute(strAFattribute,null);
            if ( myAFAttribute == null)
            {
                throw new Exception(string.Format("ERROR! Cannot find AFattribute {0}.",strAFattribute));
            } else {
                //Write value
                myAFAttribute.SetValue(myAFVal);
            }
        }

 

 

Afterthought:

It's possible to build something very cheap, running on batteries, that pushes data to PI. Not very secure but that was not the point. However this is not a sustainable solution if you need to manage deploying tens or more of these units, or if you want data that can be trusted more than just checking your outside pool temperature during winter.

Another thought: why can't i just send a call to a PI service? A PI Connect REST endpoint feeding into my AF? Or an online PI database, no warrantees, but just there to play with? Plenty of players investigating this area but little sight of OSIsoft.