Living Green

8 September, 2018

A while ago, Make Hack Void was contacted by a certain Tracey B, who was interested in a collaborative art project thingy involving, hopefully, some LED lights evoking waves on a sea shore. I had been fooling about with Arduinos and NeoPixels, and I mostly approve of art in general, so I stuck my hand up. The result was – well, not as grand as I might have hoped. But it did work, at least.

Anyway. It seems someone in Norway wants to try something similar, on a slightly bigger scale, so I’ll write down here some stuff which hopefully might be useful to her. I belive she already works with breadboards and arduinos, so this will not be a detailed how-to.

Initial Impressions

Tracey had already sourced a couple of strings of individually addressable LEDs off the internets, which she brought to our initial meeting. But, had no idea how to go about making them light up, much less do some kind of seascape-y thing. With the aid of a magifying glass app, I examined them and determined that each led was powered by a WS2811 driver chip and that the string had three conductors: power, ground, and data. So, my initial reaction was “yep, I think I can make these things go”. I took one of the two strings home with me to see if I could make them at least light up.

This proved to be no problem.

There were other possibilities that might have made the project different. For instance, there are RGB strings of LEDS with four conductors: ground, R, G and B. Another possibility is common-anode. Instead of ground, there’s a +5v. This works well with a ULN2003A. You use a PWM arduino output to switch the ULN2003A, which supplies the power that the lights need. The disadvantage is that you only get one colour for the whole string, which limits what you can do. The advantage is that some of these strings are 12v and quite a bit brighter than what we had.

In any event, I found that the neopixel library that adafruit makes for the arduino worked perfectly fine, except that this particular string did not have the standard RGB arrangement. That also is perfectly fine: the library comes with 6 constants for the 6 possible permutations of RGB. It meant that mixing these particular lights with a string of the more standard variant would be awkward … but we weren’t planning to do that.

Making them light up

So, with the aid of a loupe I worked out which end of the string was in and which was out. Or maybe I just tried it both ways and found which one worked 🙂 . Ground and power off the arduino board itself into red and black and one of the standard adafruit demo sketches into pin 6 and from there into the white conductor on the string, and the thing lit up just fine. I discovered the RGB/BGR thing about this particular strand at that time, but this was no problem.

The code

The code – legal arrangement

When doing Arduino projects, I do so with the agreement that the code will be released into the public domain and published on github.

Even for paid projects, the code is released publicly. My position is that you are not paying for proprietary code, you are paying for getting me to write the code that you want. No contracts, no legal nonsense. When I accept jobs on the ‘gigs and collaborations’ board, I just ask that the fee be remitted to my PayPal if the code works for my client and they are happy with it. I think I have been done out of my fee only once.

For a weekend job, I typically charge $50 AUD. It’s my hobby, I do it for fun. I think I did Tracey’s job for free (pretty sure), and just asked her to cover the cost of parts. I do confidential, proprietary code at my real actual job. Any sort of confidentially requirements mean that you have to pay me real actual money, which for me is $100/hr.

Tracey was perfectly happy with the Living Green code being released to the public domain, which is what I expected, and we could proceed.

The code – design of the effect

The code is here.
.

The only design spec I was working with was “something something waves on the sea shore, it can be pretty abstract”. I didn’t know at the time how the strings were going to be mounted or arranged. The string itself was 50 lights long, and Tracey had two of them. I wrote a sketch that would power 100 lights.

Neopixels work by chaining the data. Data is sent to the lights in a stream, and each light removes the head of the stream and sends the rest of the data down to the next light in the chain. If the data falls off the end of the chain – this is no problem. That meant that I didn’t need to worry about the exact number of lights we would be working with.

So, I set up a system of sine waves that would be added together. It’s hideously over-engineered, of course, because that’s just how I roll.

The heart of the system is a C++ class named ‘Sine’. Sine has a wavelength and a frequency (fixed values), and a current theta position. Frequency is rotations per second, wavelenght is how many pixels long the wave is. When advanceMicros() is called, theta is updated. The v() method gets the current value of the sine wave at a given position along the strand, adjusted to a 0-1 range. v_for_mul gives a value of -1 to +1, which I decided to include when I wanted to multiply sine waves together.

There is additional code in there to permit the creation of a static ‘advanceAllMicros’ method. When the Sine objects are instantiated, they automagically add themselves to a static linked list of Sine objects. This means that if you simply create a Sine object and the code periodically invokes advanceAllMicros, that Sine object will move with correct timing without you having to do anything further.

With that class written, it was a matter of coming up with a pleasing effect.

I created a sine wave that moved in one direction (waveIn), and another that moved in the opposite direction (waveOut). I gave these a slightly different wavelenght and speed so that it wouldn’t look too regular. I decided that one of them would be green-ish, and the other kinda cyan. The rgb value of each pixel becomes an average of these two values.

I then decided that it would be nice if these two effects sort of took turns – the wave would move one way and then the other. So I created another sine wave, waveInOut, which moves a bit slower. Rather than taking an average of waveIn and waveOut, I took a weighted average, the weight being taken from waveInOut. This weight doesn’t vary by position along the string – I just use waveInOut.v(0). I could have used position along the string, but you want the effect to be ordered enough that a viewer can see a pattern, but no so ordered that it’s predictable. Another issue is that people aren’t going to be looking at it for very long, so it’s can’t be too visually chaotic.

Finally, I decided that it would be nice to have some sparkly highlights simulating ripples. So I made rippleBase and rippleMul. These run fairly quickly, and the effect is difficult to describe. But I was happy with it. I add this as a pure blue component to the final result, just to mix it up a little.

Finally, I felt that maybe a high-tide/low-tide component would be nice, so I added a tide Sine object with a frequency of ‘once every half hour’, which would adjust the overall effect of the main wave components.

The point of all this being: once you have defined an object that does something definite and simple, playing about with it is reasonably easy. With the Sine object defined, fooling about with different frequencies, different intensities, different combinations of waves is simpler.

What I would do differently

I’m pretty happy with the effect I eventually got. The main issue was the ‘tide’ thing. Having a half-hour cycle is kinda cool, but no-one is standing there looking at them for that long, so it was a bit pointless. The lights were not bright enough for the site, and having them slowly go dim over time didn’t work well on the day. My clients addressed this by rebooting the whole thing periodically, which isn’t ideal.

The code – overall control

Well, with all those parameters to play with, you’d like your client to be able to play with them. I built a mounting box for the project out of a small plastic food container and put a potentiometer and a couple of buttons in it. This allows you to get numbers into the sketch.

Ultimately, I rigged it up so that you could control the overall speed of the effect and the hightide/lowtide brighness. As I mentioned, the whole ‘tide’ thing was a bad idea, so ultimately this control box was pretty useless. I stuck the Arduino itself into it, but that’s all it turned out to be good for.

What I would do differently

These days, I’d provide a web interface. Several ways to do this.

Alternative ways to do sketch control

Don’t bother.

Why bother? It’s some background lights for the real show, which is the art stuff and art words. Relax! Just because I’m doing up some blinkenlights, doesn’t mean it’s the most important thing in the world. Get it right and put that in the code. Of course: this means that you really need to see the final result installed so that you can perform final code tweaks.

Arduino serves up web pages

This is not ideal if you want the general public to be able to fool with the settings, because network interaction will interfere with the display. It will freeze or get choppy while pages are being served.

There’s issues with storage, too. Web pages are bulky, especially if you want images. So we are talking SD cards. It’s really not worth it.

Arduino communicates via HTTP/JSON

Put the webpage elsewhere, have the arduino accept JSON packets from the HTML forms. I have had some sucess using AngularJS for this. The NodeMCU connects to the household web router, with the SSID and password being given to the arduino over a serial connection and stored in EEPROM. Most of the web page content is on wordpress or something.

For the ‘art installation in a shipping container’ situation … perhaps you’d want to set up a router.

Arduino communicates via serial

That is, you have a computer hidden behind a cushion somewhere and an app running on it talking to the arduino over USB. I’m tending to think this would be the best option if you want to let the system communicate with the general public, simply because it allows you to use a domain name. Have the domain name point at a server on that computer, and have it connected via serial to the microprocessor. To keep people from fooling with the exhibit remotely, have a password that changes periodically.

In general

In general, it helps if you have a fairly definite idea of what you want a system to do. “It should have public interaction” needs to be narrowed down a bit.

Any system that involves serving up web pages is going to need something better than a microprocessor to actually serve them up. A microprocessor can do it, but not well.

I would not be inclined to attempt this remotely, communicating via skype or email.

Physical installation

So, the big day came. We were going to actually mount these lights into Tracey’s art thing.

The art thing turned out to be a fabric covering over some seashell forms made of bubble-wrap. (Which doesn’t do the art side of things justice, of course. Is the Mona Lisa just some pigment on a board? If your job is maintaining the Lourve climate control, then yes.)

The lights themselves – I should mention – were more like christmas lights than like the neopixel strips available from adafruit. Attached to the forms, they did not lay down in neat rows, which was good.

We decided to lay the lights on top of the forms under the fabric. We tried laying them under the bubblewrap, but the light was a bit too diffuse and dim. In the end, we simply stuck them down with tape.

Strings of neopixels can be cut. To operate, all that needs to happen is that they need to be supplied with 5v power, and a data line needs to come out of the out of the last pixel on one string and into the in side of the other. I was concerned that chopping the strings up into fragments might ruin the effect, but this turned out to be not the case. For the ‘nautilus’ shell, we attached a string in a spiral. For the scallop, we cut a length of string into five strips, lad them next to each other, and connected them. The ‘wave’ effect was still evident, or evident enough. Enough that you could say ‘yep, that is totally meant to be the sea’, which is all you need really.

Connections were done from the arduino to the pieces and between each piece using brown automotive wire and automotive ‘bullet’ connectors. This was very horrible, bulky, and ugly. I have since looked at using modular phone handset connectors for the same job, but these are also pretty horrible and the socket for a modular phone connector is bulky.

In retrospect, a three-conductor stereo audio cable with 1/4″ jacks might have been better. An issue might be capacitance, which would limit the length of the the data line.

You cannot power lights like this off the microprocessor pin. We used a separate power supply. The arduino and the neopixels both use 5v USB power, so any USB power-pack is suitable and proved adequate. Power was taken out of this for both the lights and the microprocessor.

Speaking of which, originally I had a UNO-3 set up for all this, but during installation I decided it was a little bulky. I had a leostick clone in my parts box, and soldered connections onto that. Much smaller.

So, how did it go?

It was just not bright enough. Originally, the exhibit was outdoors. Outdoors on an Australian spring day. It’s just not feasible to do any sort of light show it a situation like that. The Australian sun is not just bright, it’s full of UV which physiologically makes it even brighter. Fireworks are not bright enough on an Aussie spring day. 5v LEDs? They never had a chance.

The exhibitors managed to get it moved inside, which at least made the display visible. As I mentioned, the tidal stuff was a mistake, making the lights dim in comparison to ambient.

But, it was rather pretty overall. The power supply worked just fine, and the thing was nicely in context. A couple of people stopped and looked for a moment, which is mostly all you can hope for. Unappreciated art is almost proverbial.

I’m thinking of doing something like this, but in a shipping container. Do you have any advice?

Jeez. Shipping container eh?

I’m pretty sure a string of 50 5v LEDs isn’t going to cut it there, either. Not if it’s a major part of the installation. Here’s some 12v neopixels. Here’s some more. On the plus side, they’re 12v and bright as. On the minus side, it’s $50-$60 for 20 of them. You are not going to get away with much change from a thousand bucks if you want to have an effect encompassing the whole container, no matter how you do it.

You’ll want to inject power into them at intervals – don’t just try powering them from one end. The current will set fire to something. Power each strand of 12 individually. Data is less of a concern. You can daisy-chain everything you have, or run separate logical strands. Adafruit will happily sell you a power supply, but if you’re in Norway then you’d be better off just going to an electronics shop. The nice thing about 12v is: car battery.

I’d get online and search “outdoor stage lighting”, or “DMX”, or I’d head over to “Capital Sound and Lighting” (or the equivalent wherever you happen to be) and talk to those guys. Rent something. I’d explain the them that gosh darn it I’m an actual computer programmer and I can make DMX-driven lights dance, which means actually getting one or two from somewhere and playing with them beforehand so you can justifiably claim that.

An arduino will talk to DMX lights. But there might be better options. Although it’s unlikely that there’s be an off-the-shelf effect box that does what you want, quite possibly there’s a way to get some python code or something talking to an attached peripheral.

In summary

  • The best is the enemy of the good.
  • Some lighting won’t amaze anyone, not in this day and age. Remember that it’s only a support for the actual content of the exhibit.
  • What you are thinking of is probably do-able. It’s going to cost some money.
  • Someone like me needs something concrete to work with first. Art usually happens within the constraints of some medium. The key moment was when Tracey handed me the strand of lights that she wanted me to work with.
  • Nothing is ever quite as good or quite as bad as it first appears to be.
    • Advertisements

Baphomet

19 December, 2016

Like a boss. The code for this is mainly on thingiverse.

20161219_021143.jpg


Wardstone with arduino

28 September, 2015

It ain’t finished yet, but here it is so far. I’m doing the electronics and the panels, Bevis is doing everything else 🙂 .

wardstone1-1

I was concerned that the LEDs wouldn’t be bright enough, that it would be “is that turned on?”

Holy shit.

You can’t look directly into them. I mean, you can, but then its several seconds before you can look at anything else. With a nice orangy-yellow, this thing looks molten.

The main thing is that you cannot drive something like that off a digital out – you will fry your arduino. I am driving these with the 5v off the arduino board via a darlington pair array. This means that power is coming off the regulator, not off the microprocessor.

Darlington pair array chips are designed for stepper motors and will happily handle 24v. They switch fast and have almost no resistance when driven (otherwise driving a motor would let the smoke out). The arduino analog out is pulse-width modulated, so no worries. Don’t need to use the flyback diodes, because the load is not inductive.

The darlington array shorts its output to ground when driven, so this means using common-anode LEDs.

Wardstone-layout-1I will etch some eldrich runes into the panels with the dremel. The idea is that not only will this catch the light, but with 2 differently-coloured LEDs in the base, different parts should catch different bis of the light. The arduino has six analog outs, so the LEDs are wired up as two sets of four on opposite corners.

I used single-core hookup wire for the loom, which was probably a mistake.

As for code – I just bodged something up this evening. light cycles between reddish and yellowish every second or so, using a slightly different period for the two sets. Won’t really know how it looks with the runes until we have runes. May need to make the colours more dramatic.

TODO:

  • move electronics onto a bit of veroboard, provide a controller of some kind – on/off at least. Might be nice to provide a pushbutton to make it strobe white, maybe some kind of “Oh Noes! The wardstobe has been corrupted!”. Our DM is being cagey, unfortunately, so I can’t be sure what he needs.
  • build obelisk that has been broken. Scribe runes first – we want the broken base to have enough runes to look kind of cool.

Jaycar taking all my money

9 May, 2015

My blind opener and closer seems to have bit rot, and I can’t get any feedback from it.

So I bought a shift register (“You bought a shift register?” “Yes, I bought a shift register.”)

Four of ’em. Here’s one in operation.

Yes, obviously it’s just counting. But the counting is done on the arduino, then I shift the 8 bits out to the register. The point being that I could have those bits be anything. Specifically, I could have them indicate the state of the various buttons and the internal state of the sketch.


Classic Yak Shaving. I want my blinds to work, so I need some feedback from my arduino, so I need some status LEDs (more than I already have), so I need a shift register, but I got four, so I have made up an array of 32 LEDs and now I need to wire them up, so I need solder, more gas for the iron, a dremel tool with a cutting wheel, a box to put the dremel tool bits in – you get the idea.

Yum! That soldering job!

The thing that redeems this excursion is that I am interested in shift registers and the general issue of buying ICs and putting them together to make a thing. Oh, and I’ve wanted a dremel tool for some time.

Anyway. I wanted to pack all the circuitry in that jiffy box, but the fact is that I am not going to be able to make it fit. So meh – I’ll just have some ribbon cable hanging out of it and put the shift registers on a small breadboard.

Once that’s done, I’ll be able to hook it up to three pins (plus power and earth) of my blind controller and get some reasonable explanation from it as to why my blinds are’nt opening and closing like the should.


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.