Recently in Projects Category

sam: Project Web Site Launched

| No Comments
I finally got the project web site for the Sam State Machine Engine project launched and added to the Xephyrus Labs index.

Now I'm working on getting the version 2.0 functionality in place.  You know, in my spare time.  (That's a joke.)

The 2.0 feature list is fun.  I've been playing with using Annotations to register the state worker methods (so you will no longer have to do a manual "register()" call), and cycle callbacks, and other fun things.  The feature list is on the main site, too.  (winkyface)

sam: Penney's Game

| No Comments
Let's write a quick little version of Penney's Game using the clever little state machine engine, sam.  Penney's Game is a fun little bar game you can con your friends into playing to get them to pay for the drinks.

I want to focus on the implementation here, so here's a link for you to find out about Penney's Game (if you need it).

Before we start throwing code together, let's talk a bit about how we might put our state machine together.  We have a few options.  We could build a state for each pattern.  If we limit the pattern length to 3, that would mean 17 states: an empty start state, single flip heads and tails states, 4 double flip states covering the possible combos there, 8 triple flip states to cover those combos, then a state for player 1 winning and a state for player 2 winning.

I'm lazy and that sounds like a lot of work.  Plus, if we follow that route, we wouldn't be able to run a 4-flip game.  At least, not without adding in aw geez more states.

Another option is to just have states representing game loop flow, like one for flipping, another for checking victory conditions, and so like that.  That feels nicer to me, so that's the approach I'm going with.

Here are the states I've come up with:

public enum PenneysState
{
  ValidatePatterns,
  Flip,
  CheckVictory,
  VictorFound
}
There are really only 2 working states, Flip and CheckVictory.  But this is supposed to be a nice simple demo, so few is good.

For the payload, we need to track a few things:
  • Player 1's victory pattern.
  • Player 2's victory pattern.
  • The history of flips for this game.
  • Which player won.
We also have to have an id for the payload to fulfill the Payload contract.  Our usage pattern for this game is a single execution, so the id is pretty much useless to us.  Still, a contract is a contract and our honor is at stake.  So, we'll just put a little something in for the id too.

Here's what I have for the payload:

public class PenneysPayload
  implements Payload
{
  public enum Flip
  {
    Heads,
    Tails
  }

  public PenneysPayload (String playerOnePattern, String playerTwoPattern)
  {
    _playerPatterns = new String[] { playerOnePattern, playerTwoPattern };
    _flips = new StringBuilder();
    _id = System.currentTimeMillis();
  }

  public void addFlip (Flip flip)
  {
    _flips.append(flip.name().charAt(0));
  }

  public String getFlips ()
  {
    return _flips.toString();
  }

  @Override
  public Long getId ()
  {
    return _id;
  }

  public String getPlayerOnePattern ()
  {
    return _playerPatterns[0];
  }

  public String getPlayerTwoPattern ()
  {
    return _playerPatterns[1];
  }

  public String[] getPlayerPatterns ()
  {
    return _playerPatterns;
  }

  public Integer getVictor ()
  {
    return _victor;
  }

  public void setVictor (Integer victor)
  {
    _victor = victor;
  }

  private Long _id;
  private StringBuilder _flips;
  private String[] _playerPatterns;
  private Integer _victor;
}
The player patterns are immutable, so there are no setters for those.  The flip history is only additive, so instead of a setter, we only have an adder for that.  The victor is the only settable property.

I'm stashing the player patterns in an array of 2 so that in the validation step I can just walk through the list of patterns and validate each one.  I could have kept them separate on the inside and provided a getAsList method or something like that, but that would complicate the code a bit more than I'm comfortable with.  I kept getters for each individual pattern to emphasize the distinctness of those two patterns.  The intent is that if you're accessing a single pattern, you use the specific accessor, and if you're accessing all patterns, you use the array accessor, but there's no way to enforce that behavior.

I'm tracking the flip history as a String, but only 'H' or 'T' is valid, so I set up an enum to enforce that addFlip is never called with anything other than a valid option.  (Well, a clever coder could still shove null in there to great effect.  Don't do that.)

Once we have the states determined and the payload laid out, it's really just a matter of walking through the steps to put the machine together.  Let's break that guy down by step.

First the validations.  We need to make sure the patterns input are valid, otherwise Strange Things might occur.

public PenneysState validatePatterns (PenneysPayload payload)
    throws PenneysException
{
  int i = 1;
  for (String playerPattern: payload.getPlayerPatterns())
  {
    if (playerPattern == null)
    {
      throw new PenneysException("No pattern for player " + i);
    }

    if (_INVALID_PATTERN.matcher(playerPattern).find())
    {
      throw new PenneysException("Player " + i + "'s pattern contains invalid indicators.  " +
          "Are those Heads or Tails?");
    }

    i += 1;
  }

  if (payload.getPlayerOnePattern().length() != payload.getPlayerTwoPattern().length())
  {
    throw new PenneysException("The player's patterns are not the same length!");
  }

  return PenneysState.Flip;
}
You'll notice that I'm using an exception to indicate validation failures, rather than having an error state.  Sam considers any Throwable to be an error state, and indicates the error condition in the completion notification.

We just check to make sure neither player submitted a null pattern.  And we check to make sure the pattern only contains H's and T's.  Ah, here's that regular expression being referenced:

static final private Pattern _INVALID_PATTERN = Pattern.compile("[^HT]");
Our final check is to make sure both players submitted the same number of flips in their patterns.  We can't have a good, clean, fair game if one guy submits 4 flips and the other only 3, after all.

Sometimes people think I'm quirky because I do things like i += 1 instead of i++.  In this case, it's because i++ is intended as an inline operative, whereas i += 1 is intended as an assignment operative.  So, yeah, they're right, I am quirky.
 
The flip state code is really just a flip.  That makes it nice and simple:

public PenneysState flip (PenneysPayload payload)
{
  payload.addFlip((_RANDOM.nextInt() % 2 == 0 ? Flip.Heads : Flip.Tails));
  return PenneysState.CheckVictory;
}
Grab a psuedo-random value, mod it down to odd or even, and translate that to heads or tails.

That leaves us with the victory checker.

public PenneysState checkVictory (PenneysPayload payload)
{
  int i = 1;
  for (String playerPattern: payload.getPlayerPatterns())
  {
    if (payload.getFlips().endsWith(playerPattern))
    {
      payload.setVictor(i);
      return PenneysState.VictorFound;
    }
    i += 1;
  }
  return PenneysState.Flip;
}
To check for victory, we need to look at the tailing end of the flip history, that is, the most recent flips.  The stock endsWith call takes care of that for us.  So, if we find a match, we set the victor in the payload and jump to the end state.  If we get to the end of the check, we know there was no victor, and we need to flip again.

Since we don't have annotations in sam yet, we still need to manually associate the states with their worker methods.  We take care of that in the constructor.

protected PenneysMachine ()
    throws NoSuchMethodException
{
  super(PenneysState.class,PenneysPayload.class);
  registerState(PenneysState.ValidatePatterns,"validatePatterns");
  registerState(PenneysState.Flip,"flip");
  registerState(PenneysState.CheckVictory,"checkVictory");
}
By the way, that call to super in the constructor is a must.  Skipping that will cause breakages.

Also, remember that end states, such as VictorFound, don't get worker methods associated with them.  That's how sam knows which states are end states.  For those of you who are quirky in different ways than I, you could also use null as an end state.  If any state worker returns null instead of a state, that payload will be considered to be in it's end state.  But I like having a valid, indicated end state.  It serves as a nice resolution for my brain.

The last piece we need to build is the user interface.  We put the machine together so that we could really paint any type of user interface on this thing, a web service, a Swing application, even an FX application.  For our little example, we'll keep it small and simple and just do a command-line interface with output spewing to standard out.  After all, this is about the state machine, not some UI framework.

We will need the state machine to tell us the results of each execution.  So, our main class will need to implement the CompletionListener.

public class PenneysGame
  implements CompletionListener<PenneysState,PenneysPayload>
We'll need a few steps to set up the machine and kick it off before we try to process our game payload.  We do that in the constructor so everything is ready to roll upon instantiation.

public PenneysGame ()
    throws NoSuchMethodException
{
  _machine = new PenneysMachine();
  _machine.setProcessingQueue(new MemoryProcessingQueue<PenneysState,PenneysPayload>());
  _machine.addCompletionListener(this);
  _machine.start();
}
We set ourselves to be notified on completion, and start up the beastie.

To start a new game, we can have a little method called play.

public void play (String player1Pattern, String player2Pattern)
{
  System.out.println("Player 1's pattern: " + player1Pattern);
  System.out.println("Player 2's pattern: " + player2Pattern);
  System.out.println("\nPlaying...\n");

  _machine.process(new PenneysPayload(player1Pattern,player2Pattern));
}
Then, to handle the notifications, here's the notifyComplete part.

@Override
public void notifyComplete (PenneysPayload payload, PenneysState lastState, Exception error)
{
  if (error != null)
  {
    System.out.println("ERROR: " + error.toString());
  }
  else
  {
    System.out.println("Flip pattern was: " + payload.getFlips());
    System.out.println("Victor was: Player " + payload.getVictor());
  }
  _machine.requestStop();
}
This completion handles both happy path and error conditions.  Since we're only playing a single round per execution, we shutdown the machine once we get our first notification, regardless of whether or not that payload ended in an error state.

There's one more little tool we'll need before we can build the main.  We need to be able to have our UI thread pause and wait until the game completes.  It may take any number of flips to complete; we have no way of knowing how long it will be.  We could guess that it'll be less than a second (and we'd be right) and just have the main sleep for a second.  But that's a pretty lousy and brittle solution.  Any change to the system that changed how long that step takes could break it.

In general, it's best to avoid using timed waits for synchronization.

Instead, we could do a timed wait then check to see if the machine is still running.  Something like this.

public void waitWhilePlaying ()
{
  while (_machine.isAlive())
  {
    try
    {
      Thread.sleep(1000L);
    }
    catch (InterruptedException ignore)
    {
    }
  }
}
But then, it would really be better if we could just tell our thread to pause for as long as the machine is active, and continue as soon as it's not.  Good news, everybody.  A machine is a thread, so you can join a machine with all the proud confidence you would join any old processing thread.

public void waitWhilePlaying ()
{
  try
  {
    _machine.join(10000L);
  }
  catch (InterruptedException ignore)
  {
  }
}
Of course, in this case the join works because we're stopping the machine as soon as we get a completion notification.  In a more complex example, you might need to ... well, do something more complex.

Finally, yes, finally, we're ready to throw our main together.  We already have everything coded up, so the main just links all our nice code to the command-line itself.

static public void main (String[] args)
{
  try
  {
    if (args.length < 2)
    {
      System.out.println("PenneysGame usage:");
      System.out.println("  java -jar penneysgame.jar {player1pattern} {player2pattern}\n");
      System.out.println("Each pattern can consist of any number of H's (for heads) or T's ");
      System.out.println("(for tails), however each pattern must be the same length.");
    }
    else
    {
      PenneysGame game = new PenneysGame();
      game.play(args[0],args[1]);
      game.waitWhilePlaying();
    }
  }
  catch (NoSuchMethodException e)
  {
    System.out.println("ERROR: " + e.toString());
  }
}
We do a quick little check just to make sure the user isn't just hunting blindly for usage information, then kick up our PenneysGame and fire off a game.

Cool!  Let's run it and see what happens!  I'll run it with an extreme case, HHH and THH.

Player 1's pattern: HHH
Player 2's pattern: THH

Playing...

Flip pattern was: HTTHH
Victor was: Player 2
And that, ladies and gentlemen, is Penney's Game in sam.

sam: A State Machine Engine

| No Comments
Sam is a Java state machine engine.  I know, it sounds redundant putting that word engine after the word machine.  What I mean is it's an engine for driving state machines.

I'm going to assume you know what a state machine is, and how it's used.  If not, here's some reading for you:


So, now, let's talk about the basic functionality of sam.

There are three basic entities that sam works with: the state, the payload and the machine.  The state is an enum type which defines all the possible states of the machine.  The payload is a pojo which is the piece that is worked upon.  The machine is the code that runs it all.

It might be easiest to walk through a quick example.  Let's say we have a simple A-B-C state machine: A goes to B, B goes to C.  So, first off, let's define our states:

public enum AbcState
{
  A,
  B,
  C
}

Now for our payload, let's have the payload include a little cycle tracker which just tracks how many times it's been called.  This is just to give the payload something to carry.  Normally it would carry the information being processed, along with whatever context is needed to process it.

The only requirement for a payload is that it define an Id field.  We use the Payload interface to enforce that.  So, here's our payload:

public class AbcPayload
  implements Payload
{
  public AbcPayload (Long id)
  {
    _id = id;
    _cycle = 0;
  }

  public Long getId ()
  {
    return _id;
  }

  public int cycle ()
  {
    return _cycle++;
  }
  
  private Long _id;
  private Integer _cycle;
}

For the machine, we just need to define the methods to perform the work of each state.  Each of these worker methods must take the payload as it's only parameter and must return the state upon completion.  In other words, they must be defined with the method signature:

State functionName (Payload payload)

In version 1 of sam, we don't yet have annotations, so each worker method has to be registered with the state machine.  In this example we'll take care of that in the constructor, which seems a fine place to do that sort of thing.  Here's what we have for our simple little ABC State Machine:

public class AbcStateMachine
  extends StateMachine<AbcState,AbcPayload>
{
  public AbcStateMachine ()
      throws NoSuchMethodException
  {
    super(AbcState.class,AbcPayload.class);
    registerState(AbcState.A,getClass().getMethod("doA",AbcPayload.class));
    registerState(AbcState.B,"doB");
  }

  public AbcState doA (AbcPayload payload)
  {
    int cycle = payload.cycle();
    if (cycle < 3)
    {
      return AbcState.A;
    }
    return AbcState.B;
  }

  public AbcState doB (AbcPayload payload)
  {
    int cycle = payload.cycle();
    if (cycle < 8)
    {
      return AbcState.B;
    }
    return AbcState.C;
  }
}

You may notice that there is no worker method for state C.  That's because it's the Done state.  When the state machine reaches a state with no method, it considers that payload done and stops processing it.

Another thing you may notice is that the first register call finds the method and registers it, while the second just passes the name of the method.  Both ways work.  Personally, I like the second method since it's so much easier.

So then, to get this beast rolling and processing something, we need a few lines of setup and configuration:

Instantiate the machine:
StateMachine<AbcState,AbcPayload> machine = new AbcStateMachine();

Set the queue manager (more on that later):
machine.setProcessingQueue(new MemoryProcessingQueue<AbcState,AbcPayload>());

And start the machine:
machine.start();

Then, to plunk a new payload into the machine for processing:
machine.process(myMagicPayload);

One of the things I'm happy with in sam is how easy it is to set up a new state machine.  No twisting abstractions or convoluted declarations.*  Just set it up and make it go.


* Well, other than registering each worker method manually, but annotations will fix that right up good.

sam: The (somewhat boring) PreStory

| No Comments

Way back when, in the olden days, the early ought days, the days when the memories of a big technology bubble were still fresh in my mind, I was racking my brain for something COOL to do.  I would have loved to have come up with something like video-in-flash, or a yeti batting a penguin, but my brain didn't find those ideas.

Flume was a clever little idea to address an issue I ran into all the time: Different groups like to work with different technologies, but some suit always wants to hook together pieces from all those groups.  Flume was a pipelining tool which would take a context and pass it to different technologies.  While I was writing the little proof-of-concept prototype, I thought, Why do I have to write a new state machine every time I need one?  There should be a generic state machine library.  I wrote one as part of Flume, but it was horrid.

Fast forward a few years to an evening with my friend Robert, or Trebor if you like handles.  Robert write beautiful code.  He is brilliant at finding the perfect balance between abstraction and effectiveness.  He also has a thing for genetic algorithms.  That night we had a long conversation about using structured tree languages in genetic algorithms.  It inspired me, so a few weeks later I wrote another state machine for some weekend project playing around with defining a structured tree programming language using XML, but it was a bit too... recursive.

In another few years I started working extensively with workflow engines.  I wrote and worked on several proprietary workflow engines, which all begged to be state machine driven (but weren't).  Finally I wrote a nice big complicated generic state machine Tool.  Notice the capital T?  Yeah, that rhymes with P.  And I now think of that state machine as Trouble.

About 6 months ago I sat down and wrote out a framework for a generic state machine engine which is small and simple.  It's easy to use and easy to forget about, which is nice.

And thus, sam was born.

The Dream of BookVault

| No Comments
Let me tell you a bit about my BookVault dream...

Book databases are the bread and butter of Freshman year CS-101 Introduction to Computer Science.  It's easy to correlate the logical structure with the real world, and everybody understands the difference between a Bag of Books and a Library.  So why would I, with twentycough years of fun and exciting coding projects under my belt, want to build a one of those?

It's all about real estate.  I read a lot of science fiction books.  And I tend to have a collectors mentality.  I want my collection of all the books I've read (those are my books now, I've read them so I own them) so I can peruse them and reminisce and think about the good times we've had together.  Keeping that entire collection means I have to have bookshelves and space for my bookshelves.  The area in which I live right now, floorspace is an average of ~$400 per square foot.

Instead of rows of bookshelves, I'd like to keep my collection in a database on one of my servers.  I'd like to be able to browse my collection, write notes about any given book, search my collection, and so on.

Well, that's all well and good, but there's more.  When I'm at Book Buyers and I can't remember whether or not I already own that fun looking book in front of me, I want to be able to scan the ISBN code of the book with my phone and have it check my collection on my server for me.  I also want to be able to scan a book and have it tell me which books by that author I have in my collection.  Hey, now.

And just to round it out, I want to be able to keep a watch list of books I want to read.  There are all kinds of exciting vectors here: I could have it periodically search online for sales of books on my watch list and alert me if something just got cheap.  I could have my watch list be by author, where it could look up all the books by that author and automatically add any that aren't already in my list.  And so on.

Ah, to dream of books.  This is what my BookVault project is all about.

Bian: Metrics Tracker

| No Comments
What am I doing starting another project?!

Bah, I ask myself that every time I start something new.  Some projects are destined to be worked on indefinitely and some are fated for abandonment, a few even become completed.  But there will always be new ideas to be exercised and vetted out.

I've always been frustrated with the workout tracking and journaling systems out there.  They just don't track what I want to track in ways I want to track them.  (I'm very selfish, you see.)

So, my latest little coding project is to come up with a system which will allow me to track my workouts the way I want them to be tracked.

Like the wide variety of projects I work on, I also do a lot of different types of workouts:martial arts, running, hiking, skating, gym weights, kettle bells, rowing, yoga... and so like that.  I want to be able to track hiking by elevation change, hiking duration and sweat level; but I need to track gym weight work by machine weight, repetitions and sets.

My bian system should be able to handle both beautifully, gracefully, seamlessly, and flawlessly.

And if my system can do all that, it can probably track just about any kind of metric.  Thus, I am not building a workout tracker, but a metrics tracker!  Tadaaaa!

Xenia: Wiring It All Up

| No Comments
The first thing I did this weekend was check the voltage on the new batteries.  They were each between 13.33 - 13.40.  I think at full charge they should be at about 16.92 volts, and fully depleted they're at about 10.80 volts.  So these are maybe a little less than half-charged.

Next up I wired everything for the controller.  Aiyah!  What a pain!  The contactor-resistor-switch setup for this KBS Kelly Controller is just a lot of stuff to have to mess with for a scooter. 

I wanted everything to be clean, so I used "professional" connectors wherever I could.  Which maybe wasn't so professional, I don't know.

I had a problem wiring the switch in because the wires were too small (22 gauge) to fit into any of my connectors.  So I ripped apart the switch and rewired it with 18 gauge wire.

Serene took a few pictures throughout the process:
Xenia WiringXenia WiringXenia Wiring
I also revisited the mounting bracket for the motor.  I added a couple bolts to go all the way across to the other side of the frame, and hooked them to the bracket with hex connectors.  I think that's much more stable now. 

You can see the new bolts on the right side of this pic (click through for a better view):
Xenia Motor Bracket
Now I'm just hoping I can get some time to finish this up in the next few days.

Xenia: Mounting the Motor

| No Comments
The folks over at ModifiedElectricScooters.com really didn't like my idea of using the casing of the old 500 watt motor as a bracket for the new little 2800 watt one.  So, I've been thinking about how to make a bracket for this thing instead.

One detail of the new motor is that almost the entire casing rotates as the motor turns.  One of the ends doesn't, and that's where the bolt holes are.  At some point I realized that I could put the sprocket on the same side as the bracket.  And that led me to the solution I'm trying out now.

I went down to the hardware store with my wife this morning and grabbed a metal electrical box cover.  The biggest they had was 4" diameter, and I paid an extra 43 cents to get one with a knock-out in the middle.  Of course, then, my lovely wife wanted to pick out and buy a bunch of plants and pots, so 2 hours and something north of $100 later I finally got home.  Yes, I do love that woman.

Anyhow, back to my bracket:  When I got home, I used a little jig that came with the motor to draw the hole positions onto the metal cover.  I took a hammer and nail and tapped a little starter indent as close to the middle of each of those positions as I could.  Then I drilled them and punched out the knock-out hole in the middle.  That got me an almost perfectly sized bracket, like this.
Xenia Motor Bracket
The motor bolted on perfectly, and I rewarded myself with a can of Pepsi.  Mmmm, carbonated high fructose corn syrup.
Xenia Motor BracketXenia Motor BracketXenia Motor Bracket
I had ordered a 6mm sprocket when I ordered the motor, so I put that on.  I had to spray both the sprocket and the shaft with WD-40 and tap it with a mallet (ever so gently) to get it on.
Xenia Motor Bracket
When I was at the hardware store I had also picked up some bolts, nuts, washers and a few spacers.  I was trying to line things up so that the sprocket on the motor would line up with the sprocket on the wheel.  I had measured everything before-hand, but I forgot to account for the motor shaft... So, everything is off by about 1/2 an inch.  Heh heh.

I'll have to pick up some longer bolts and see how it all fits.
Xenia Motor Bracket
Since the bracket is just kind of sitting out there in space with a motor on it, I'm a bit concerned that the torque from turning the chain is going to make it flop around.  I added some big fender washers and extra nuts to try to brace everything.  But that might not be enough.  We'll see when I get the electronics working.
Xenia Motor Bracket
My final little exercise of the day was to plunk all the components in the frame and see how they fit space-wise.  I wasn't expecting it to be such a tight fit!  The controller and contactor take up quite a lot of space, and those batteries really aren't much smaller than the originals (though they are a lot lighter).
Xenia Motor Bracket
Next up will be wiring it all together.

Xenia: Parts Pix

| No Comments
I rather expected the new LiFePo batteries to be significantly smaller than the same capacity SLAs.  But they were only a little smaller.
Xenia BatteriesXenia Batteries
The charger, however, was significantly larger.  Heh heh.
Xenia ChargerXenia Charger
I didn't expect the new Brushless motor to be much smaller, but it's positively tiny.
Xenia MotorXenia MotorXenia Motor
The motor has me a bit worried.  For one I'm concerned that it won't be able to produce the amount of torque I'll need to propel myself.  But I guess I'll find that out soon enough.

My my biggest concern is that I can't figure out how to mount that little thing.  It's just too small.  It's like one of those little cell phones that were trendy back in the late '90's that you could barely hold on to.

So far my only idea is take apart the old 500 watter and see if I can mount this motor inside the case of that motor some how, some way.  Maybe I'll come up with a better plan...

Xenia: Getting the Parts

| No Comments
It's been a bit over two weeks now since I ordered all the parts for my 1300 watt stand-up scooter project. 

Most of the parts arrived within a few days of the initial order.  I have a nice little cache building up in the den of all the shiny new parts.

But the motor... the motor did not show up.  I checked my order confirmation emails and found that I had received an email about the motor saying, "Your order has been sent to the warehouse for processing." 

I waited, each day watching as delivery trucks drove past my house without stopping.  I waited, each evening pulling up the web site showing the motor just so I could gaze at it wistfully.  The weekend came, but the motor didn't.

One day, midway through the second week, I forgot to watch vigilantly.  It was a busy day, I was preoccupied, I was distracted.  Suddenly, I realized I hadn't been watching for the delivery trucks.  My motor could be here, out on my porch, just waiting for me to open the door and pick it up!  I gasped and ran to the door swinging it wide... but there was no motor on the porch waiting for me to pick it up.  The porch was empty.  The porch was empty.

Another weekend came, and the motor still didn't.

Finally, I went back to the web site from which I'd ordered the motor.  I found that there was a status next to my order that said, "Processing."  I then found a note that said that the motor was on back-order and there was no expected date of delivery.

WHAT?!

I turned red in the face all the way up to the top of my bald head.  I got up and walked to the living room then marched back and sat down in front of my computer again. 

Amidst a flood of good old American self-righteous indignation, I canceled that order.  And it felt good!

It took a few minutes for me to realize that I had no motor, that no motor would be showing up, that I had to find another motor for this project.  (sigh)  Asearching I went, to find me a motor that would fit my needs. 

I never did find one that had the shaft-size I needed.  And I couldn't find a 6000 watt beastie either, at least not for a reasonable price.  So, I ordered a 2800 watt one; since I'm only planning to push 1300 watts anyhow, it's still way overkill.  And I ordered a new front sprocket to fit the shaft of the new motor I ordered.  Great.

Now imagine my surprise when the motor showed up on my porch The Next Day.

Right.  So, I got the parts, I'll take some pictures and throw them up.

About this Archive

This page is an archive of recent entries in the Projects category.

Journeys is the previous category.

Ramblings is the next category.

Find recent content on the main index or look in the archives to find all content.