Wind and Rain sensor kit newly arrived from SparkFun Electronics to upgrade an Arduino Weather Station project.
Also pictured are earlier DIY prototypes – a childrens bee wind spinner with hall effect sensor to count rotations, an anemometer made from recycled plastic packaging utilising a IR Led optical rotary encoder and a wind vane with eight fixed directional magnetic switches.
Weather station projects are a popular accessible introduction to microelectronics; a microcontroller and sensors can be found at low cost, modular hardware design results in easy assembly and open software platforms like Arduino IDE streamline packaging and deployment of code to devices.
Analysing real time or historical time series data, from weather sensors is a lot of fun. Frameworks like R Project for Math Stats: https://www.r-project.org/ ) and Python, Pandas, Numpy & Mathplotlib provide implementations of most alogirithms and convenient data structures for importing & manipulating data.
Techniques and methods are transferable and can be applied to other domains or ontologies – finanicial, accounting data for example.
With advent of 3d modelling & printing it is also feasible for an enthusiast to design and fabricate via a 3d printer custom sensor components, perhaps using template models downloaded from repos like ThingiVerse.
In competition marine OpenWind are defining what smart network connected sensors can achieve utilising Bluetooth LE to make near real time wind data available on smartphone.
Ideal for enthusiast or educator SparkFun Weather kit comes wihout circuitry, microcontroller or software. An add-on PCB designed for use with Arduino / ESP32 can be purchased or Datasheet Technical Specs provide reference sensor circuit designs, not significantly complex due to use of magnetic reed switch and variable resistance technology.
Traditionally 433MHz RF has been used for base station to transmitter devices. A popular project is to use Arduino, a cheap 433Mhz receiver and a library to read data from a commercial weather station designed for use with manufacturers display, enabling this data to be provisioned to the cloud.
For data transmission non GPRS (cellular) options include Bluetooth LE (range ~100 metres) or LoRa (Long Range Low Power Network – range between 300 – 10km depending on antenae) offering cableless wireless connectivity allowing remote sensor situation with no associated network costs.
At data layer WebSockets and MQTT for IOT devices are challenging serial protocols as defacto lightweight, reliable & easy to implement transport relays.
Apart from range and connectivity goals of low power consumption for efficient and long battery running time combined with solar charging enable devices to run standalone for long periods.
Weather Stations have applications beyond meteorology in smart agriculture, industrial, safety monitoring and for wind or wave based leisure pursuits.
More generally Internet of things wireless networked smart sensor platforms can be used for many purposes and combined with AI and Machine Learning algorithms useful insight and patterns within data can be analysed, classified and predicted.
Personally, I really enjoyed SparkFun Arduino LilyPad e-textile, smart fabrics and conductive thread kit, so looking forward to now spinning up the Weather Station sensors!
Binary wire protocols are long established for embedded machine to machine (M2M) communication, network applications and wireless radio data transmission.
Internet of Things (IOT) devices, real time sensors, robotics, smart home of industrial machine control data also demand efficient, low latency & lightweight data communications.
WebSockets ( RFC6455 ) protocol brings native support for binary framed messaging to web browser clients, offering a compact lightweight format for fast and efficient endpoint messaging.
Why use binary format data messaging?
Compared to serialisation of more complex text based wire formats, binary is lightweight and requires minimal storage / bandwidth and processing.
In case of high performance applications supporting a large number of clients or very high frequency of data exchange, minimising data size, bandwidth and processing becomes an important priority.
Taking as a simple example an embedded ESP8266 WiFi device, message gateway and web browser client, data serialisation and bidirectional binary framed WebSocket data exchange are demonstrated.
ESP8266 Byte Array Serialisation
Internally data is represented in embedded microcontrollers as ones and zeros, sequences of bits arranged in addressable memory.
Higher level programming language abstraction provides human readable textual labels and in case of C/C++ associated type information.
Lets define a mixed type data structure that could be some kind of sensor or message data payload –
// define mixed type data struct
struct Data
{
int id;
float v1;
float v2;
unsigned long v3;
char v4[20];
};
struct Data data;
// populate data values
data.id = 67;
data.v1 = 3.14157;
data.v2 = -7.123;
unsigned long ts = millis();
data.v3 = ts;
char c[20] = "N NE E SE S SW W NW";
strncpy(data.v4, c, 20);
To access underlying bytes, a pointer to data structure address is created –
Data pointer and length are passed to WebSocket send method “webSocket.sendBIN()”, byte range is read, packaged (framed) according to protocol specification and written to TCP/IP network socket.
Hexidecimal and Binary text representation of in memory data structure can also be displayed –
A Python3 middleware hosts WebSocket server and acts as a message relay gateway.
Binary WebSocket messages can be decoded in Python, the struct module performs conversions between Python data types and C structs –
async def wsApi(websocket, path):
try:
async for message in websocket:
print('User-Agent: '+ websocket.request_headers['User-Agent'])
print('Sec-WebSocket-Key: '+websocket.request_headers['Sec-WebSocket-Key'])
print('MessageType: '+str(type(message)))
print(message);
print('Hex: '+message.hex());
if isinstance(message, (bytes, bytearray)):
i = message[:4];
print(i);
tuple_of_data = struct.unpack("i", i)
print(tuple_of_data)
tuple_of_data = struct.unpack_from("f", message, 4)
print(tuple_of_data)
tuple_of_data = struct.unpack_from("f", message, 8)
print(tuple_of_data)
tuple_of_data = struct.unpack_from("i", message, 12)
print(tuple_of_data)
tuple_of_data = struct.unpack_from("20s", message, 16)
print(tuple_of_data[0])
## forward message
await asyncio.wait([user.send(message) for user in USERS])
To index into byte array and read a number of bytes according to data type being unpacked Python’s array slice method “i = message[:4]” can be used where [<from>:<to>] specifies start/end positions.
Method struct.unpack_from() is another approach, taking as parameters a format character specifying data type (“i” – integer, “f” – float), data buffer and an index (in bytes) to read from.
Here is decoded binary message output including some WebSocket headers –
User-Agent: arduino-WebSocket-Client
Sec-WebSocket-Key: zoJ0aR/5XunSvEKKcUkWfQ==
MessageType: <class 'bytes'>
b'C\x00\x00\x00|\x0fI@\x9e\xef\xe3\xc0\xb9\x17\x00\x00N NE E SE S SW W NW\x00'
Hex: 430000007c0f49409eefe3c0b91700004e204e45204520534520532053572057204e5700
b'C\x00\x00\x00'
(67,)
(3.1415700912475586,)
(-7.123000144958496,)
(6073,)
b'N NE E SE S SW W NW\x00'
Web Browser – Binary Encode/Decode in JavaScript
In web browser, JavaScript primitives Blob, ArrayBuffer and TypedArray perform a similar conversion.
Firstly, received WebSocket messages (event object) can be debugged to console –
websocket.onmessage = function (event) {
console.log(event);
Binary framed data payload is reported as type “Blob” (raw data) of length 36 bytes –
To de-serialise message, raw data Blob is converted asynchronously using FileReader API to ArrayBuffer, a generic fixed length binary data buffer –
if (event.data instanceof Blob) // Binary Frame
{
// convert Blob to ArrayBuffer
var arrayPromise = new Promise(function(resolve) {
var reader = new FileReader();
reader.onloadend = function() {
resolve(reader.result);
};
reader.readAsArrayBuffer(event.data);
});
When promise is fulfilled, ArrayBuffer can be read using typed views (Uint32Array, Uint32Array) for integer (including long) and float types, TextDecoder API is used to decode character array –
arrayPromise.then(function(buffer) {
// Decoding Binary Packed Data
// int (4 bytes)
var arrInt = new Uint32Array(buffer);
var id = arrInt[0];
console.log("id:"+id);
// 2x float (4 bytes)
var arrFloat = new Uint32Array(buffer,4);
var v1 = arrFloat[0];
var v2 = arrFloat[1];
console.log("v1: "+v1);
console.log("v2: "+v2);
// long (4 bytes)
var v3 = arrInt[3];
console.log("v3:"+v3);
// character data (20 bytes)
var uint8Array = new Uint8Array(buffer,16);
var string = new TextDecoder("utf-8").decode(uint8Array);
console.log(string);
});
JavaScript Binary Data Encoding
Binary data can also be encoded from native JavaScript. TypedArrays created for each data type – integer, float, long and character array are populated and packed into an ArrayBuffer suitable for use as WebSocket data payload –
console.log("Binary Encode example");
// Binary Encode example
var buffer = new ArrayBuffer(36)
var arrInt = new Uint32Array(buffer, 0, 1);
arrInt[0] = 67;
var arrFloat = new Float32Array(buffer, 4, 2);
arrFloat[0] = 3.14157;
arrFloat[1] = -7.123;
var arrInt2 = new Uint32Array(buffer, 12, 1);
arrInt2[0] = Date.now();
var uint8Array = new Uint8Array(buffer,16);
var charBuffer = new TextEncoder("utf-8").encode("N NE E SE S SW W NW");
for(var i = 0; i<charBuffer.length; i++)
{
uint8Array[i] = charBuffer[i];
}
// send binary data
websocket.send(buffer);
At message gateway, logs demonstrate parity between data packed by embedded device and those sent from web browser client –
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
Sec-WebSocket-Key: 1TD9Zp71cMTivUbj+QSx5w==
MessageType: <class 'bytes'>
b'C\x00\x00\x00|\x0fI@\x9e\xef\xe3\xc0\x07\x1c\xff\x91N NE E SE S SW W NW\x00'
Hex: 430000007c0f49409eefe3c0071cff914e204e45204520534520532053572057204e5700
b'C\x00\x00\x00'
(67,)
(3.1415700912475586,)
(-7.123000144958496,)
(-1845552121,)
b'N NE E SE S SW W NW\x00'
Limitations / Drawbacks
Compared to UTF-8 text formats (XML, JSON) packed binary data has significant disadvantages –
legibility – text based key/value formats are easy to read, manipulate and maintain
fixed frame boundaries – using positional byte sequence indexes means even small changes to message structure, size or field position require updates to consumer client code
endianess / alignment / padding must be maintained consistently, compiler and platform implementation differences may occur
Security
WebSockets Secure (WSS) offers transport layer security (TLS) to encrypt data streams. An authentication and authorisation strategy (challenge/response password, token or certificate based) for client identification should also be deployed. Cryptographic message digest signing or encryption might also be used as extra protection for critical data.
Internet of things devices can be networked wirelessly over internet and integrate easily with cloud, mobile and web applications.
WebSockets ( RFC6455 ) run on a variety of platforms including embedded systems and web browsers enabling low latency bidirectional binary socket communication over TCP/IP.
ESP8266 is a low cost Wi-Fi micro-controller compatible with Arduino open-source electronics framework designed for networked sensor, robotics and micro-electronic control applications.
WebSocket MicroServo
In a simple example a micro-servo reports position data and is controlled via a potentiometer (rotary encoder), web and mobile interfaces.
This tutorial demonstrates:
Microservo / Potentiometer GPIO control
ESP8266 WebSocket Client
Binary format Messaging
Python WebSocket Library to synchronise state between clients
Responsive HTML5 / Javascript Browser Control Interface
Microservo power is provided by an external regulated 5v DC power supply sharing a common ground connection to microcontroller.
Servo library initialisation and global variables –
#include <Servo.h>
int servoPin = 15;
int angle; // current angle (degrees)
int servoStartAngle = 90; // initial position
int limit = 90; // range of servo in degrees
Servo Servo1;
// in setup()
Servo1.attach(servoPin);
rotateServo(0);
Servo position is set in a function by passing angle in degrees as a parameter –
Potentiometer, a type of incremental rotary encoder based on variable resistance, allows relative rotary motion to be tracked, 2 out-of-phase output channels indicate direction of travel.
#define outputA 12 // Rotary Encoder #1 CLK
#define outputB 13 // Rotary Encoder #2 DT
int counter = 0; // rotary encoder incremental position
int aState; // rotary encoder state comparator
int aLastState;
void setup() {
pinMode(outputA,INPUT);
pinMode(outputB,INPUT);
aLastState = digitalRead(outputA);
}
Rotary encoder CLK and DT pins are compared to determine direction of rotation. Calling map() converts counter (relative position) to angle –
void readRotaryEncoder()
{
aState = digitalRead(outputA);
if (aState != aLastState){
// If the outputB state is different to the outputA state, that means the encoder is rotating clockwise
if (digitalRead(outputB) != aState) {
if (counter < limit)
{
counter ++;
angle = map(counter, -90, 90, 0, 180);
rotateServo(angle);
}
} else {
if (counter + limit > 0)
{
counter --;
angle = map(counter, -90, 90, 0, 180);
rotateServo(angle);
}
}
}
aLastState = aState;
}
Websocket Binary Message Framing
WebSocket protocol natively supports binary framed messaging, offering a compact lightweight format for fast and efficient endpoint messaging.
To report and update microservo position command messages are passed between control interfaces and microcontroller as binary data.
A Python service running on server creates WebSocket and maintains and synchronises shared application state between connected clients.
In web browser user interface, Javascript also has native support for (un)packing binary data.
Data Serialisation
A C struct data_t encapsulates two fields – “cmd” (unit8_t) and “value” (int) – two commands are defined, one to report servo position and another to set a new position, value represents an angle.
To assist serialisation data_t is wrapped in a packet union –
// data message
typedef struct data_t
{
uint8_t cmd;
int value;
};
// message packaging / envelope
typedef union packet_t {
struct data_t data;
uint8_t packet[sizeof(struct data_t)];
};
#define PACKET_SIZE sizeof(struct data_t)
#define CMD_SERVO_ANGLE 12 // command to report servo position
#define CMD_SERVO_ROTATE 13 // move servo to a specified position
Instances of data structs are created representing send and receive messages.
// send / receive msg data structures
union packet_t sendMsg;
union packet_t receiveMsg;
// messaging function prototypes
void readByteArray(uint8_t * byteArray);
void writeByteArray();
void printByteArray();
// buffer
uint8_t byteArray[PACKET_SIZE];
Methods allow struct data to be written and read from byte array buffer –
// read bytes from buffer
void readByteArray(uint8_t * byteArray)
{
for (int i=0; i < PACKET_SIZE; i++)
{
receiveMsg.packet[i] = byteArray[i];
}
}
// write data to buffer
void writeByteArray()
{
for(int i=0; i<PACKET_SIZE; i++)
{
// msg into byte array
byteArray[i] = sendMsg.packet[i];
}
}
ESP8266 Wifi / Websocket Setup
Details for setting up ESP8266 wifi can be found here.
ESP8266 Websocket library is added by including headers, defining server IP address / port / path and creating a WebSocketsClient class instance.
void setup() {
...
// server address, port and URL
webSocket.begin(ws_server, ws_port, ws_path);
// event handler
webSocket.onEvent(webSocketEvent);
// use HTTP Basic Authorization (optional)
//webSocket.setAuthorization("user", "Password");
// try again if connection has failed
webSocket.setReconnectInterval(5000);
WebSocket – Message Receive
Event of type “WStype_BIN” defines handling when a binary format message is received, size is reported and hexdump() displays message contents –
In setServoPosition() received byte array is de-serialised into message data structure. Angle field is used to update servo and potentiometer position.
Servo position is reported by populating sendMsg data structure, converting to byte array and calling webSocket.sendBIN() passing a pointer payload data and size.
// send servo position to Websocket server
void sendServoPosition()
{
sendMsg.data.cmd = CMD_SERVO_ANGLE;
sendMsg.data.value = angle;
// write message to buffer
writeByteArray();
webSocket.sendBIN(byteArray, PACKET_SIZE);
}
Server Side – Python WebSocket (WS) Library
A simple WebSocket server implemented in Python v3x is tasked with text / binary message exchange, tracking connected clients and maintaining shared application state.
WebSocket server event loop is started by passing function/method name, IP address and port –
Python struct library allows packed binary bytes representing C structs to be unpacked as native python data types.
Format specifier “Ii” represents a message containing an int and unsigned int.
try:
async for message in websocket:
print('Sec-WebSocket-Key: '+websocket.request_headers['Sec-WebSocket-Key'])
print('MessageType: '+str(type(message)))
print(message);
if isinstance(message, (bytes, bytearray)):
tuple_of_data = struct.unpack("Ii", message)
cmd = tuple_of_data[0]
value = tuple_of_data[1]
STATE["value"] = value
await notify_state()
except Exception as e:
print(e);
finally:
await unregister(websocket)
Connected WebSocket clients are push notified (synchronised) when position data (state) is updated –
async def notify_state():
if USERS:
binary_data = struct.pack("Ii", 12, STATE['value'])
await asyncio.wait([user.send(binary_data) for user in USERS])
WebSocket Browser Client – Binary Messaging in JavaScript
Web Interface is responsive and runs in mobile and web browser clients.
A radial D3.js radial gauge displays current angle. HTML5 buttons (divs) and slider control allow position to be updated.
JavaScript WebSocket onmessage function handles decoding of binary framed data.
A FileReader object is used to convert Blob to ByteArray in an asynchronous function, with flow control provided by a promise (future). TypedArray Uint8Array is used to extract 4 byte int and unsigned int command and value data fields –
websocket.onmessage = function (event) {
if (event.data instanceof Blob) // Binary Frame
{
async function readBinaryData(blob) {
let promise = new Promise((res, rej) => {
var fileReader = new FileReader();
fileReader.onload = function(event) {
var arrayBuffer = event.target.result;
res(arrayBuffer);
};
fileReader.readAsArrayBuffer(blob);
});
// wait until the promise returns us a value
let arrayBuffer = await promise;
var v = new Uint8Array(arrayBuffer);
// v[0] = cmd, v[4] = value
//console.log(v[0] + " " + v[4]);
// update UI elements
value.textContent = v[4];
angleSlider.value = v[4];
gauges.forEach(function(g) {
g.gauge.update(v[4]);
});
};
readBinaryData(event.data);
Conclusion
WebSockets offer significant advantages over HTTP request/response polling techniques for real time data exchange, principally overhead of opening connection should occur only once per client.
Message push notify model, lightweight protocol framing, client libraries for embedded devices and native support in modern web browsers make this protocol well suited to real time Internet of Things data message exchange.
While underlying TCP provides message ordering and re-transmission (of failed packets), higher level application abstractions: guaranteed message delivery (at least once, at most once), message acknowledgements and queue / persist / forward (to offline clients) are not specified by WebSocket specification.
Similarly, TLS SSL can be used at network layer to encrypt data transmission (WebSocket Secure WSS), but client authentication / authorisation is not handled by WebSocket protocol, meaning a strategy for token or key based client identification (OAuth for example) must be considered for secure use cases.
Event driven implementations supporting asynchronous non-blocking IO result in efficient, well structured and modular code with handlers dedicated to specific tasks.
Factors influencing choice between binary framing and text format messages (field delimeted, JSON) include legibility, convenience, compactness and parsing overhead. Binary format introduces complexity due to differences in compiler / platform / network architectures and data type implementations between programming languages.
Necessary cookies help make a website usable by enabling basic functions like page navigation and access to secure areas of the website. The website cannot function properly without these cookies.
We do not use cookies of this type.
Marketing cookies are used to track visitors across websites. The intention is to display ads that are relevant and engaging for the individual user and thereby more valuable for publishers and third party advertisers.
We do not use cookies of this type.
Analytics cookies help website owners to understand how visitors interact with websites by collecting and reporting information anonymously.
We do not use cookies of this type.
Preference cookies enable a website to remember information that changes the way the website behaves or looks, like your preferred language or the region that you are in.
We do not use cookies of this type.
Unclassified cookies are cookies that we are in the process of classifying, together with the providers of individual cookies.
We do not use cookies of this type.
Cookies are small text files that can be used by websites to make a user's experience more efficient. The law states that we can store cookies on your device if they are strictly necessary for the operation of this site. For all other types of cookies we need your permission. This site uses different types of cookies. Some cookies are placed by third party services that appear on our pages.