2019-05-12

Version Numbers

A version number is used to identify different versions, revisions or editions of an artifact. It is common to start to count from 0 and then continue with 1, 2, 3 and so on. No implied meaning should be assigned to any specific number. They are all equally good at identifying a version of the artifact.

A universal version number is open for branching at any existing version. There is always at least one free version number to pick. There are many ways to achieve this, but a simple variant only needs one extra rule; trailing zeros are free. You can always add ".0" to any version and it will still be the same version, e.g. 2, 2.0, 2.0.0, 2.0.0.0 are all the same version. Now when we want to branch, we add one or more zeros to designate the head of the branch. Then we increase the last zero with one for the first version on the branch.

   0 - 1 - 2 - 3 - 4 - 5 - (main line)
            \
      (2.0)  \- 2.1 - 2.2 - 2.3 - (1:st branch of v. 2)
              \
      (2.0.0)  \- 2.0.1 - 2.0.2 - 2.0.3 - (2:nd branch of v. 2)
                   \
      (2.0.1.0)     \- 2.0.1.1 - (1:st branch of v. 2.0.1)

It is easy to see that if you limit the number of dots in your version numbers, they are not universal.

In software it is common to track the version of different aspects of the artifact using dot notation and branches, e.g. 2.4, where the first number tracks the version of the interface and the second tracks the version of the implementation of that interface. Semantic Versioning[1] is one example of a versioning scheme that does this. Another case is when development takes place in multiple branches in parallel, for example when an old release is maintained at the same time as new functionality is added to a future release. Then you need a number to separate the branches.

   0.0           1.0 ---------> 2.0     3.0
      \           ^ \              \     ^
       \          |  \              \    |
        \- 0.1 - 0.2  \- 1.1 - 1.2   \- 2.1 - 2.2


Sometimes it is necessary to depart from the main line and branch off a separate version of the artifact. Semantic versioning lets you add an extra number to indicate that it is a patched version, e.g. 2.4.1. This has its limitations though. It is for example not possible to make changes to the interface as the interface version number is fixed. The insight that leads to a solution is that semantic version numbers always comes in pairs. Add a pair of numbers instead of just one, e.g. 2.4.0.1. The first is for interface changes and the second for implementation changes, just like the main line.

A few rules I try to follow for version numbers are...
  • All artifacts, insofar it is possible, should be able to tell you which version they are.
  • Change happens all the time. Don't assume you know beforehand when an artifact is free of errors and no longer will change.
  • Let the version numbers map to the branching strategy.
  • Use version numbers that are universal (see above).
  • Only change the version number when the artifact changes.
  • Don't use the version number for other things like review status, approval status or quality status.

The first version with a new major number, e.g. 1.0, 2.0 and so on, is not the end goal but the start of something new. If you treat it as the end goal you create a lot of practical problems for yourself. It is likely that 1.0 is less than perfect and you need to release a bugfix. You could call it 2.0 to replace 1.0 as the new perfect version with the high quality moniker .0, but it is better to reserve the first number to interface or other major changes where it serves a better purpose. You settle for 1.1. Now what do you call the pre-release version leading up to 2.0? Not 1.2 because that is the second bugfix release of 1.0. And you do want to give those internal pre-release artifacts a version number to be able to relate test results, reviews and other feedback to something traceable. Therefore it is better to start off the development with version 2.0 and tag the first few versions in the 2.x series as experimental pre-releases and then later decide which version of the 2.x series that happens to be good enough to qualify as the first usable version.

Release Candidate is also a useful concept, where you build an artifact that you hope will be good enough to later be released, but you don't know for sure until you have tested it. I am against rebuilding a good release candidate just to change the version number, therefore the RC designator will always be part of the version number, e.g. 2.0.12 which is the 12:th release candidate of version 2.0.

It is usually a mistake to use development versions and unstable pre-releases a la Semantic Versioning. Say you have decided to use something like Semantic Versioning to communicate with your users, but then say you won't follow it for 0.x versions. Then what's the point? It is likely that you will be stuck on 0.x for many years and your users will struggle to understand what will break in the next release. Use your versioning scheme right from the start, if only for the purpose of learning about its consequences. If you are too lazy to figure out if the interface has changed in the last iteration, just increase the major number to not give your users any unpleasant surprises. The urge not to waste numbers is a delusion. You will not run out of numbers and used numbers don't pollute.

To reiterate, a specific version number, say 2.0, is never a goal in itself. The purpose of the version number is to communicate what it supersedes and how extensive the changes are. The version number you eventually end up with is dictated by the changes you make and how often you release.

Another common misconception is when you insist that reviewed and approved documents should have the version number 1.0, 2.0, 3.0 and so on. The version number gets overloaded with meaning when its main purpose should be to uniquely identify the document and simplify traceability. Also, formal review is just one method among others to get feedback for improvement and if you link it to the version number it will suppress other types of feedback. One of the problems that this x.0 rule causes is that documents never get to be reviewed because the author thinks there still are too many loose ends for the document to be worthy a 1.0. Reviews are good for finding errors and spreading knowledge in the organization and a process that adds obstacles for reviews to take place is sub optimal. Another problem with x.0 is that you create artificial barriers to improve x.0 documents when you find errors because it costs too much effort to get it to the next y.0 version. There are very few organizations that can afford such rigor in the process. Most organizations I have worked with would fare much better with a process that prioritizes small and cheap iterative corrections to documents and only in rare cases require that documents should be reviewed and approved before the development can proceed. What I see in practice is that a 0.6 or a 2.1 never prevents the development to proceed, so why pretend to have a rigorous process when it only causes problems and discomfort? Track the quality status of a document with their own properties, e.g. as additional notes in the document changelog, so that both 0.7 and 2.3 can be reviewed as well as approved.

It is better to let the version number of documents follow the branching of the artifact they describe. This means you don't need to maintain a cross reference matrix for traceability. Documents with version 2.x contain changes that are valid for artifact version 2.y. If you need separate documentation for each minor version, then documents with version 2.1.x belong to artifact 2.1. You choose the granularity that is suitable for your circumstances. With documents in source control the most significant numbers in the version can be tracked globally by the branch. The least significant number can be assigned automatically, e.g. using the global Subversion repo revision or by counting the number of commits of the document in the branch.

Another reason to prefer the branch centric versioning scheme instead of the review centric is the way it changes how you think about your work. The artifact is never finished, only continuously improved.

A third superficial concern regarding version numbers is that users somehow are entitled to get all consecutive versions and that they will be confused if there is a jump in minor or major version between releases. If the user has version 2.3, they expect to get 2.4 next. To get 2.7 instead would cause severe psycological trauma. This is simply not true. Don't let this concern prevent you from tracking changes, may they be internal or external, with a universal version number. The persons that insist on this anti-pattern are typically also the ones that want to haphasardly jump to 10.0 just to catch up with competitors.

For practical advice on how to assign versions to software builds, see [2].

[1] https://semver.org/
[2] https://embeddedartistry.com/blog/2016/10/27/giving-you-build-a-version


2019-02-20

Wifi Remote Control

I have started to automate my home and thought that a dedicated remote control would be useful as an alternative to web pages and phone apps. Pushing a button on the remote performs an HTTP request that in turn controls some equipment at home.

An ESP8266 acts as wifi modem. Out of the box, it implements a serial protocol that is used to control its networking functions. An Arduino Pro Mini is master in the system and is responsible for monitoring the buttons and communicating with the ESP8266 to perform the desired action.

When the system is idle the ESP2866 is turned off and the Arduino is put to sleep. It is woken when a button push generates an interrupt on pin 2 or 3 (interrupt 0 and 1). An internal pull up resistor prevents the interrupt from being triggered when the button is inactive.

#include <avr/sleep.h>

#define GREEN_COLUMN 2
#define RED_COLUMN   3

#define FIRST_ROW   A3
#define SECOND_ROW  A2
#define THIRD_ROW   A1


int activeButton = 0;

void green_isr() { ... }

void red_isr() { ... }

void setup()
{
  pinMode(FIRST_ROW, OUTPUT); 
  pinMode(SECOND_ROW, OUTPUT);
  pinMode(THIRD_ROW, OUTPUT);

  digitalWrite(FIRST_ROW, 0);
  digitalWrite(SECOND_ROW, 0);
  digitalWrite(THIRD_ROW, 0);

  pinMode(GREEN_COLUMN, INPUT_PULLUP);
  pinMode(RED_COLUMN, INPUT_PULLUP);
 

  // Low power mode, no ADC
  ADCSRA &= ~(1<<ADEN);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
 

}

void loop()
{
  if (activeButton == 0) {
    attachInterrupt(0, green_isr, LOW);
    attachInterrupt(1, red_isr, LOW);

    ...
    sleep_cpu();
    ...

  } 
  else {
    detachInterrupt(0);
    detachInterrupt(1);

    ...
  }
}

You may notice that I don't use CLI and SEI before SLEEP which is necessary to make sure I don't miss an interrupt. This would have been required with for example interrupt mode FALLING but with interrupt mode LOW new interrupts keep getting generated and it doesn't matter if one is missed. We need the button to be pushed for longer than one interrupt anyway as we need some time to figure out which button is pressed, which leads us to the next step.

The 6 LED buttons are connected to the Arduino in a 3x2 matrix to share the two interrupt enabled pins, one for each column. By setting the row outputs to 0 one by one, it is possible to figure out which button is currently active.

#define GREEN1 5
#define GREEN2 7
#define GREEN3 9
#define RED1   4
#define RED2   6
#define RED3   8
 


int greens[4] = {0, GREEN1, GREEN2, GREEN3};
int reds[4] = {0, RED1, RED2, RED3};
 

int scanRow = 0;

void green_isr() 
{
  if (scanRow != 0) {
    activeButton = greens[scanRow];
  }
}

void red_isr()
{
  if (scanRow != 0) {
    activeButton = reds[scanRow];
  }
}
 

void loop()
{
    ...
    sleep_cpu();

    digitalWrite(FIRST_ROW, 1);
    digitalWrite(SECOND_ROW, 1);
    digitalWrite(THIRD_ROW, 1);

    scanRow = 1;
    digitalWrite(FIRST_ROW, 0);
    digitalWrite(FIRST_ROW, 1);

    scanRow = 2;
    digitalWrite(SECOND_ROW, 0);
    digitalWrite(SECOND_ROW, 1);

    scanRow = 3;
    digitalWrite(THIRD_ROW, 0);
    digitalWrite(THIRD_ROW, 1);

    scanRow = 0;
    digitalWrite(FIRST_ROW, 0);
    digitalWrite(SECOND_ROW, 0);
    digitalWrite(THIRD_ROW, 0);
    ...
}

The interrupt will be triggered when the correct row output is toggled. This results in an assignment of activeButton and the program moves to the second phase where the ESP8266 is started and instructed to do an HTTP request.

The serial data received from ESP8266 is written to a small circular buffer. Whenever a carriage return ("\r") is detected the buffer is inspected to see if we have received a message that we are interested in. The following messages will trigger an action in the code.

ready
OK
GOT IP
CONNECT
CLOSED
FAIL
ERROR

Whenever "ready" or "OK" is received, a command will be sent to the ESP8266. A normal exchange looks something like this.

< ready\r
> AT+CIPMUX=1\r\n
< OK\r
> AT+CWJAP="SSID","password"\r\n
< GOT IP\r
< OK\r
> AT+CIPMUX=1\r\n
< OK\r
> AT+CIPSTART=0,"TCP","192.168.0.102",8081\r\n
< CONNECT\r
< OK\r
> AT+CIPSEND=0,31\r\n
< OK\r
> GET /on?group=Nere HTTP/1.1\r\n\r\n
< OK\r
< OK\r
> AT+CIPCLOSE\r\n
< CLOSED\r

Note the HTTP GET and the dual CRLF which executes the command on the web server. The second "OK" thereafter is part of the response from the web server.

The messages "CLOSED", "FAIL" and "ERROR" terminates the communication, turns off the ESP8266 and puts the Arduino back in sleep mode. There is also a timeout condition that does the same.

#define ESP32_CS 10
#define ESP32_RST 11
 

unsigned long start = 0;

byte next = 0;
#define BUFSZ 16
byte buffer[BUFSZ] = { 0 };

byte   ready[5] = { 'r', 'e', 'a', 'd', 'y' };
byte   gotip[6] = { 'G', 'O', 'T', ' ', 'I', 'P' };
byte      ok[2] = { 'O', 'K' };
byte connect[7] = { 'C', 'O', 'N', 'N', 'E', 'C', 'T' };
byte  closed[6] = { 'C', 'L', 'O', 'S', 'E', 'D' };
byte    fail[4] = { 'F', 'A', 'I', 'L' };
byte   error[5] = { 'E', 'R', 'R', 'O', 'R' };

byte cmd = 0;


void setup()
{
  Serial.begin(115200);


  pinMode(ESP32_CS, OUTPUT);
  pinMode(ESP32_RST, OUTPUT);
  ...

}

void allOff()
{
  digitalWrite(GREEN1, 1);
  digitalWrite(GREEN2, 1);
  digitalWrite(GREEN3, 1);
  digitalWrite(RED1, 1);
  digitalWrite(RED2, 1);
  digitalWrite(RED3, 1);
}

bool received(byte* msg, byte n)
{
  int i;
  for (i = 0; i < n; i++) {
    if (buffer[ ( BUFSZ + next - (n+1) + i ) % BUFSZ ] != msg[i]) {
      return 0;
    }
  }
  return 1;
}

bool activeIsGreen()
{
  return activeButton == GREEN1 || activeButton == GREEN2 || activeButton == GREEN3;
}


void loop()
{
  if (activeButton == 0) {
    ...
    digitalWrite(ESP32_CS, 0);
    digitalWrite(ESP32_RST, 0);

    ...
    start = millis();
  }
  else {

    ...
    digitalWrite(ESP32_CS, 1);
    digitalWrite(ESP32_RST, 1);

    digitalWrite(activeButton, 0);

    if (Serial.available())
    {
      buffer[ ( next++ ) % BUFSZ ] = Serial.read();
      if (buffer[ ( BUFSZ + next-1 ) % BUFSZ ] == '\r') {
        if (received(ok, sizeof(ok))) {
          if (cmd == 1) {
            Serial.print("AT+CWJAP=\"SSID\",\"password\"\r\n");
          }
          else if (cmd == 2) {
            cmd = 3;
            Serial.print("AT+CIPMUX=1\r\n");
          }
          else if (cmd == 3) {
            Serial.print("AT+CIPSTART=0,\"TCP\",\"192.168.0.102\",8081\r\n");
          }
          else if (cmd == 4) {
            cmd = 5;
            if (activeIsGreen()) {
              Serial.print("AT+CIPSEND=0,31\r\n");
            }
            else {
              Serial.print("AT+CIPSEND=0,32\r\n");
            }
          }
          else if (cmd == 5) {
            cmd = 6;
            if (activeIsGreen()) {
              Serial.print("GET /on?group=Nere HTTP/1.1\r\n\r\n");
            }
            else {
              Serial.print("GET /off?group=Nere HTTP/1.1\r\n\r\n");
            }
          }
          else if (cmd == 6) {
            cmd = 7;
            // Wait for HTTP GET 200 OK
          }
          else if (cmd == 7) {
            cmd = 8;
            Serial.print("AT+CIPCLOSE\r\n");
          }
        }
        else if (received(ready, sizeof(ready))) {
          cmd = 1;
          digitalWrite(activeIsGreen() ? RED1 : GREEN1, 0);
          Serial.print("AT+CIPMUX=1\r\n");
        }
        else if (received(gotip, sizeof(gotip))) {
          cmd = 2;
          digitalWrite(activeIsGreen() ? RED2 : GREEN2, 0);
          start = millis();
        }
        else if (received(connect, sizeof(connect))) {
          cmd = 4;
          digitalWrite(activeIsGreen() ? RED3 : GREEN3, 0);
          start = millis();
        }
        else if (received(closed, sizeof(closed)) ||
                 received(fail, sizeof(fail)) ||
                 received(error, sizeof(error))) {
          cmd = 0;
          allOff();
          activeButton = 0;

        }
      }
    }
    else if (millis() - start > 8000UL) {
      cmd = 0;
      allOff();
      activeButton = 0;

    }     
  }

}

Worth noting here is the timeout expression "millis() - start > 8000UL" which always works, especially when the number of millseconds rolls over to zero. A slightly different expression like "millis() > start + 8000UL" does not work as expected close to the rollover point.

As you can see, I have the same action on all green and red buttons, but this can easily be extended with a couple of if statements where the command is sent.

The three LED buttons of the opposite color of the one being pushed are used as a tiny progress bar.

To reduce the power consumption in idle mode I removed the power indicator LEDs from the Arduino and the ESP8266.

When debugging the serial communication I used my PC and the program socat to print a trace of the communication between ESP8266 and Arduino. I used two FTDI USB to Serial cables connected to the Arduino and an adapter board for the ESP8266 which besides the connector for FTDI also contains a 3.3V regulator and voltage dividers on RST, CS and RXT to allow it to be driven from the 5V Arduino. It is visible in the image, here connected directly to the Arduino.

$ socat -v /dev/ttyUSB0 /dev/ttyUSB1

It is highly recommended as a debugging tool.

2018-07-20

PM1 - Pro Memoria - For Memory

The written word is a powerful tool for knowledge transfer to colleagues and future self. Still, documentation is often neglected in favor of more tangible things like new products and features. One culprit could be an overly complicated process for writing and maintaining documents. Revising large documents and coordinate contributions from many authors can be daunting.The goal of the PM system described here is to simplify the task to share product designs, processes, utility guides, tool descriptions and ways of working within an organisation.

A PM is typically fairly short, like a blog post. It should be possible for one person to write a PM in a day or a few hours. Multiple PMs are required to describe all aspects of a large system.

A PM is assigned an unique identity, e.g. PM1, PM2, PM3 and so on.

A PM can be revised if it contains errors or needs to be improved. The first revision of PM27 is called PM27.0, the second is called PM27.1 and so on. (See also Version Numbers).

If the thing being described has changed then a new PM should be written. Old things will come back to haunt you and then it is good to have the documentation readily available. The underlying philosophy here is that a PM should be a stable value that doesn't change with the times. Let's assume you have described the build process for you product in a PM. Release 5 of the product has a new build system. You should keep the original PM and clarify that it covers product releases 1 to 4 and write a new PM for product release 5. (See also The Value of Values). The information that is allowed to continuously grow in a PM is the set of relations it has to other PMs.

A Wiki could be a suitable tool for your PMs.

If you need to organize related PMs in a larger structure, then do that in a PM. You are allowed to append to such a structural PM, but preferably not replace referenced PMs with superceeding ones. If this becomes necessary, then create a new structural PM to maintain the history intact. Don't start out by creating a lot of structural PMs with no content. Let the structure grow organically when you have PMs with actual content to organize.

For practical purposes it may also be necessary to keep a volatile list of the most up to date PMs in a well known location, e.g. the front page of a Wiki.

An alternative to the structural PM is to continuously revise PMs to contain cross references to related, superceeding and superceeded PMs.

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/




Debugging with Popper