2017-07-05

RF Temperature Sensor

This is the description of a sensor board that measures the temperature and transmits it over 433.92MHz radio. It uses the protocol for the weather1 sensor in pimatic/rfcontroljs [1]. Four separate temperature sensors can be connected to the same board.

Components

Arduino Pro Mini 5V [2]
TX433N Transmitter [3]
KTY84/150 Temperature Sensor [4]
1 kOhm Resistor
Coil Loaded Antenna [5]
9V Battery Connector
2x 1 MOhm Resistor

Assembly

The Arduino is the backbone of the construction. Connect the battery connector to RAW and GND.

Analog 4 is used to monitor the battery health. The two 1 MOhm resistors form a voltage divider to get below 5V on the analog input. Connect one resistor between RAW and Analog 4. Connect the other resistor between Analog 4 and GND.

The KTY84 and the 1 kOhm resistor forms a voltage divider. KTY84 connects to GND and one of Analog 0, 1, 2 or 3. The resistor connects to the same Analog and VCC.

TX433N is powered by VCC and GND on the Arduino. Connect TX433N Data to Digital 2.

Connect the coil loaded antenna to Ant.

Configuration

The board is configured with messages sent on the serial port (115200 bps). The following values can be set.

X0: Exact resistance of the 1 kOhm Resistor
X1: Exact resistance of KTY84 at zero degrees Celsius
02: Id 0-255 (weather1)
03: Measurement Interval 0-32767 (ms)
04: Low battery warning level 0-1023

where X selects one of the analog inputs 0, 1, 2 or 3. Each sensor transmit on separate weather1 channels 1, 2, 3 and 4. All sensors have the same id and measurement interval.

Example: To set the analog in 3 KTY84 zero degree resistance to 509 Ohm, send the following message, ending with newline.

31:509

The temperature measured by this KTY84 will transmit on channel 4.

Calibration

To calibrate the sensor, first measure the resistance of the 1 kOhm resistor and write the value to config X0. Then adjust config X1 until the measured temperature matches the value of a reference temperature. The sensor is disabled when X0 is zero.

References

[1] https://github.com/pimatic/rfcontroljs
[2] https://www.arduino.cc/en/Main/ArduinoBoardProMini
[3] https://www.velleman.eu/products/view/?id=350619
[4] http://www.nxp.com/docs/en/data-sheet/KTY84_SER.pdf
[5] http://www.instructables.com/id/433-MHz-Coil-loaded-antenna

Source Code

/*
  Copyright (C) 2017  Marcus Andersson

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see .
*/

#include "EEPROM.h"

// Digital
int ledPin = 13;
int txPin = 2;

// Analog
int thermoPin[4] = {0, 1, 2, 3};

// Pulse period time, 10 microsecond steps
#define PT 48

// Number of config values
#define NCONFIG 32

// EEPROM stored configuration data
struct Config {
  int data[NCONFIG];
};
Config config;

#define BUFLEN 100
byte packet[BUFLEN];
byte length = 0;

void printConfig() {
  for (int i = 0; i < NCONFIG; i++) {
    Serial.print(i);
    Serial.print(": ");
    Serial.println(config.data[i]);
  }
}

void setConfig(int i, int val) {
  config.data[i] = val;
  EEPROM.put(0, config);
}

void setup()
{
  pinMode(ledPin, OUTPUT);
  pinMode(txPin, OUTPUT);
  Serial.begin(115200);
  EEPROM.get(0, config);
  printConfig();
}

void send(int temp, byte id, byte ch) {
  int i, j;
  unsigned long t0;
  unsigned long periods;
  byte data[36] = {
    0, 1, 0, 1,
    1, 1, 0, 1, 0, 0, 0, 0, // id
    1, 1, // batt
    0, 1, // channel
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // temp
    0, 0, 0, 0, 0, 0, 0, 0 // hum
  }; 
 
  // Write id
  for (i = 0; i < 8; i++) {
    data[4+i] = (id >> (7-i)) & 1;
  }
 
  // Write channel
  data[14] = (ch >> 1) & 1;
  data[15] = (ch >> 0) & 1;
 
  // Write temp
  data[16] = (temp < 0) ? 1 : 0;
  for (i = 1; i < 12; i++) {
    data[16+i] = (temp >> (11-i)) & 1;
  }
 
  digitalWrite(ledPin, HIGH);   // sets the LED on

  periods = 0;
  t0 = micros();
 
  digitalWrite(txPin, HIGH); periods += 8;
  delayMicroseconds(max(10000, t0 + periods*PT - micros()));
  digitalWrite(txPin, LOW); periods += 230;
  delayMicroseconds(max(10000, t0 + periods*PT - micros()));

  for (j = 0; j < 4; j++) {
    for (i = 0; i < 36; i++) {
      digitalWrite(txPin, HIGH); periods += 8;
      delayMicroseconds(max(10000, t0 + periods*PT - micros()));
      digitalWrite(txPin, LOW); periods += 4*10 + 4*11*data[i];
      delayMicroseconds(max(10000, t0 + periods*PT - micros()));
    }

    digitalWrite(txPin, HIGH); periods += 8;
    delayMicroseconds(max(10000, t0 + periods*PT - micros()));
    digitalWrite(txPin, LOW); periods += 230;
    delayMicroseconds(max(10000, t0 + periods*PT - micros()));
  }
  digitalWrite(ledPin, LOW);    // sets the LED off
}

void loop()
{
  static unsigned long sendTime = millis() - config.data[3];
 
  if (millis() - sendTime > config.data[3]) {
    sendTime += config.data[3];
    for (long ch = 0; ch < 4; ch++) {
      long r0 = config.data[ch*10 + 0];
      long r1 = config.data[ch*10 + 1];
      long id = config.data[2];
      long s = analogRead(thermoPin[ch]);
      long r = r0*s/(1023-s);
      int t = (r-r1)*2;
     
      if (r0 == 0) {
        continue;
      }
     
      send(t, id, ch);
   
      Serial.print(ch);
      Serial.print(" ");
      Serial.print(s);
      Serial.print(" ");
      Serial.println(t);
    }
  }

  while (Serial.available() > 0) {
    int d = Serial.read();
    packet[length++] = d;
    if (d == 10 || length >= BUFLEN) {
      int p = 0;
      int i = packet[p++] - '0';
      i = i*10 + packet[p++] - '0';
      if (i >= 0 && i < NCONFIG) {
        int v = 0;
        int d = packet[p++];
        d = packet[p++];
        while (d >= '0' && d <= '9') {
          v = v*10 + d - '0';
          d = packet[p++];
        }
        setConfig(i, v);
        printConfig();
      }
      length = 0;
    }
  } 
}


2017-06-26

ASK Baseband Decoder (433.92MHz)

/*
ASK Baseband Decoder  Copyright (C) 2017  Marcus Andersson

This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions; GPLv3.

This is a proof of concept 433MHz ASK baseband decoder for messages sent by
remote controls, weather stations, car keys and so on. It is written for Arduino,
but the algorithm can run on anything that has GPIO IN with interrupt and a
microsecond time function.

You need a 433Mhz receiver. There are plenty of cheap receivers on the market [1].
A good, compact antenna that I recommend is the DIY coil loaded antenna [2].

|   Receiver       Arduino
8  ----------     -----------
+--|Ant Data|-----|D2     TX|--- Serial Out
   |      V+|-----|5V    Raw|--- 5-12V
   |      V-|-----|Gnd   Gnd|--- Gnd
   ----------     -----------

Connect the data out from the 433MHz receiver to digital 2 on Arduino. Upload
this program using the Arduino IDE. Open a serial terminal set to 115200 bps
to start message reception. The output format is similar to homeduino [3],
with a list of microsecond intervals followed by the message which consists
of indexes referencing the list.

Without an ongoing transmission, the receiver will pick up noise. We want
to ignore noise and only detect proper messages. A proper message consists
of a sequence of high/low signal pairs. The signals varies between 1 to N
periods in length. A period is around 300 to 600 microseconds.

1_  _
0_ | |_.       = 11

1_  _
0_ | |_._.     = 12

1_  _._
0_ |   |_._._. = 23

...and so on.

A low signal that is longer than N periods is a sync. The high signal sets
the period time for the message. A sync is sent before and after a message.
The sync signal can be shared by two adjacent messages, which means it marks
both the end of the first message and the start of the next.

1_  _
0_ | |_._._._._._._._._._._._._._._._._._._._._._. = sync

When a sync signal is detected the message recording starts. As long as no signal
has a shorter duration than half a period, the reception continues until a new sync
signal is detected. There is a minimum length for a proper message and there is
also a minimum period time. This lowers the risk of interpreting noise as proper messages.

Incoming messages are written to a circular buffer by the interrupt routine. If
the reception buffer becomes full, the message being received is discarded. When
a complete message has been received the writer index is advanced to the position
after the message and the main loop can start to consume the message using the reader
index. The first datum in the buffer is the period time in microseconds. The following
data is the number of periods for all signals. The main loop transmits the message over
the serial port until (reader == writer) or until the number of periods of a datum is
larger than N, which means that a new message starts.

References
----------
[1] https://www.electrokit.com/rx433n-mottagarmodul-ask-433-9-mhz.45095
[2] https://arduinodiy.wordpress.com/2015/07/25/coil-loaded-433-mhz-antenna
[3] https://github.com/pimatic/homeduino
*/

// Circular buffer length
// Must be 256 for modulo arithmetic to work on byte index variables without using %.
#define BUF_LEN 256

// Noise filter
#define MIN_MSG_LEN 16

// Max length of data signal. Longer signals are treated as sync.
#define MAX_SIGNAL_PERIODS 20

// Minimum signal period time for a proper message
#define MIN_PERIOD_TIME 30 // * 4 microseconds

// Remembers the time of the last interrupt
volatile unsigned int lastTime;

// Signal period time of message being received
volatile unsigned int periodTime;

// Signal counter for message being received
volatile byte streak;

// Buffer pointer where the next message will be stored
volatile byte writer;

// Buffer pointer for the main loop reader
volatile byte reader;

// Circular message buffer
volatile unsigned int msgbuf[BUF_LEN];

void setup()
{
  Serial.begin(115200);
  // Scale down time by 4 to fit in 16 bit unsigned int
  lastTime = micros() / 4;
  periodTime = 1;
  writer = 0;
  reader = 0;
  streak = 0;
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(0, isr, CHANGE);
}

void writeNum(unsigned long num, char* tail)
{
  char buf[10];
  String( num ).toCharArray(buf, 10);
  Serial.write(buf);
  Serial.write(tail);
}

unsigned int insertSort(unsigned int list[8], unsigned int val)
{
  byte i;
  for (i = 0; i < 8; i++) {
    if (list[i] == val) {
      // No duplicates
      val = list[7];
      break;
    }
    if (list[i] > val) {
      unsigned int tmp = list[i];
      list[i] = val;
      val = tmp;
    }
  }
  return val;
}

byte listpos(unsigned int list[8], unsigned int val)
{
  byte i;
  for (i = 0; i < 8; i++) {
    if (list[i] == val) {
      break;
    }
  }
  return i;
}

void loop()
{
  while (reader != writer) {
    unsigned int periodMap[8] = {-1, -1, -1, -1, -1, -1, -1, -1};
    unsigned int pt = msgbuf[reader++];
    byte prereader = reader;
    byte i;

    while (prereader != writer) {
      unsigned int periods = msgbuf[prereader++];
      if (insertSort(periodMap, periods) != -1) {
        // Too many different signals
        reader = writer;
        break;
      }
      if (periods > MAX_SIGNAL_PERIODS) {
        // End of message
        break;
      }
    }
  
    if (reader != writer) {
      for (i = 0; i < 8; i++) {
        if (periodMap[i] != -1) {
          writeNum(periodMap[i]*pt*4, " ");
        }
        else {
          writeNum(0, " ");
        }
      }
    }
  
    while (reader != writer) {
      unsigned int periods = msgbuf[reader++];
      writeNum(listpos(periodMap, periods), "");
      if (periods > MAX_SIGNAL_PERIODS) {
        // End of message
        Serial.write("\n");
        break;
      }
    }
  }
}

void isr()
{
  // Scale down time by 4 to fit in 16 bit unsigned int
  unsigned int now = micros() / 4;
  unsigned int signalTime = now - lastTime;
  unsigned int periods = (signalTime + periodTime/2) / periodTime;
  byte lowSignal = digitalRead(2);

  lastTime = now;
 
  if (periods == 0) {
    // Noise, ignore message
    streak = 0;
  }
  if (streak > 0) {
    // Receive message
    byte index = (writer + streak++); // % 256
    if (index == reader) {
      // Reception buffer is full, drop message
      streak = 0;
    }
    else {
      msgbuf[index] = periods;
    }
  }

  if (lowSignal) {
    if (periodTime > MIN_PERIOD_TIME && periods > MAX_SIGNAL_PERIODS) {
      // Sync detected
      if (streak > MIN_MSG_LEN) {
        // Message complete
        msgbuf[writer] = periodTime;
        writer = (writer + streak); // % 256
      }
      // Start new message
      streak = 1;
    }
  }
  else {
    // high signal
    if (periods > MAX_SIGNAL_PERIODS) {
      // Noise, ignore message
      streak = 0;
    }
    if (streak > 0) {
      if (periods == 1) {
        // Approximate average of single period high signals in message
        periodTime = (periodTime*streak + 2*signalTime) / (streak + 2);
      }
    }
    else {
      // Initiate search for new period time and sync
      periodTime = signalTime;
    }
  }
}

2017-04-15

Power Logger

The power meter I have in my basement has a LED that blinks once every Wh. With a consuption of 3600W it will blink once every second. By logging the clock time at every blink it is possible to get a good overview of the consuption, both momentarily and over time.

The logger I have built is based on the Raspberry Pi Zero W. The LED sensor is based on a photo resistor and an opamp. It is connected to GPIO on the Raspberry Pi. An optional 16x2 LCD display is used to display the IP address of the logger and the current power consuption. A button is used to turn on the LCD backlight. Blink timestamps are logged to file and the data can be viewed in a Web GUI.

Mechanical Design

There is not much to say about the mechanical design. The LCD is attached to the RPi with a single M4 bolt and nuts. The logger and the sensor is attached with blu-tac to the power meter. The photo resistor have long legs that are bendable and can be used to finetune its position in front of the LED.

Electrical Design

The LED sensor is designed using two GL5528 photo resistors, one LM324N operational amplifier and one 4.7kOhm potentiometer. Two photo resistors make the circuit more tolerant to varying ambient lighting conditions. The photo resistors form two voltage dividers with the left and the right side of the potentiometer. The center of each divider is connected to the two inputs, plus and minus, of the opamp. More light on a photo resistor will increase the voltage to the opamp. When the plus voltage is larger than the minus voltage the output of the opamp will go high. The circuit has two modes of operation. Use the photo resistor connected to the plus side to get a high signal when the LED blinks and use the photo resistor connected to the the minus side to get a low signal when the LED blinks. If you choose the positive option then you should use the plus side photo resistor to detect the LED and adjust the potentiometer to get a low signal when the LED is dark. A 1kOhm resistor in parallel with the minus photo resistor prevents it from activating the opamp in darkness when it goes towards infinite resistance. The circuit is powered with 5V from the RPi. The output of the opamp is connected to RPi GPIO 10.


The 16x2 LCD is powered by 5V from the RPi and connected to GPIO in the following way.

RS - GPIO 25
EN - GPIO 24
D4 - GPIO 23
D5 - GPIO 17
D6 - GPIO 21
D7 - GPIO 22
BACKLIGHT (K) - GPIO 4

More information about LCD and RPi can be found at [1].

A switch is connected to GPIO 16 and GND to be used to turn on LCD backlight.

Software Design

See the GitHub project [2].

References

[1] https://learn.adafruit.com/drive-a-16x2-lcd-directly-with-a-raspberry-pi/wiring
[2] https://github.com/TheOtherMarcus/PowerLogger

2017-04-02

Designing a Turtle

In the seventies, Seymour Papert invented LOGO as a teaching tool for computer programming, encouraging exploration and interactivity. The accompanying Turtle robot could draw sketches on paper, creating a tangible thing from the programming effort. Now, the concept has been recreated on a platform base on Raspberry Pi and Python.

The prototype Turtle

The prototype is more complicated than it has to be, mostly due to the fact that Raspberry Pi model A doesn't have built in Wifi and can't even supply a USB Wifi dongle with enough power on its own. Today the amazing Raspberry Pi Zero W can be used instead.

Development Environment

The Turtle is programmed using a Web IDE running on the robot itself.

The Integrated Development Enviroment

The IDE makes it possible to edit and run code on the Turtle. A history of executed programs are saved to make it easy to go back to an earlier version. It is also possible to save a program using a descriptive name. The console output of the running code is displayed in a separate text area to simplify debugging.

More than one person can program a single Turtle at the same time if they have one computer or tablet each and take turns running programs.

Mechanical Design

Wheels from inline skates are perfect for the Turtle. They are narrow with good friction which makes the robot turn in a tight and controlled way. To attach a wheel to a stepper motor shaft you need a mounting hub [6]. It is unlikely that you will find a hub that matches the holes on the wheels. I used a 1mm thick steel plate to adapt the mounting holes on the hub to the wheel.

Two stepper motors are used to drive the front wheels. The drawing pen needs to be centered between the motors for the Turtle to be able to turn on the spot without moving the pen. The pen should also be possible to lift to be able to move the robot without drawing a line. I found two copper plumbing parts in the local hardware store that fit perfectly together without getting stuck or fall through. I attached the pen to the narrow conical tube and placed six of the larger tubes between the motors. I strapped it all together with cable ties and used some blu-tac between the tubes to increase the friction.

On top of the engine pack I attached a 1mm thick steel plate to hold the electronics. Steel is probably not optimal for the Wifi signal and I would choose wood or plastic today.

As third supporting wheel I used a bearing ball but anything with low friction works fine.

The servo motor that is used to lift the pen is attached to a vertical steel plate that is glued to the base plate. Rubber bands push the pen down. The servo arm pulls a thread to lift the pen.

Electrical Design

The Turtle is powered by a rechargeable racing pack delivering 12V DC. To get the 5V DC that the Raspberry Pi requires, dual 5V/1A regulators [7] are used, one powering the Raspberry Pi and one powering the Wifi dongle through the USB hub.

The stepper motors are 200 steps/revolution, 12V DC. I use Easy Driver [8] to drive the stepper motors, one per motor. The STEP and DIR pins are connected to Raspberry Pi GPIO.

The servo that lifts the pen runs on 5V DC and is connected to the same converter that drives the Raspberry Pi. The signal wire is connected to Raspberry Pi GPIO.

Software Design

Install Raspbian: [1]

Setup headless networking: [2] [9]

Change keyboard layout:
sudo vi /etc/default/keyboard
sudo setupcon
sudo reboot

Install the IDE: [3]

References

[1] https://www.raspberrypi.org/documentation/installation/installing-images/linux.md
[2] https://davidmaitland.me/2015/12/raspberry-pi-zero-headless-setup/
[3] https://github.com/TheOtherMarcus/TurtleIDE
[4] https://www.raspberrypi.org/documentation/remote-access/web-server/apache.md
[5] http://abyz.co.uk/rpi/pigpio/
[6] https://www.sparkfun.com/products/10006
[7] https://www.kjell.com/se/sortiment/el-verktyg/elektronik/halvledare/spanningsregulatorer/spanningsregulator-ua7805-p90061
[8] http://www.schmalzhaus.com/EasyDriver/
[9] http://tech.scargill.net/pi-zero-wi-fi-automatic-reconnect/




2017-03-25

Makron - template notation and evaluator

Makron (swedish for macaron) is the programming language used for text-based code generation in the Biotope application server. It is a kind of template or string processing language. It is typically used for server side generation of HTML code, but it is also suitable for any text based target languages like Graphviz, JSON, CSS and Javascript. The current implementation of the interpreter is tightly coupled with the relational model described in A Distributed and Relational Data Model.

Execution starts by invoking a well known template that is stored as a Code entity in the relational model and ends when the template is completely evaluated. A Makron template has access to the invocation URL and its parameters, can iterate over the result from a query to the relational model and can download content from the net and use all of these sources when composing its text-based result. Conditional execution is driven by regex matching, which also handles the limited parsing that is possible. A future goal is to include a proper parser generator. A Makron program is typically responsible for creating the response to GET requests to a Biotope servlet.

A Makron program cannot directly modify the relational data model. Instead, it is the JS/HTML application generated by the Makron evaluator that can modify the model through the Logger and Archiver servlets in Biotope.

High Level Language Overview

  • Everything is UTF-8 encoded text.
  • A string of text can be bound to a symbol. A symbol can either represent data or template code.
  • A string of text can be evaluated, which means that template invocations that are found in the text are substituted with the result of the recursive evaluation of the template (in a new local symbol scope). Templates are invoked using a language construct called a macaron, e.g. {template_name.arg1.arg2.arg3}. The rest of the text being evaluated is left unchanged.
  • Macaron start and end chars and argument delimiter chars are dynamically exchangeable to easily adapt to the properties of the target language.
  • Template arguments can either be evaluated in the invocation context or in the template context. The template itself explicitly performs eager, lazy or no evaluation of its arguments.

Hello, world!

A trivial program with no template invocations (macarons) will just output itself as the result.

Hello, world!

>>> Hello, world!

Before we get to more advanced evaluation rules we need some definitions.

Symbols

A symbol name can use the characters a-z, A-Z, 0-9, _ and -.

This_is_a_Symbol

Macarons

The default macaron start and end chars are '{' and '}'. It is a macaron that triggers template invocation and all characters in the macaron are part of the invocation.

{This is a macaron}

It is possible to change the macaron chars dynamically in the source by putting a macaron framed with new macaron chars directly inside a macaron. Valid macaron chars are '(', ')', '{', '}', '[', ']', and '<', '>'.

{(Parenthesis are now used as macaron chars within this macaron.)}

The change is tied to the lexical scope and does not affect templates that are invoked from within the macaron.

Escaped Chars

The escape char '\' can be used to prevent the normal interpretation of characters with special meaning, for example if you want to type a macaron char but don't want to start or end a macaron. It can also be used to insert newline '\n' and tab '\t'. A backslash is written '\\'.

Delimiter Chars

A delimiter char is a character not part of any of the other categories above. It is used to separate template name and the arguments in a macaron.

Now we have what we need to go on with template evaluation.

Template Evaluation

During evaluation, the template source code is read character by character and they are typically passed on unprocessed to the output stream. Escaped characters are passed on to the output stream without the escape char '\'.

Plain \{text\}

>>> Plain {text}

Template Invocation

When a macaron is encountered it is not passed on to the output stream. Instead, template invocation is initiated. First, a template name followed by a delimiter char is scanned for. The process is a form of evaluation and other templates can be invoked during the scan. When a delimiter char is found, evaluation stops and the rest of the unprocessed input stream up to the end of the macaron is treated as the template argument. The source code of the new template is loaded and evaluated in a new scope with the variables self, '.', body, start and end bound to new values.

{template/with/{argument}/body}

Bindings in new evaluation scope:

self  = template
.     = /
body  = with/{argument}/body
start = {
end   = } 

Function: = (let)

Makron is dynamically scoped with all the usual drawbacks, but don't dismiss this language just yet. For small programs implementing view generation, it is quite convenient to not have to specify all necessary parameters in every template invocation. Symbols are semi-global, i.e. global in its local execution branch. The code structure with one template per file also doesn't make static scope very useful.

A symbol always refer to a stream of UTF-8 encoded text. Symbols can be defined in the local scope using the built-in function let. It will evaluate both of its arguments before binding the first argument, the symbol name, to the second argument, the value.

{let.fn1.Hello, world!}

or

{=fn1=Hello, world!}

 The content of the symbol fn1 can then be evaluated, i.e. a template invocation.

Makron says "{fn1}"

>>> Makron says "Hello, world!"

Template: ' (quote)

We will now define a second symbol containing code that calls fn1.

{=fn2={'{fn1}}}

The argument to let is always evaluated, therefore we have to prevent the premature evaluation of {fn1} with the quote template. We could also escape the bracket or temporarily changed the macaron chars with the same effect.

{let.fn2.\{fn1\}}
{let.fn2.{( {fn1})}} 

The result when evaluating fn2 is the same, as expected.

Makron still says "{fn2}"

>>> Makron still says "Hello, world!"

Function: $ (value)

If we instead want to get the content of the symbol fn2 without evaluating it, we can use the built-in function $.

{$$fn2}

>>> {fn1}

The reason for the double $$ is that $ takes two arguments and we have given an empty first argument. It is very common that strings need to be escaped in different ways when inserting them in program code. Therefore the $ function takes a first argument that specify what kind of escaping that needs to be applied before output. A number of escape schemes are defined, e.g. qoute, squote, html, url.

{$url$fn2}

>>> %7Bfn1%7D

Function: [space] (identity)

As we saw above we can use the space function as a function that just evaluates and outputs its argument. This is for example useful when we want to change macaron chars but don't want to invoke a template as the first thing.

{( {fn1})}

>>> {fn1}

Functions: first and rest

If we break down the anatomy of a template invocation we can see that it consists of a template name followed by a delimiter char and the rest is the argument. Yes, always a single argument. The template itself needs to break up the single argument into parts if it needs multiple arguments. To be able to do this it can access the delimiter char that was used in the invocation with the template '.', i.e. {.}. The unevaluated argument itself is bound to the variable body.

There are two helper functions first and rest that are useful when splitting up body. first returns the part before the first delimiter char and rest returns the part after the first delimiter char.

Function: @ (arg)

{arg.a1.{first{.}{$$body}}}
{arg.a2.{first{.}{rest{.}{$$body}}}}
{@a3@{rest{.}{rest{.}{$$body}}}}


The function arg works like let with the difference that it evaluates the second argument twice, first in the local scope and the second time in the scope of the caller. This is the mechanism that turns the template into a function with eager evaluation of its arguments.

By convention, the last argument is assigned to what is left without looking for the delimiter char, i.e. no call to first. This means that the last part of body can include the delimiter char as data without the need to escape it.

Normally, a function splits its argument at the delimiter chars and then evaluates the parts separately. This allows variables containing arbitrary strings to be passed as arguments. If the argument is first evaluated as a whole and then split at the delimiter, the arbitrary strings that are inserted into the argument may have introduced unwanted delimiter or macaron chars that will ruin the intended argument separation. first and rest are different though. They will first evaluate their argument and then look for the delimiter char.

Function: ~ (eval)

Sometimes it can be handy to be able to evaluate a string as a template without first bind it to a symbol. The function eval will evaluate its argument and then evaluate the resulting string once more.

Function: ^ (upeval)

The upeval function is a mix of eval and arg. It will evaluate its argument and then evaluate the resulting string in the scope where the current template was invoked.

Function: ? (query)

To get data from the relational model, the function query is used. The function evaluates the first argument and sends it as a sql query to the database. The second argument, when evaluated, is the code that will be evaluated for each row in the query result. The third argument, when evaluated, is the code that will be evaluated between each row. Brackets in the query will mark the symbol name to bind to each column in the result.

{query|SELECT s.n AS [num] FROM (VALUES(1),(2),(3),(4),(5)) s(n)|{'{$$num}}|, }

>>> 1, 2, 3, 4, 5

Function: % (regex)

Using regular expressions, it is possible to conditionally evaluate Makron code. When the regex matches a string, one code path is evaluated. If it doesn't match, another code path is evaluated. The whole input string must match, not just a part of it. The regex can also be used as a simple parser as regex groups can be bound to variables in Django style.

Pre-Defined Symbols

When the Makron evaluator is running in a servlet context we very much like to get the URL and its parameters to be able to respond accordingly. The following symbols are pre-defined in such a context.

contexturl - Contains the URL to the servlet engine.
rooturl - Contains the ULR to the servlet.
url - Contains the part of the URL after rooturl. Always starts with '/'.
p_xyz - Contains the value of the URL parameter xyz.

Symbols in the Relational Data Model

I mentioned earlier that the first function that was invoked would be retrieved from the relational data model. Symbols stored in the relational data model are available in the outermost scope. A symbol is represented by an entity in the relation code10c1. It is associated with a name through the chain code10c1-name11c11-text_value. The contents is stored in a web archive with an URL found through the chain code10c1-data11cn1-location02cn1-text_value.  

Function: location

For symbols stored in the relational data model, it is possible to get the URL where the template data is stored.

<script src="{location.jquery}" type="text/javascript"></script>

>>>
<script src="https://kornet1.se/archive/biotope/95a5d6b46c9da70a89f0903e5fdc769a2c266a22a19fcb5598e5448a044db4fe" type="text/javascript"></script>

Undefined Templates

Macarons that invoke an undefined template are ignored and can be used as source code comments. 

Arithmetic Operations

Ongoing... 

Input Qualification

Ongoing...

2017-01-01

Laser Trap: Dexterous Escape

Laser Trap: Dexterous Escape is a hobby project I have been working on for some time. In this post I will go though the things you will need to build your own laser course at home. The goal is to move as fast as possible through a room filled with laser beams without touching any of the beams.


To learn more about how the hardware is constructed, read Designing a Laser Trap. If you want to know how to put a trap together, see Assembling a Laser Trap.

Equipment

Laser Trap, Battery, Mirrors, Smoke, Android phone/tab, The app Dexterous Escape.

Troubleshooting

The laser and the led turns off and on. Contact with Android app is lost.

The trap is responding slowly on the commands from the Android app.

Assembling a Laser Trap

This post shows how to assemble a laser trap from parts. If you want to know how the trap is designed, read Designing a Laser Trap. If you want to know how it can be used, check out Laser Trap: Dexterous Escape.


The following tools are useful to have during the assembly of a laser trap. Soldering iron, tin solder, flux, tweezers, nippers, toothpick (for the flux), microscope, Blu-Tack, permanent marker and a FTDI USB TTL serial cable.

You need some experience at soldering to be able to put together a laser trap. Some of the components are quite small and can be difficult to handle for a beginner.

Main Board

The following parts are needed when assembling the main board for a laser trap.

1x Main PCB
1x 75Ohm resistor, 0.1W, 0603 package (R1)
1x 1uF ceramic capacitor, 0603 package (C1)
1x 1.8V LED, 0603 package (D1)
1x HM-10 Bluetooth LE Module
1x LDO regulator 3.3V, SOT-223 package (U1)
2x 1x3p female connector
1x 9V battery connector
1x Steel stripe



Start by applying flux to the pads for R1, C1 and D1. This will make the components stick a little and you avoid having to chase the components around the board with the soldering iron. Place the components on the pads and solder them in place. Note that D1 have polarity and should be placed with the cathode pointing towards R1. The polarity is marked on the back with a T. The top side of the T is the cathode. (This is a bit strange as all guides on the net say the opposite, but the diode I am using, HSMH-C190, has its marking reversed.)


Put a small amount of Blu-Tack on the back of HM-10 and put it in place on the Main PCB and solder it to the seven pads. If you have Main PCB R1 (without revision marking) you only need to solder one of the four ground pads on the lower side. If you have a HM-10 module without USB as the one in the image, you don't even have pads for two of the ground connections.


Put U1 in position and solder the three legs and the heat sink in place.


Insert the leads from the 9V battery connector into the holes below U1 and solder them in place. Black is ground and should be soldered to the hole out in the corner of the PCB.

If you have Main PCB R1 you need to cut the copper trace that connects the two outer pads in the lower 3p connector. You should then connect the lower pad to one of the spare ground pads next to HM-10.


Put both 1x3p female connectors in position and solder them in place.

Mark the top side of the board as shown in the image.


Connect a 9V battery or another power source between 4.8V and 15V. The LED should start to blink. Attach a USB TTL serial cable to the serial terminals and exchange the following request response sequence at 9600-8N1.
   AT+MODE1
   OK+Set:1
   AT+NAMELASERTRAP
   OK+Set:LASERTRAP

The trap is now ready to connect to the Android application.


For lasers that draw more than 30mA the voltage regulator needs to be cooled more efficiently than what the PCB alone can manage. Attach the steel stripe with Blu-Tack on the opposite side of U1. This stripe is also useful for mounting the trap and sensor in the correct position to monitor the laser beam.


Sensor Board

The following parts are needed when assembling the sensor board for a laser trap.

1x Sensor PCB
1x 1x3p male connector, 90 degree angle
1x 1.3kOhm resistor, 0.1W, 0603 package (R1)
2x 1x1p male connector, straight
7x Photo resistor


Start by applying flux to the pads for R1. Place the component on the pads and solder it in place.


Insert the two photo resistors at the top and the two photo resistors at the bottom into the mounting holes. Cut the leads and solder them in place.


Remove the pins from the two 1x1p male connector and put the remaining plastic tubes on the outer legs of the right and left photo resistors as support. Insert the three center row photo resistors into the mounting holes. Cut the leads and solder them in place.

Put the 1x3p male connector in position and solder it in place.


Red Laser

The following parts are needed when assembling the red laser for a laser trap.

1x 1x3p male connector, straight
1x Red Laser Module
2x Shrink tubes
1x Steel stripe
1x Copper bracelet
1x M5 bolt
1x M5 nut

Green Laser

The following parts are needed when assembling the green laser for a laser trap.

1x Driver PCB
1x 1x3p male connector, 90 degree angle
1x 200Ohm resistor, 0.1W, 0603 package (R1)
1x BC807 PNP transistor, SOT-23 package (Q1)
1x Green Laser Module
1x Steel stripe
1x Copper bracelet
1x M5 bolt
1x M5 nut

Laser Trap

The following parts are needed when assembling a laser trap.

1x Main Board
1x Sensor Board
1x Red or Green Laser

Felfri produktutveckling

Skojade bara. När vi konstruerar och tillverkar produkter är det alltid möjligt att göra misstag. Konstruktionen fungerar inte som det var t...