Work related posts have been moved.

9 November, 2010

My work and computing related posts are now at

If you have come here from a work-related perspective (computing, semweb, bioinformatics, math). Perhaps you could go there right now and not read the gory personal stuff here.

Use the wordpress rss mechanism to follow categories:

Follow Paul Murray on Quora


Catenary

20 April, 2015

Some unpainted dudes face some other unpainted dudes on a 50′ bridge which sags 5′ in the middle and whose glue is not yet dry.

Each “step” is 1/3 of an inch, which is the thickness of a dwarven forge floor tile. The shape is a catenary, of course. I’m thinking of doing a 50′ bridge where one end is 10′ higher than the other end, now that my spreadsheet is cooking and correctly calculating the shape.

Once the glue is dry, I’ll stain the wood with ink, then bind the wood to the wire with some twine – hiopefully it will look like rope. The ends of the wire will just sit on the tiles. Maybe I can decorate them to look like posts or stakes driven into the ground.


Machine

3 April, 2015

Well – here’s the code for my “open the blinds when it’s daylight” project. It features time slicing – while the motor is winding the blinds, you can hit a button at any time to stop it.

Inputs:
PIN_UP, PIN_DN, PIN_ARM – buttons on my little control stick.
PIN_LDR – light sensor.

Outputs:
pin 13, of course. And pins A, A_, B and B_ which are the coils of the stepper.

A down on any button stops the motor.

A hold on PIN_UP/PIN_DN moves the motor until you release the button.

A hold on PIN_ARM arms and disarms the device.

A double-click on any button moves the blinds to the preset position for that button.

A triple-click sets the preset position for that button. This means that when setting, the motor will briefly activate (as if you had double-clicked), but meh.

Finally, the daylight sensor. If the device is armed, and A0 goes over the threshhold and stays over the threshold for a given time, then the motor moves the blinds to the position set on the ARM button.

The basic design is that each object has a loop() method which is called each time the main loop() runs. A boolean is returned to indicate if anything interesting happened. If nothing interesting happens for a while, then it would be reasonable to put the board to sleep, but meh – haven’t done this bit.

To set up:
Click and hold one of the UP/DN buttons to close the blinds. Triple-click to remember the closed position.
Click and hold the other UP/DN button to open the blinds. Triple-click to remember the open position, and also triple-click the ARM button.

At bedtime:
double-click the ‘close the blinds’ button to block out the light from that damn streetlight right outside my bedroom window. (If it isn’t already closed, which it will be due to privacy.)
click and hold the ‘arm’ button. The LED will flash once.

At dawn(ish), the blinds will open to let in the glorious morning sun and hopefully my circadian rhythm will finally get the message that it’s ok to wake up in the morning.

const byte INFO_LED = 13; // blue wire - also dives on-board LED

const byte A = 2;
const byte A_ = 3;
const byte B = 4;
const byte B_ = 5;

#define STEPPER_PINS(v) for(int v=2;v<=5;v++)
#define STEPPER_OFF STEPPER_PINS(stepperOut) digitalWrite(stepperOut, LOW);

const byte PIN_UP = 12; // red or black wire
const byte PIN_DN = 11; // red or black wire
const byte PIN_ARM = 10; // white wire

const byte  PIN_LDR = 0; // analog 0

const int LDR_THRESHHOLD = 300; // seems to be about right.
const int DAYLIGHT_TIME_MS = 1000 * 60 * 10; // 10 minutes

//#define DEBUG true

#ifdef DEBUG
#define info(x) _info(x)
#else
#define info(x)
#endif


// first - library code (in effect)

// An object that knows how to listen for button presses and releases
// and to invoke callbacks in response
// call loop() to make it go.

class Button {
  protected:

    byte pin;

#ifdef DEBUG
    void _info(const char *s) {
      Serial.print(name);
      Serial.print(": ");
      Serial.println(s);
    }
#endif

    virtual void dn() {
      info("DOWN");
      if (dn_callback) {
        dn_callback();
      }
    }

    virtual void up() {
      info("UP");
      if (up_callback) {
        up_callback();
      }
    }

  public:

    void (*dn_callback)() = NULL;
    void (*up_callback)() = NULL;
    char *name = "A Button";

    boolean buttonDown = false;

    void setup(byte _pin) {
      pin = _pin;
      pinMode(pin, INPUT_PULLUP);
      buttonDown = !digitalRead(pin);
    }

    boolean loop() {
      boolean newState = !digitalRead(pin);
      if (newState == buttonDown) return false;
      buttonDown = newState;
      if (buttonDown) dn(); else up();
      return true;
    }

};

// An object that knows how to listen for clicks, double-clicks, and holds
// and to invoke callbacks in response
// call loop() to make it go.

class Clicker: public Button {
  public:

    const int DOUBLE_CLICK_MS = 1000;
    const int HOLD_MS = 500;

    void (*click_callback)(int);
    void (*startHold_callback)(int);
    void (*hold_callback)();
    void (*endHold_callback)();

    boolean loop() {
      Button::loop();

      if (buttonDown) {
        if (holding) {
          hold();
        }
        else if (timeExceeded(HOLD_MS)) {
          startHold(clickCount);
          holding = true;
          clickCount = 0;
          hold(); // an initial hold to produce consistent behaviour
        }
        return true;
      }
      else {
        if (clickCount > 0 && timeExceeded(DOUBLE_CLICK_MS)) {
          clickCount = 0;
        }

        // if clickcount is greater than zero, we are in a possible double-click situation
        // so we want the loop to be running fast.
        return clickCount > 0;
      }
    }

  protected:

#ifdef DEBUG
    void _info(const char *s) {
      Serial.print(name);
      Serial.print(": ");
      Serial.println(s);
    }
#endif

    unsigned long dnMillis = 0L;
    boolean holding = false;
    int clickCount = 0;

    boolean timeExceeded(int t) {
      // millis may wrap, but the math works out
      return (millis() - dnMillis) >= t;
    }

    void dn() {
      Button::dn();
      dnMillis = millis();
      clickCount ++;
    }

    void up() {
      Button::up();
      if (holding) {
        holding = false;
        endHold();
      }
      else {
        click(clickCount);
      }
    }

    virtual void click(int clickCount) {
      info("CLICK");
      if (click_callback) {
        click_callback(clickCount);
      }
    }

    virtual void startHold(int clickCount) {
      info("HOLD ON");
      if (startHold_callback) {
        startHold_callback(clickCount);
      }
    }

    virtual void hold() {
      if (hold_callback) {
        hold_callback();
      }
    }

    virtual void endHold() {
      info("HOLD OFF");
      if (endHold_callback) {
        endHold_callback();
      }
    }
};

// A stepper motor that keeps track of location. It can move the motor to a target, or just start and stop.
// it will only do 5 minutes max of movement before stopping unconditionally
// call loop() to make it go.

class MyStepper {
  public:

    int location = 0;
    char *name = "Stepper";

    void setup() {
      STEPPER_PINS(v) pinMode(v, OUTPUT);
    }

    boolean loop() {
      // it's ok to leave lastMove uninitialized, because it's unsigned

      // 4ms = 250/s = 10/s turns = 600rpm, which is about the maximum

      if (millis() - lastMove < 5) return movingUp || movingDown || moveToTarget;

      lastMove = millis();

      if (movingUp) {
        location++;
      }
      else if (movingDown) {
        location--;
      }
      else if (moveToTarget) {
        if (location == target) {
          stopMoving();
          return true;
        }

        location += target > location ? 1 : -1;
      }
      else {
        return false;
      }

      if (tripFailsafe()) return true;

      // this code may cause both A and A_ to be turned on at the same time, but meh -
      // it's only for a microsecond or so. It isn't going to break anything.
      digitalWrite(A,   (location & 2) ? HIGH : LOW);
      digitalWrite(A_, !(location & 2) ? HIGH : LOW);
      digitalWrite(B,   ((location ^ (location >> 1)) & 1) ? HIGH : LOW);
      digitalWrite(B_, !((location ^ (location >> 1)) & 1) ? HIGH : LOW);

      return true;
    }

    void moveUp() {
      info("moveUp()");
      stopMoving();
      setFailSafe();
      movingUp = true;
    }

    void moveDown() {
      info("moveDown()");
      stopMoving();
      setFailSafe();
      movingDown = true;
    }

    void moveTo(int _target) {
      info("moveTo(_)");
      stopMoving();
      setFailSafe();
      moveToTarget = true;
      target = _target;
    }

    void stopMoving() {
      info("stopMoving()");
      movingUp = false;
      movingDown = false;
      moveToTarget = false;
      STEPPER_OFF;
    }


  protected:
    const unsigned long FAILSAFE_MS = 1000 * 60 * 5; // 5 minutes max time running the motor

    unsigned long failsafe = 0;
    unsigned long lastMove = 0;
    boolean movingUp = false;
    boolean movingDown = false;
    boolean moveToTarget = false;
    int target = 0;

#ifdef DEBUG
    void _info(const char *s) {
      Serial.print(name);
      Serial.print(": ");
      Serial.println(s);
    }
#endif

    void setFailSafe() {
      failsafe = millis();
    }

    boolean tripFailsafe() {
      if (millis() - failsafe < FAILSAFE_MS) return false;
      if (!movingUp && !movingDown && !moveToTarget) return false;
      stopMoving();
      return true;
    }

};

// the control objects and their event handlers.
// the control schema is:
// if the 'set' button is on, then a click on up/down/arm will set the corresponding setpoint
// it the 'set' button is not down
// buttons up and dn will move the motor on a hold and move to the setpunt on a dclick
// single-click does nothing. It's just an 'oops'.
// arm button moves to the arm setpoint on a dclick, and arms/disarms on a hold.
// on an arm, the onboard led is flashed once. on a disarm, it's flashed three times.

MyStepper stepper;
Clicker buttonUp;
Clicker buttonDn;
Clicker buttonArm;

boolean armed = false;
int upSetpoint;
int dnSetpoint;
int armSetpoint;
boolean daylight = false;
unsigned long daylight_t = 0;


#ifdef DEBUG
void _info(const char *s) {
  Serial.print(": ");
  Serial.println(s);
}
#endif

void buttonUp_click(int clickCount) {
  info("click");
  if (clickCount == 2) {
    info("2 clicks. moving up");
    stepper.moveTo(upSetpoint);
  }
  else if (clickCount == 3) {
    info("3 clicks. Setting up");
    upSetpoint = stepper.location;
  }
}

void buttonUp_startHold(int clickCount) {
  stepper.moveUp();
}

void buttonUp_endHold() {
  info("stop moving the stepper");
  stepper.stopMoving();
}

void buttonDn_click(int clickCount) {
  if (clickCount == 2) {
    stepper.moveTo(dnSetpoint);
    info("2 clicks. movto dn.");
  }
  else if (clickCount == 3) {
    dnSetpoint = stepper.location;
    info("3 clicks. Setting dn");
  }
}

void buttonDn_startHold(int clickCount) {
  stepper.moveDown();
}

void buttonDn_endHold() {
  stepper.stopMoving();
}

void buttonArm_click(int clickCount) {
  if (clickCount == 2) {
    stepper.moveTo(armSetpoint);
  }
  else if (clickCount == 3) {
    armSetpoint = stepper.location;
  }
}

void buttonArm_endHold() {
  armed = !armed;
  info_flash(armed ? 1 : 3);
}

void stopStepper() {
  stepper.stopMoving();
}

void setup() {
  pinMode(INFO_LED, OUTPUT);

#ifdef DEBUG
  Serial.begin(9600);
#endif


  buttonUp.setup(PIN_UP);
  buttonDn.setup(PIN_DN);
  buttonArm.setup(PIN_ARM);
  stepper.setup();

  buttonUp.name = "BtnUp";
  buttonUp.click_callback = buttonUp_click;
  buttonUp.startHold_callback = buttonUp_startHold;
  buttonUp.endHold_callback = buttonUp_endHold;

  buttonDn.name = "BtnDn";
  buttonDn.click_callback = buttonDn_click;
  buttonDn.startHold_callback = buttonDn_startHold;
  buttonDn.endHold_callback = buttonDn_endHold;

  buttonArm.name = "BtnArm";
  buttonArm.click_callback = buttonArm_click;
  buttonArm.endHold_callback = buttonArm_endHold;

  // all buttons, irrespective of anything else they do, stop
  // the stepper when they are hit.

  buttonUp.dn_callback = stopStepper;
  buttonDn.dn_callback = stopStepper;
  buttonArm.dn_callback = stopStepper;

}

// don't need a class to encapsulate this: it's pretty simple
boolean checkLightSensor() {
  if (!armed) return false; // nothing to see, here.

  boolean _daylight = analogRead(PIN_LDR) > LDR_THRESHHOLD;

  if (!_daylight) {
    if (!daylight) {
      // still naptime
      return false;
    }
    else {
      info("Just a passing car");
      // just a passing car.
      daylight = false;
      return true;
    }
  }
  else {
    // ok. it's daylight now, and we are armed.

    if (!daylight) {
      info("Daylight sensed");
      // this was the first sensing
      daylight = true;
      daylight_t = millis();
      return true;
    }
    else if (millis() - daylight_t < DAYLIGHT_TIME_MS ) {
      // waiting to see if the sun remains out for a bit
      return false;
    }
    else {
      info("It's daytime. Moving.");
      // we are armed, it's daylight, and it's been daylight for some time
      info_flash(10);
      armed = false;
      stepper.moveTo(armSetpoint);
      return true;
    }
  }

}

void loop() {
  // put your main code here, to run repeatedly:

  boolean somethingHappened = false;

  somethingHappened = buttonUp.loop() || somethingHappened;
  somethingHappened = buttonDn.loop() || somethingHappened;
  somethingHappened = buttonArm.loop() || somethingHappened;
  somethingHappened = stepper.loop() || somethingHappened;
  somethingHappened = checkLightSensor() || somethingHappened;

  if (!somethingHappened) {
    // TODO! Put the arduino to sleep, wake on interrupt.
    // for now - meh. It's ok to run a tight loop.
  }

}

void info_flash(int n) {
  while (n--) {
    digitalWrite(INFO_LED, HIGH);
    delay(200);
    digitalWrite(INFO_LED, LOW);
    delay(200);
  }
  delay(200); // to demarcate the phrase
}


Machine

23 March, 2015

The Device is complete.

Oh, there are issues. Most particularly, I damaged a part with excessive heat, but the part that was damaged was part of my hand-held controller – for the particular function needed, I am content to use the probe mounted onto the brain itself.

There is no special reason to suppose the device will fail to perform its function. I fully expect that I shall be shielded from the annoying light from the streetlamp outside all night, and that at dawn my new domestic servant shall open the blinds for me.

(PS: Mad! Mad! They called me mad! I’ll show them who’s mad, the brutes, the ignorant philistines – what do they know of science etc etc. Grandfather Freemont might have dared the Bravarian torches and pitchforks – I am a little more circumspect.)


Machine

21 March, 2015

Progress. I have successfully moved the interface onto its own board, which shortens the lines needed to attach the interface to the motor.

I have also found sources of Coulombic fluid separate for the mind and the matter – 6v and 9v respectively. The 9v supply, I suspect, will not suffice to operate the device when it is attached to its load: if has force, but not capacity.

Best of all, I found a purveyor of parts for model flying machines, who had a part that would fit the 3mm shaft of my motor. Now I shall be able to attach it to its load without difficulty.

I enclose a moving photolithograph (the wonders of technology!) of the device thus far in operation.

— ADDENDUM —

I have rebuilt the gearing. The result is simpler and more compact, which is usually the way. Just now I tested the device against the load it is intended to move. The coulombic nine-volt source is not sufficent for the task. Using my bulkier source, it moved the load handsomely, and above all reasonably quietly.

I shall restore to the brain its previous intelligence. Then I shall investigate adding its eye. Finally, I shall need to think carefully with respect to the logic behind its mind, and perhaps organise a somehat more robust control device. At present, it is controlled by switches mounted into its skull. They are tiny and prone to come loose – something a shade more robust would serve better.

Exciting times, although I must give some few hours to another task needing to be done.


Machine

18 March, 2015

As I mentioned previously, the subtle currents of the mind cannot animate the gross matter of the body directly: an intermediary is needed. The mind – physically – exists as a gas, or perhaps smoke confined within the brain. The currents of thought are breezes blowing though the brain, moderated and manipulated by its – shall we say – shape. And as the subtle currents of the mind cannot animate the gross motions of the body, should the situation be reversed, the brain will be overwhelmed and the mind will escape it, leaving a mere husk, an empty shell.

While not obvious to the eye, nevertheless this catastrophe may be detected by the nose, as the smoke that is the substance of mind has a characteristic odour.

Which brings me to my latest experiments.

I had grown impatient with the device to move the limbs – it was noisy, it shook and howled. The motive power for it required far too much Coulombic Fluid. Although I have a source of such, it is bulky and I did not want to use it for the final installation. The motive heart spun at a furious rate, requiring mechanical reducing, and lacked a way to delicately control it. I attempted to grant my machine a kinesthetic sense, but the shaking of the whole device rendered these organs worthless: they could not distinguish between the motion of the shaft and the overall vibration.

I was aware, of course, of a different kind of motive power – a “stepper” which, rather than merely spinning, could be made to advance forwards in fine steps (one forty-eighth of a revolution, in fact). I had avoided these, as the other motors (‘motor': that which provides motive power) were far simpler. Additionally, these “steeper motors” required a mind/body barrier.

But I had needed a barrier anyway when working with the simpler motor, so it was much of a muchness. And although the stepper is more complex, the mathematics of it were by no means beyond me. It was the correct tool for the task at hand, and I simply felt up to the challenge. And so, once more to the graveyard, coin in hand, for the purchase of a stepper motor and a darlington array chip.

First, the mind! I made use of a certain well-known library for such problems, the one that the italian gentlemen who manufacture my mechanical brain supply. I desired to see what manipulations it would attempt to perform on the motor, and so I attached to the brain certain devices (light-emitting diodes, or LEDs) that glow in the presence of motion in the electrical fluid, and which are sensitive enough to do so in response to the subtle currents of the mind.

Observing the behaviour of the brain, and consulting with the excellent information with which the motor came supplied, I determined how to instruct the mind to operate the motor.

At which point it came time to attempt to use the mind/body mechanism, the “darlington array”. I reasoned that to start with, rather than using the powerful currents that animate the body, I could use the potential of the brain – five voltaic units – as its output, as I merely wanted to see that the damn thing worked at all. I moved the taps that were connected to the LEDs to the array, and put the LEDs on the output of the array.

And all was as it should be! When I directed the mind to move its limb, the LEDs illuminated in correct sequence.

Now it is usual, when LEDs are used, to equip them with a prophylactic whose function is to resist an excess flow of Coulombic Fluid. Such a prophylactic reduces the brightness of the LEDs, but not by a great amount. However, I had disdained this, principally because it is rather fiddly to install the devices – my eyes and fingers are not so precise as they once were. As the darlington array was connected to a mere five volts, this was not an issue.

But the whole point of the array is that it can manage a far greater voltage. That it whole point of it. And so I connected the array to the full unmoderated nine volts of input potential. Within seconds, the LEDs flickered and went dark, and I smelled the smoke. Something had died.

But as you have gathered, I had merely fried the LEDs. One of them still worked, although barely, and was illuminating and darkening in the correct sequence, and so I saw immediately that the brain was uninjured. On the whole, a cheap and instructive lesson.

After this, I connected the motor to the mind via the array (the motor must not be connected directly to the mind under any circumstances due to the phenomenon of back-EMF), and the brain controlled it quite handsomely. I commanded it to spin the motor forward and back by one revolution pausing for one second in between, and the motor accurately turned by a revolution and returned precisely to its starting point. Formidable!

And so matters stand. I believe the motor is quite a bit stronger than the previous ones, which rely on rotational speed. Thus, I shall be able to remove quite a bit of the gearing from The Machine. I suspect I could connect the motor directly to its destined load, but I have been to quite a bit of trouble to source the “Meccano” and damn it – I intend to use it irrespective of whether it is actually necessary.

Further developments anon.


Machine

13 March, 2015

I have a moment to work on the machine, as the steam pipes that supply my place of employment with power have temporarily been interrupted.

I have succeeded in ordering the brain to move the arm back and forth. My machine moves more smoothly in one direction than in the other – it seems that it is destined to be an awkward, misshapen thing. But it shall serve my purpose, or be destroyed.

Now I must consider the details of what thoughts this brain is to think. I wish to be able to move the arm back and forth on command, and to have the brain move the arm itself when the event occurs.

Of particular importance is that the arm must not move too far, lest it damage the device it is attached to. I believe I need to equip it with the following:

  • An arming switch. When the device is armed, then at the event it will move the arm to its extremity.
  • A pair of buttons, for moving the arm forth and back. Perhaps I may be able to have the brain recognise a press-and-hold to differ from a swift double press.
  • A way to inform the device that the current position of the arm is an extremity that it must not move beyond. Perhaps the pair of buttons may serve this purpose: a switch shall indicate that the pair of buttons is to be treated as an order to move the arm; or that they are to be treated as a setting of the arm’s limits.
  • If the buttons can distinguish between a press-and-hold and a double-press, then a press of both buttons will suffice to indicate where the arm is to be moved to at the occurrence of the event.

In order to make any of this occur, I must first equip the machine with a kinesthetic sense – some way to know where the arm is. I shall resort to a lodestone and magnetic switch located on the middle shaft. The brain shall be prodded as the switch activates in the presence of the lodestone. (Will it interpret this prodding as “pain”? Perhaps. Its suffering is unimportant to me, or to science.)

But how will it know whether the count is to be increased or decreased? There are two ways. Two magnetic switches may be used, slightly offset. But a simpler method is for the counter neurons to be aware of the motor neurons.

So. Two switches, two buttons, and the magnetic switch. And perhaps a temporary switch whose task it is to simulate the occurence of the event to which the creat- the machine shall respond.

volatile byte increment = 0;
volatile int position = 0;

int positionA = 0;
int positionB = 0;
int sunrisePosition = 0;

void reed_switch_interrupt() {
  position += increment;
}

void start_up() {
  increment = 1;
  // start the motor
}

void start_down() {
  increment = -1;
  // start the motor
}

void stop() {
  // stop the motor
  increment = 0;
}

void motorTo(int moveTo) {
  long stop_time = now() + 10000; // 10 sec

  if(moveTo > position) {
     start_up();
     while(now()<stop_time && position<moveTo) {
       sleep(100);
     }
  }
  else if(moveTo < position) {
     start_down();
     while(now()moveTo) {
       sleep(100);
     }
  }

  stop();
}

loop() {
  if(need_to_handle_sunrise()) {
    handle_sunrise();
  }
  if(either_button_pressed()) {
    handle_button_press();
  }
  sleep(250);
}

handle_sunrise() {
  moveTo(sunrisePosition);
}

handle_button_press() {
  // ok. watch A||B. if it goes low during the .5 seconds, note the fact
  // after the .5 seconds is done, look at the state of the buttons.
  // this means that a double click is a click then a click-hold

  sleep(25); // debounce

  boolean doubleClick = false;

  long tt = now() + 500;

  while(now() < tt) {
    doubleClick = doubleClick || !either_button_pressed();
  }

  if(!either_button_pressed()) return; // meh.

  if(set_switch_setting()) {
    if(both_buttons_pressed()) {
      sunrise_position = position;
    }
    else if(button_a_pressed()) {
      positionA = position
    }
    else if(button_b_pressed()) {
      positionB = position
    }
  }
  else {
    if(both_buttons_pressed()) {
      motorTo(sunrisePosition);
    }
    else if(button_a_pressed()) {
      if(double_click) {
        motorTo(positionA);
      }
      else {
        start_up();
        while(button_a_pressed()) sleep(10); // maybe put a timer in here, too
        stop();
        positionA = position;
      }
    }
    else if(button_b_pressed()) {
      if(double_click) {
        motorTo(positionB);
      }
      else {
        start_down();
        while(button_b_pressed()) sleep(10); // maybe put a timer in here, too
        stop();
        positionB = position;
      }
    }
  }
}


Machine

11 March, 2015

I have purchased parts for the Device (Curse it! Why must I part with good money for this! Is it not the 19th century? When, oh when will they install turnstiles on the cemeteries?)

Nevertheless, it has been worth the expense. Now, the heart drives the limbs without difficulty, even with as little as one and one-half voltaic units of electrical Fluid. The final arm turns with enough force that I must give some thought to the possibility that the Device may injure the fitment it is destined to drive. My landlord has been a reasonable soul, and I do not wish to annoy him.

Next, I shall attempt to connect the brain to the body. A difficult matter, as the currents of the mind cannot directly excite the gross matter of the body. I have, again, been to some expense to purchase an H-Bridge. Its principles are simple enough, and although I desired to build such an assembly from more basic components (the better to understand them), the cemetery had been picked clean of the necessary.

If I can succeed in commanding the brain to move the limb forward and back, then I shall have made good progress.

The headaches continue unabated, but I grow accustomed to them. The spots in my vision are no more than a nuisance.


Follow

Get every new post delivered to your Inbox.

Join 48 other followers