2020-08-01

Goovi EV-693 Repair

For the last 9 months or so our go-to hand vacuum has been the Goovi EV693. We purchased it after moving into a smaller space and ditching our full sized upright Dyson. Moving made the budget tight and so we were looking to optimize our vacuum purchase. Research lead us to the high reviews of this cordless stick with a sticker price less than half that of a Dyson. We figured if it didn't hold up we would invest in a better unit.

To our surprise this unit operated FLAWLESSLY for months. We also have a Neato Botvac-connected that handles our day to day cleaning so the Goovi would come out for spills or getting into nooks too small for a robo-vac. We love this vacuum but thought we killed it when we had to clean up a large quantity of baking soda. My first thought was that we got baking soda in the motor and too much friction was causing it to trigger an over current device. The unit would run for about 30 seconds but become very finicky where the motor would cut out randomly and run for only short durations. During this the vacuum would sometimes turn itself off while other times the motor would stop for a few seconds and then turn back on.

I was wrong and found that the only issue was a loose connection to the motor with the signal wire that tells it to turn on. After a difficult time getting it apart (It is unfortunately designed to not come apart) I was able to bend a connector to fix the alignment of the pin and put it all back together. 

But lastly about that baking soda. I swear not one particle of baking soda made it through the filter. The black inlet to the motor should show any light colored dust that makes it through the filter. We have never had ANY. That filter is amazing and it is a shame you can't buy new ones should anything ever happen to it.

Amazon link (No referral): https://smile.amazon.com/dp/B081Z826R7/

So here is a guide to hopefully help any others that need to repair their goovi.

Things I learned in this repair:

  • The control circuitry is all contained in the battery.
    • All the buttons and motor control and charging wires just route to the battery connector.
  • 14 Screws and 3 retention clips hold the two halves together.
  • There is a current sensor in the battery, if the motor does not turn on or turns off despite being given an "on" signal, the battery will disconnect power to the motor.
  • Both buttons actually share a single signal wire
First things first, this is for the Goovi EV-693. The sticker usually covers the seam between the two halves. It can easily be moved with a putty knife or a razor blade to catch the edge of the sticker. Simply peel it back and place it elsewhere as shown below.


I made a cardboard guide for the screws as I remove them. ** They are different lengths. ** You must keep track and return them to where you removed them. I recommend also tracing the vacuum on a piece of cardboard and placing screws accordingly as you remove them.


Be careful of the stick release button, it is spring loaded and after removing the screw below it it may shoot across the room if you flex the two halves of the gray plastic body. Remove the dust bin and start by removing all the screws you can see. There are 5 that can only be seen after removing the dirt bin. There are 4 screws behind the orange accent pieces, don't worry about them yet, we'll remove them in a moment. If it hasn't come out already, carefully pull the two halves by the stick release button slightly apart and remove the button and spring.

Now to remove the orange plastic pieces. Start with the handle piece. It has tabs on the top and bottom and 4 on the sides that grip the gray body. This piece is a giant PAIN IN THE ARSE to remove. I removed it from the bottom with thin trim tool, it /might/ be easier to unclip the top of the piece but I would use care to avoid damaging that clip. If you tackle the bottom first you will need to unclip at least one on both sides then pull the bottom out to release the end. The bottom end clip is quite large so you have to do a lot of prying.


Another angle showing the large bottom clip. You can also see two tabs that make uncliping the sides a challenge.


After removing it, you can see the buttons underneath.


Removing the orange trim through the center is much easier.  A narrow trim tool can be used to work your way front to back. Do this on both sides and once it is free it should slide straight back.


A view of the tabs you are releasing from the gray body.


Remove the 4 screws you exposed, you should have now removed all 14 screws

The last difficult part. Using a tool (such as a flathead screwdriver) release 3 tabs holding the two halves together. Two on the top and on on the base of the handle. Definitely use these images to help you locate them so you can depress and release them. They were a pain to locate without knowing they were there.



Now, the two halves should easily separate. Note the notch the motor grill is in as well as the two grooves the motor mounts are slipped into. Note the green wire in this photo is the signal wire that tells the motor to turn on.


Three screws can be removed to expose the control board in the motor. The connector at the top is where the green wire plugs in to turn the motor on. On our vacuum the connector was damaged/defective. A single pin in the connector (the pin for the green wire) was bent and was shallower than the other pins in the connector. This was causing it to make a bad connection. I tested this by turning the vacuum on and wiggling the wire and connector. With the wire not plugged in, I used a pair of pliers to pinch the connector slightly where the bent pin was making it return to its proper position. I tested again and the vacuum worked properly when wiggling the wire instead of the motor cutting in and out as it had before.


When putting the vacuum back together it is mostly a matter of reversing the process. Most of it is easier than taking it apart with the exception of reinstalling the motor and grill such that the grill screw holes align properly. The motor and grill are quite snug together making it particularly frustrating for how simple it is. Also make sure the wires are routed through the notches in the gray body before putting the two halves together as you don't want to pinch a wire.

2017-01-29

Raspberry Pi, Multicast, UDP, & WIFI

I've been working on a project recently to synchronize multiple raspberry pi's playing videos. The goal was to be within 50ms (about 1 frame) of each other. I'd started testing with a Pi-3 and a Pi-1B but quickly realized the Pi-1B was going to be painful to diagnose issues with the synchronization vs just being slow. The Pi-1B took a LOT longer to start playing a video and had more latency in seeking/changing speed. So I switched to testing with two Pi-3's and to my surprise, it was worse. I banged my head for weeks trying to figure out why different configurations behaved radically different. Data I collected with a packet sniffer was contradictory. In some configurations, specifically any configuration where a Pi-3 was the slave in the synchronization scheme, the latency from the master Pi had a jitter of 300ms. I would receive 10-20 synchronization packets at once then there would be silence for 300ms. With the Pi-3's I had been using wireless because for convenience that would be the easiest way to use them in production. I was guessing going to a wired connection would correct the issue but it bugged me that a wireless link with <2ms of latency with the ping command regardless of packet size would have such a wild jitter with UDP packets. It took a long time for the significance of the Pi-1B being wired and the jitter only appearing when the Pi-3 was a slave. It didn't help that when I used Wireshark on my desktop the packets came spaced give or take 2ms, in contrast my laptop would show the jitter. I was doing each test on a different day and then last night it finally hit me, Multicast packets from a device to my rt-ac66u over wireless or wired have little latency when viewed on a wired connection like my desktop. In contrast, those packets were being buffered and delivered to all wireless devices only 3-4 times per second. ICMP packets were being shipped same-day, but UDP multicast/broadcast were being snail-mailed in batches. GRR. I never did find a setting on the router to alter this behavior and have simply conceded to using wired connections for the Pi's. If anyone has thoughts on this phenomenon, I would be glad to hear them in the comments.

2014-07-21

Google-Drive, Fuse, and Docker

Today I was fiddling with an idea I had of using Google-Drive to store configuration information for docker images. It is much too slow to do anything that would hit it on a regular basis, but for configuration I think it could work quite nicely.

On that note I went out to explore the idea of a Google-Drive docker image. The idea being you would use the volumes-from capability to access the information from the container connected to google drive which would also likely need extra privileges.

On that I was correct, the internet recommends time and time again that to get Fuse to work you need

--privileged=true

I was curious if there was any way around that and found that indeed it is possible to only allow access to fuse by instead using

-v /dev/fuse:/dev/fuse

That maps the device into the container and surprisingly works.

For those interested in my dockerfile:

FROM base/archlinux

RUN pacman -S --noconfirm python2-pip fuse
RUN pip2 install ez_setup
RUN pip2 install --allow-unverified antlr-python-runtime --allow-external antlr-python-runtime google-api-python-client gdrivefs


You will need to get credentials as described in https://github.com/dsoprea/GDriveFS

2013-09-03

Wordpress

I thought it was about time I start using Wordpress for my website. Actually, for quite a while I have wanted to switch to using a CMS instead of using some custom integration with phpBB. I had dabbled in Drupal and Joomla and a few others and always got frustrated. I installed Wordpress a long time ago and didn't make much progress.

However I had decided to give it another go since Dreamhost added one click install. The admin interface appears to have matured and after some googling to get a decent blank slate theme I have started work on porting my current sites theme to Wordpress. The current site is full custom and I have been wanting to move away from the dark theme for a while, however I am in love with the general layout and wanted to preserve it. I have made decent progress on core layout and modernizing certain elements of the design, including dropping support for IE <8.

One annoyance though was the new Admin Bar (or Toolbar). My theme utilizes a height:100% style on html and body. The admin bar adds a margin to the top of html which made my theme scroll funny. This only matters if you are logged in (aka, just me) but it pissed me off so I wanted to fix it. After scouring the internet I pieced together the steps to unhook their addition of the margin and add my own hook to adjust the padding instead. Just add this to functions.php in your theme:

function pad_admin_bar() {
  echo '
    <style type="text/css">
      html {
        padding-top: 28px !important;
        box-sizing: border-box;
      }
    </style>';
}
function my_admin_bar_init() {
  remove_action('wp_head', '_admin_bar_bump_cb');
  add_action( 'wp_head', 'pad_admin_bar' );
}
add_action('admin_bar_init', 'my_admin_bar_init');

The box-sizing trick makes my height style still work as expected. Now I can be happy and move on to the parts of the theme normal people see.

2012-02-03

Akka Untyped Actors & Google Guice

I started using Akka Actors in my Scala project and wanted to make them play nice with Google Guice.

Akka Actors are STRICTLY (I don't think you understand how strict) enforced to be instantiated in a certain way.  So I went on a trek to do this.

First off, Akka Actors are wrapped in an ActorRef. This ref has no reference to the underlying Actor type. An ActorRef is an ActorRef is an ActorRef. So to differentiate them you must use annotations. Not wanting to make a new annotation for each I went with the provided @Named annotation. This leads to a not so terrible injection of:

class WeaveCompiler @Inject() ( @Named("WeaveActor") val weaveActor:ActorRef ) {

That's all fine and dandy, and satisfies the first half of the binding:

bind(classOf[ActorRef].annotatedWith( Names.named("WeaveActor") )

But now we need our ActorRef. We can't let Guice do it for us since we have an Actor and it needs an ActorRef. Time for a provider. I didn't want to have to make a provider for every single Actor so I made it generic. Java type erasure quickly bites us in the ass but there are ways around that there are over my head. In short, the following works:

.toProvider( new TypeLiteral[ActorProvider[WeaveActor]]() {} )

So TypeLiteral is provided by Guice and takes care of that type erasure headache. The ActorProvider is my own special glue that I will provide below and WeaveActor is my Actor that extends akka.actor.Actor

So here is my glue:

import com.google.inject._
import akka.actor._

class ActorProvider[T <: Actor] @Inject() ( val m:TypeLiteral[T], val injector:Injector ) extends Provider[ActorRef] {
  def get = {
    Actor.actorOf( injector.getInstance( Key.get( m ) ) ).start
  }
}

Because of how akka actors work the Actor object MUST be created inside a call to actorOf. Fail to do that and you get a horrible exception.  Thus the provider gets the TypeLiteral for the actor injected with the Injector from Guice. Then uses those to get an instance of the Actor from the Injector inside the call to actorOf. Then it starts the actor and returns the ActorRef.

This solution seems simple, but I spent the better part of 4 hours to figure it out.  There is probably still something missing (I haven't checked what happens if you inject the same actor in multiple places) but that can be an exercise for the reader. If I find any problems I'll make another post about it.

Hope this helps!

2012-02-01

Tip: Guice Multibinder & Scala Set


I am using Guice Multibinder with Scala and this took me way too long to think up. To make things nice I want Scala Sets not Java Sets. So the following is a nice way to do it, make a constructor with Java types and call the one with Scala types.

import scala.collection.mutable.{Set => MutableSet}
import scala.collection.JavaConversions._

class MyClass( val t:MutableSet[T] ) {
@Inject() def this( t:java.util.Set[T] ) = this( asScalaSet(t) )

//...

}

2012-01-19

Parboiled, Case Classes and Headaches

I had the lovely task today of figuring out how to not use case classes in Scala for my AST.  My AST demanded as structure that resulted in conflicts of overriding constructor parameters when case classes were used. This meant I needed to build the class myself, and it took all day to get it right. My first attempt was the following as I found a blog post stating that Parboiled only depends on the apply method:

object ImportViral {
  def apply( packagespec:PS ):ImportViral =
    new ImportViral( packagespec )
}
class ImportViral( packagespec:PS ) extends ImportStatement( packagespec ) {}


And my respective rule in the parser:

def ZImportViral = rule { "importviral" ~ WhiteSpace ~ ZPS ~~> ast.ImportViral }

But this resulted in a rather ugly error:

error: overloaded method value ~~> with alternatives:

[X, Y, Z, R](f: (X, Y, Z, ast.PS) => R)org.parboiled.scala.rules.ReductionRule3[X,Y,Z,R] and

[Y, Z, R](f: (Y, Z, ast.PS) => R)org.parboiled.scala.rules.ReductionRule2[Y,Z,R] and

[Z, R](f: (Z, ast.PS) => R)org.parboiled.scala.rules.ReductionRule1[Z,R] and

[R](f: ast.PS => R)org.parboiled.scala.rules.Rule1[R]

cannot be applied to (net.codingwell.weave.languages.silk.ast.ImportViral.type)

I figured out pretty quick that the issue was f: ast.PS => R meaning it wanted some sort of conversion from my PackageSpecification to "R" (ImportViral). So obviously I did something wrong. I fiddled for half the day before trying hardcoding the rule type.

def ZImportViral:Rule1[ast.ImportViral]

This gave me a new only minorly more helpful error.

found   : net.codingwell.weave.languages.silk.ast.ImportViral.type (with underlying type object net.codingwell.weave.languages.silk.ast.ImportViral)

required: net.codingwell.weave.languages.silk.ast.PackageSpecification => net.codingwell.weave.languages.silk.ast.ImportViral

So again, my object isn't a conversion. After another several hour sprint trying to figure out what a case class is equivalent to in code. I found a decompiler http://java.decompiler.free.fr/?q=preview
Plugged in a case class from my AST and looked for differences. Took me a little bit to notice but plain as day, the object was inheriting from Function1. The magical glue to make a function object in scala. Now I am no Scala expert so this also taught me that objects can inherit from different things than their companion class. Who Knew?

Final working code:

object ImportViral extends Function1[PS,ImportViral] {
   def apply( packagespec:PS ):ImportViral =
     new ImportViral( packagespec )
}
class ImportViral( packagespec:PS ) extends ImportStatement( packagespec ) {}


This code can be found on github: Github - Deathbobomega/Weave - ast.scala