TinyPostman

TinyPostman is a REST-like protocol for reading and writing data resources from microcontrollers via a serial port. It is based on the TinyPacks data serialization format and supports the REST methods GET, PUT, POST and DELETE.

The current C++ implementation for Arduino and Atmel AVR microcontrollers uses about 1.5 KB of Flash and 48 bytes of RAM.

Frame format

The TinyPostman requests and responses are encoded as a sequence of TinyPacks values as follow:

Request

< method:Integer >  < token:Integer >  < path:String >  [ payload:* ]

Response

< code:Integer >  < token:Integer >  [ payload:* ]

References

method:  An integer value representing one of the following methods:

            1   Get
            2   Post
            3   Put
            4   Delete

token:   An integer value used as ID for the request and treated as
         opaque value when processing the request.

path:    The path of the addressed resource encoded as an UTF-8 string.

code:    An integer value representing one of the following response
         codes:

            0x21    201 Created
            0x22    202 Deleted
            0x24    204 Changed
            0x25    205 Content
            0x40    400 Bad Request
            0x41    401 Unauthorized
            0x43    403 Forbidden
            0x44    404 Not Found
            0x45    405 Method Not Allowed
            0x4D    413 Request Entity Too Large

payload: An optional payload encoded as one or more TinyPacks values.

Code examples

Python

from tinypostman import *

pm = tinypostman.Postman("/dev/ttyUSB0")

response = pm.get("")
if response[0] == TPM_205_Content:
    resources = response[1]
    for resource in resources:
        print(resource + ":")
        response = pm.get(resource)
        if response[0] == TPM_205_Content:
            print(repr(response[1]))
        else:
            print("Cannot get the %s resource." % resource)
else:
    print("Cannot get the resource index.")

Arduino

#include <TinyPostman.h>

#define   MAX_PACKET_LENGTH 128 
uint8_t   pack_buffer[MAX_PACKET_LENGTH];

Postman   postman;
Framer    framer(pack_buffer, MAX_PACKET_LENGTH);

class LED : public Resource {
  uint8_t  led_pin;
public:
  LED() { led_pin = 13; pinMode(led_pin, OUTPUT); };

  uint8_t get(Request &request) {
    request.writer.openMap();
    request.writer.putString("pin");
    request.writer.putInteger(led_pin);
    request.writer.putString("state");
    request.writer.putBoolean(digitalRead(led_pin));
    request.writer.close();
    return TPM_205_Content;
  }

  uint8_t put(Request &request) {
    request.reader.next();
    if(request.reader.openMap()) {
      while(request.reader.next()) {
        if     (request.reader.match("pin"))     { led_pin = request.reader.getInteger(); pinMode(led_pin, OUTPUT); }
        else if(request.reader.match("state"))   digitalWrite(led_pin, request.reader.getBoolean());
        else request.reader.next();        
      }
      request.reader.close();
      return TPM_204_Changed;
    }
    else
      return TPM_400_Bad_Request;
  }
} led_resource;


void setup()
{
  postman.registerResource("led", led_resource);
  Serial.begin(9600);
}

void loop()
{
  if(framer.getState() == TPM_SENDING)
    Serial.write(framer.getByteToSend());
  while(framer.getState() == TPM_RECEIVING && Serial.available() > 0) {
    if(framer.putReceivedByte(Serial.read()) && framer.getLength()) {
      framer.setLength(postman.handlePack(pack_buffer, framer.getLength(), MAX_PACKET_LENGTH));
      framer.setState(TPM_SENDING);
      break;
    }
  }
}

Source code

Get the source code from TinyPacks repository on GitHub

TinyPacks

TinyPacks is data serialization format for constrained environments like 8-bit and 16-bit microcontrollers. It was designed to achieve these goals:

  • Easy to traverse and to skip entire nested elements.
  • Small serializated data footprint.
  • Small encoder/decoder memory footprint.
  • In-place parsing using static memory allocation.
  • Easy translation to and from JSON

The current C++ implementation for Arduino and Atmel AVR microcontrollers uses about 2 KB of Flash and 9 bytes of RAM plus 14 bytes of RAM per nesting level in the source data.

Data types

None        Null value.
Boolean     Boolean True or False value.
Integer     Signed integer 8, 16, 32 or 64 bits long.
Real        Floating point number 32 or 64 bits long.
String      Variable length string of characters encoded as UTF-8.
Bytes       Variable length raw sequence of bytes.
List        Sequence of values.
Map         Sequence of key-value pairs.

Serialization format

Specification syntax

|    Alternative
[ ]  Repetition, zero or more elements 
[n]  Repetition, n elements
:    Length in bits, element:length
*    Arithmetic multiplication
@    Native C type using network byte order
-    Range
#    Define parameter, element#name

Format specification

document =       element[]

element =        none | boolean | integer | real | string | bytes | list | map

none =           0:3   0:5

boolean =        false | true
false =          1:3   0:5
true =           1:3   1:5          1:8

integer =        integer0 | integer8 | integer16 | integer32 | integer64
integer0 =       2:3   0:5
integer8 =       2:3   1:5          @int8_t:8
integer16 =      2:3   2:5          @int16_t:16
integer32 =      2:3   4:5          @int32_t:32
integer64 =      2:3   8:5          @int64_t:64

real =           real0 | real32 | real64
real0 =          3:3   0:5
real32 =         3:3   4:5          @float:32
real64 =         3:3   8:5          @double:64

string =         small_string | medium_string | big_string
small_string =   4:3   0-0x1E#n:5   @char[n]:8*n
medium_string =  4:3   0x1F:5       0-0xFFFE#n:16             @char[n]:8*n
big_string =     4:3   0x1F:5       0xFFFF:16                 0-0xFFFFFFFE#n:32         @char[n]:8*n

bytes =          small_bytes | medium_bytes | big_bytes
small_bytes =    5:3   0-0x1E#n:5   @uint8_t[n]:8*n
medium_bytes =   5:3   0x1F:5       0-0xFFFE#n:16             @uint8_t[n]:8*n
big_bytes =      5:3   0x1F:5       0xFFFF:16                 0-0xFFFFFFFE#n:32         @uint8_t[n]:8*n

list =           small_list | medium_list | big_list
small_list =     6:3   0-0x1E#n:5   element[]:8*n
medium_list =    6:3   0x1F:5       0-0xFFFE#n:16             element[]:8*n
big_list =       6:3   0x1F:5       0xFFFF:16                 0-0xFFFFFFFE#n:32         element[]:8*n

map =            small_map | medium_map | big_map
small_map =      7:3   0-0x1E#n:5   (element element)[]:8*n
medium_map =     7:3   0x1F:5       0-0xFFFE#n:16             (element element)[]:8*n
big_map =        7:3   0x1F:5       0xFFFF:16                 0-0xFFFFFFFE#n:32         (element element)[]:8*n

Serialization examples

Python                                      TinyPacks
------                                      ---------
None                                        00
0                                           40
123                                         7b
4567                                        42 11 d7
8.9                                         64 41 0e 66 66
0.0                                         60
True                                        21 01
False                                       20
"ABC"                                       83 41 42 43
"hello world!"                              8c 68 65 6c 6c 6f 20 77 6f 72
                                            6c 64 21
"A string longer than 30 characters."       9f 00 23 41 20 73 74 72 69 6e
                                            67 20 6c 6f 6e 67 65 72 20 74
                                            68 61 6e 20 33 30 20 63 68 61
                                            72 61 63 74 65 72 73 2e
b"\x01\x02\x03"                             a3 01 02 03
[1, 2, 3]                                   c6 41 01 41 02 41 03
[4, True, "fun"]                            c8 41 04 21 01 83 66 75 6e
{"a": 1, "b": False, "c": "foo"}            ed 81 61 41 01 81 63 83 66 6f
                                            6f 81 62 20
{"foo": [1, 2], "bar": {True: 3, False: 4}} f5 83 66 6f 6f c4 41 01 41 02
                                            83 62 61 72 e7 20 41 04 21 01
                                            41 03

Code examples

Python

import tinypacks

data = { "message": "Hello world!", "status": True, "count": 123 }
print("Data: " +  repr(data))

packed_data = tinypacks.pack(data)
print("Packed: " + repr(packed_data))

(unpacked_data, remaining_data) = tinypacks.unpack(packed_data)
print("Unpacked: " + repr(unpacked_data))

Arduino

#include <TinyPacks.h>

PackWriter writer;
PackReader reader;

#define MAX_PACKED_DATA 256
unsigned char packed_data[MAX_PACKED_DATA];
int packed_data_length;

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  #define MAX_TEXT_LENGTH 32
  char text[MAX_TEXT_LENGTH] = "";
  bool status = false;
  int  count = 0;  

  // Pack
  writer.setBuffer(packed_data, MAX_PACKED_DATA);
  writer.openMap();
  writer.putString("text");
  writer.putString("Hello world!");
  writer.putString("status");
  writer.putBoolean(true);
  writer.putString("count");
  writer.putInteger(123);
  writer.close();
  packed_data_length = writer.getOffset();

  // Unpack
  reader.setBuffer(packed_data, packed_data_length);
  reader.next();
  if(reader.openMap()) {
    while(reader.next()) {
      if     (reader.match("status"))  status = reader.getBoolean();
      else if(reader.match("count"))   count = reader.getInteger();
      else if(reader.match("text"))    reader.getString(text, MAX_TEXT_LENGTH);
      else reader.next();
    }
    reader.close();
  }

  // Print unpacked data
  Serial.println("Map content:");
  Serial.print("  text: ");
  Serial.println(text);
  Serial.print("  status: ");
  Serial.println(status);
  Serial.print("  count: ");
  Serial.println(count);
  Serial.println();

  delay(5000);  
}

Source code

Get the source code from the TinyPacks repository on GitHub

Luminch One


Luminch One is an interactive lamp controlled by the movements of your hand. Wave your hand over it to turn it on or off, or move your hand up or down above it to change its brightness. Inside the lamp, an Arduino hooked to an infrared distance sensor tracks your hand and sets the state and the brightness of the LED lamp.

Get the source code, schematics and folding pattern (395 KB)

Luminch One repository at GitHub

View the step-by-step guide to build your own Luminch One

Low-tech Magazine: The bright future of solar powered factories

Most of the talk about renewable energy is aimed at electricity production. However, most of the energy we need is heat, which solar panels and wind turbines cannot produce efficiently. To power industrial processes like the making of chemicals, the smelting of metals or the production of microchips, we need a renewable source of thermal energy. Direct use of solar energy can be the solution, and it creates the possibility to produce renewable energy plants using only renewable energy plants, paving the way for a truly sustainable industrial civilization.