Saturday, January 28, 2012

Replacing the motor on a Maytag MDE3000AYW dryer...


About two weeks ago, one of these 1999 Maytag dryer models suddenly stopped working. You could push the Start button but all you would get is a low-hum. Suspecting that the motor died, I decided to open up the unit up to see if I could confirm the issue myself.  NOTE: apparently if you try to call a Maytag service person at this point, they'll actually charge you extra for being forced to figure out what you did!  It actually took at least 5+ hours to get this whole unit disassembled and reassembled, so also don't try if you really have a pressing need to clean your dirty underwear.  A technician may work faster too if you don't have all the tools are your disposal.

Maytag still keeps its information about these models online, and this particular model the links are posted below.  However, you won't find any repair guide, since the company normally recommends that you contact an authorized technician.  Nonetheless, you can get a list of parts information in case you want to get a rough estimate about the cost.  Pandora OEM, which sells off Amazon.com, can deliver your parts from Fresno, CA within a few days, and they even respond to email inquiries on weekends so double-check with them about your model before you order your parts.

https://www.maytag.com/digitalassets/MLPDF/Use%20and%20Care%20Guide%20-%206-3702450.pdf (Owner's Manual)

https://www.maytag.com/digitalassets/MLPDF/Dimension%20Guide%20-%20DNDRY.pdf (Dimensions Guide)

https://www.maytag.com/digitalassets/MLPDF/Repair%20Part%20List%20-%20MDE3000AYW.pdf (Parts List)

You can however find the service manual by doing a Google search for the Dryer Service Manual (16023110.pdf) for this model.  Background info: Apparently the way these models are named is that M represents the brand, DE represents an electric dryer (DG is gas dryer), 3000 represents the model no, A is the production code, Y = 240 volts, and W represents the color (white on white).

If you want some context about how to disassemble dryers for Maytag models, there are several YouTube clips that provide a good overview (PartSelect.com and RepairClinic.com do an excellent job explaining the step-by-step process). A dryer consists of a pulley system, a belt, motor, a cynlindrical tumbler for turning your clothes, a heating element, and a fan (along with a thermal fuses to keep you from burning the whole unit up). The videos make things a lot easier than they seem, but many of the aspects of taking apart a dryer especially related to the retaining clips and clamps were fairly consistent even with this older 1999 model.





A brief overview of many of the tools that I ended up using:

- Safety goggles and worker gloves
- Ohm-meter (for continuity testing)
- 5/16" socket wrench (and 7/8" if you actually want to pull off the motor pulley)
- Screwdriver
- Adjustable wrench
- Snap ring pliers (for removing the snap ring on the blower wheel)
- Vice grips (for removing the blower wheel clamp)
- Replacement motor w/ a motor pulley
- Optional: blower wheel & replacement belt (the blower wheel can be a pain to remove since it may be fused to the motor shaft after many years of use).


Disclaimer: do not try any of this work without the plug for the dryer disconnected.   You may also want to verify that your circuit breaker didn't accidentally trip and therefore is actually supplying no power to your unit (if you see a lamp on when the lid is open, chances are there is power).  In addition, verify that the thermal fuse simply needs replacing.  The thermal fuse can be seen at the bottom of the dryer, and you can use an ohm-meter to verify that there is still connectivity.  In other words, check everything before you decide to replace the motor since it's a fairly involved procedure.


In order to get to the motor, you need to first remove the hinges from the door (see Chapter 5 - Cabinet Assembly Components).  The hinges can be removed with a screwdriver, and the door can be pulled from the slots in the wall.   You can then remove the outer shell of the once these hinges and the screws attached to the door are removed.


Once you've removed the front door (see Section 6 on Tumbler & Bulkheads in the service manual) you can remove the front bulkhead by starting to remove many of the screws that you see.  What's confusing is that the instruction manual says "Remove the lowest outside screw at each corner and then the top screw on each side") in step 4.  What the manual is really saying is that there are another set of screws holding the tumbler in place, so the manual wants you to remove the screws that aren't attached directly to the tumbler.  By doing so, you'll disconnect the first layer of the dryer before you get to the tumbler.
There are also "hold down brackets" shaped as a horse-shoe that keep you from being able to lift the lid of the dryer. Once you unscrew the bolt from the bracket with a 5/6" socket wrench, you swing the bracket to the side (see Figure 5-5).  Chances are the bracket will fall out, so it's better just to stow them away somewhere.

The manual covers how to remove most of the front assembly, but basically you need to remove a lot of screws to even get to the tumbler and then to the motor. You'll notice that the tumbler actually is held in the front and the moment you start removing the screws, it may start to no longer align with the back of the dryer cabinet.

The tumbler itself may need to be removed, which requires you to crouch down and try to locate several pulleys in the back.  One is the motor pulley, which is attached the motor.  The other is known as the idler pulley, which is attached a spring and helps provide the tension of the belt.  You should be able to pull the idler pulley to the left, which will remove the tension from the belt and allow you to take it.  Consult the YouTube clips to see how it's done.   This model's instruction manual specifies explicitly that the belt should be ribbed-side down on the tumbler, whereas other models seem to indicate the opposite.



Once the tumbler is removed, you'll eventually get to the blower wheel, which amounts to a small fan at the bottom of the dryer.  You need to remove the blower wheel since it's attached to the motor shaft and the motor can't be removed until you detach this blower wheel.   Basically there are two clamps attached to the blower wheel: the retaining clip and clamp.  The retaining clamp usually requires snap-ring pliers to remove (Home Depot sells them for $20, but you have to make sure that the pliers can fit into the small holes of this retaining clip.  Orchard Supply Hardware carried a pair but the attachments were a bit too large.)  The clamp itself can be removed with vise grips.  If you squeeze the outer protruding edges, the clamp will actually expand (the video clips may show this point, but it's not exactly obvious since it happens so fast).

Make sure to keep the retaining clip and clamp, since they can be reused when reinstalling the blower wheel.  Once you've successfully removed the retaining clip and clamp, you'll need to remove the blower wheel itself.  The instructions in the service manual say to "work blower wheel back and forth while pulling off shaft...[and] may require considerable effort to remove the blower wheel."  The wheel itself may be somewhat fused to the shaft after so many years of use, so adding some lubricant (careful not to spray it elsewhere in the dryer, since the substance may be flammable) to help remove the wheel.   A replacement blower wheel can cost $20-$30, but makes it much easier to attach to a replacement motor so I'd highly recommend replacing it too if you're also planning to change the motor.



Once the tumbler blower wheel is removed, you should be able to see the motor.  There should be a plastic connector adapter that is attached to the motor that needs to be removed by pushing on both sides.  The connector is keyed to only plug-in a certain way, so you shouldn't have to remember which way to reinsert the connector when replacing the motor.  The service manual contains the resistance that you could measure from the controller panel to the terminal (i.e 3.00 Ohms from terminal 3 of the start switch to terminal 4), but I found opening another panel to be extra hassle and decided to just replace the battery to see if it would solve the problem.  There are YouTube clips that also show how you can create a test cord to verify the motor works independently, but there isn't an off-the-shell unit so you'd have to do soldering work and find a 220-volt three-prong adapter so I decided to opt against making one.

After the plastic connector is removed, the next step is to remove the motor.  There are two retaining clips on the two sides of the motor, which keep the motor in place.   You need to remove them by pushing down on one side of the tab, which will hopefully unlatch the tab.  Again, the YouTube links are a good place to see how to remove these retaining clips.  You shouldn't have to remove the screws attached to the base of the dryer cabinet, but it may be easier to manuever.


At this point you should check if you have the same replacement motor as the one you purchased.  Some motors don't include the motor pulley.  If you don't have the motor pulley with the replacement motor, you either ordered the wrong motor, especially for this model.  Other MayTag units seem to require that you take it from the old motor using an Allen wrench.  (For this particular brand, if you wanted to remove the motor pulley, you could use a 7/8" socket wrench and a wrench to hold one side of the motor shaft steady you loosen the screw attached to the motor pulley.  I wouldn't recommend spending the time since the replacement motor you ordered should come with it.)

At this point, you should have successfully removed the motor from the dryer.   Now you have to reverse the process and reinstall the replacement one.  There are several things that are key to know.  First, you may find it easier to remove the blower wheel casing that sits adjacent to the motor before installing the motor, since the motor has two cylindrical circles that need to be aligned with the motor housing.   The retaining clips need to be added, and you may find one side easier to latch than the other. The plastic connector on the battery terminal should then be reattached to this new motor.

Reattaching the belt also proved to be different from how the YouTube clips demonstrated, which often call for looping the belt around the idler pulley first before attaching to the motor pulley.  For this particular model, you actually want to follow the instructions specified in "Installing Drive Belt" which call for looping the belt around the motor pulley first.  You want to position the ribbed-side of the belt against the tumbler and you should loop the belt around the motor pulley first ribbed-side.   You then want to pull the idler pulley left and up (only pull to the maximum spring tension allowed) and attach the 
belt.  In this way, the remaining slack from the belt is taken up by the tension in the idler pulley.  You should be able to rotate the tumbler now and have everything rotated correctly.

You shouldn't have had to remove any electrical wiring, but if you inadvertently detached a cable, you should also check that wiring connections are correct.   For the dryer lamp, there are two connectors (one ground and one power), and one plug is bigger than the other, which makes it easier to distinguish between the two.   Since the door light controls whether the start switch can be used, you actually need to reinstall most of the unit to verify that your motor is successfully work.  You can obviously take shortcuts and attach the door without screwing everything back, but do it with caution.

The rest of the installation pretty much involves reversing what you just did.  Remember that the lint is flammable, so you would be well-advised to try to clean up all the excess lint in the dryer.   Remember that installing the clamp on the blower wheel requires that you use vice grips and squeezing the sides to expand the clamp.  In addition, adding the retaining clip on the blower wheel will require using the snap-ring pliers again.  Finally, you should verify the felt seals are resting on the outside of the tumbler before reattaching everything, which appear to be used to help reduce the friction between the metal cabinet and the tumbler. (You can check by rotating the tumbler and watching where the felt pieces rest).

You should make sure that everything works by carefully monitoring the dryer.  Although there is a circuit breaker that will trip, it's a good idea not to leave the dryer on when you're out of the house after this install process.  You may also wish to find a service technician instead, since this information should only be used as a reference.


Wednesday, January 25, 2012

Using PyCrypto instead of M2Crypto on Google App Engine

If you've ever tried to leverage the M2Crypto library on the Google App Engine platform, you'll notice that you're prevented from doing so. One of the reasons is that M2Crypto depends on C-compiled openssl libraries that are not allowed to be used on the platform. The major alternative that Google provides is the Python Cryptography Toolkit, or pycrypto, which is a Python-based version of various encryption algorithms.

Performance wise, the pycrypto library may not run as fast since it implements the encryption algorithms primarily in Python. Furthermore, the current version that is supported is version 2.3, which doesn't provide RSA PKCS#1 v1.5 encryption support (if you find yourself getting "Block type 01 not supported, you need to handle the padding/encoding format -- see information below) nor does it provide PEM format (you need to encode your keys as DER format, which are basically the base64-unencoded versions of the PEM).

Nonetheless, you can get pycrypto working on Google App Engine. Here's how you can do it inside your App Engine projects. I'll also discuss some of the extra steps needed to get RSA signatures working too (using the PKCS#1 v1.5 standard). It's not completely obvious so some of the stack traces you see will hopefully explain why these steps were necessary.

1. Inside the app.yaml configuration, you need to specify a runtime of python27, and specify the pycrypto librarie. Note: you either have to install python2.7 on your own local dev instance, or you simply have to deploy constantly to Google App Engine to test your app. Note that you also need to specify the threadsafe: parameter too for the Python 2.7 experimental version (see the Google App Engine Python 2.7 docs for more information).
application: YOUR_APP_NAME_HERE
version: 1runtime: python27
api_version: 1
threadsafe: false
handlers:- url: /static  
static_dir: static

...
libraries:
- name: lxml  
version: "2.3"
- name: pycrypto  
version: "2.3"

2. You need to generate your RSA keys with the DER format. If you see this stack trace, chances are that you're trying to provide key formats in PEM. The Pycrypto v2.3 version on Google App Engine assumes you're feeding things in DER format. In other words, these two lines will not work:
>>> from Crypto.PublicKey import RSA
>>> privatekey = open("privkey.pem", "r")
>>> m = RSA.importKey(privatekey)

Stack trace:File 
"/base/python27_runtime/python27_lib/versions/third_party/pycrypto-2.3/Crypto/PublicKey/RSA.py", line 247, in importKey    return self._importKeyDER(der)  
File "/base/python27_runtime/python27_lib/versions/third_party/pycrypto-2.3/Crypto/PublicKey/RSA.py", line 234, in _importKeyDER    raise ValueError("RSA key format is not supported")

You can use the openssl command to convert your public/private key PEM file to DER format. You'll also need to the same if you're using a PEM certificate too:
openssl rsa -outform der privkey.pem > privkey.der
openssl rsa -outform der < cert.pem > cert.der

These lines should the work:
>>> from Crypto.PublicKey import RSA 
>>> privatekey = open("privkey.der", "r")
>>> m = RSA.importKey(privatekey)

If you really want to verify your keys are working correctly, you can also use m.decrypt() and m.encrypt() to encode and encode the data. You can click this link for another good overview about how to use pycrypto.
>>> from Crypto.PublicKey import RSA
>>> from Crypto import Random
>>> plaintext = "abcd"
>>> random_generator = Random.new().read
>>> encrypted_data = m.encrypt(plaintext, random_generator)
>>> m.decrypt(encrypted_data)

3. Next, it's not completely obvious how to use pycrypto to handle RSA signature signing. Most of the heavy lifting is done by the M2Crypto library, but if you're using pycrypto to do you may find yourself having to do some extra work to implement some of the PKCS algorithms.

For PKCS#1 v1.5 (which M2Crypto seems to be using), the private key is signed by the sender using the decryption process (not encryption) while the public key is used with the encryption process by the receiver to verify a signature, which is somewhat the opposite if you wanted to encrypt/decrypt data. (You can confirm this point by reviewing the Latex-based pycrypt.tex file).

In the M2Crypto world, RSA signature can be performed with the following lines:
>>> m = M2Crypto.RSA.load_key_string(privatekey)>>> digest = hashlib.sha1(plaintext).digest()>>> signature = m.sign(digest, "sha1")
In the PyCrypto world, you have to implement PKCS#1 v1.5 yourself. If you get "block type is not 01" when using M2Crypto to verify a signature create by PyCrypto, chances are M2Crypto is expecting the data to be encoded. You can review the RFC2313 spec to see what needs to be done to convert the signature with the correct padding. There is a padding spacing (ps) parameter that determines how many FF bytes should be added, which can be calculated by doing a number.size() call on your private key with the PyCrypto library and dividing by 8 bits (i.e. 2048 bits / 8 = 256 bytes)

The code below demonstrates how you would implement PKCS#1 v1.5 padding. Note that the private key is used to decrypt the data:
import hashlib

   def _emsa_pkcs1_v1_5_encode(M, emLen):
        SHA1DER = '\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'
        SHA1DERLEN = len(SHA1DER) + 0x14

        H = hashlib.sha1(M).digest()
        T = SHA1DER + H
        if emLen < (SHA1DERLEN + 11):
            raise Exception('intended encoded message length too short (%s)' % emLen)
        ps = '\xff' * (emLen - SHA1DERLEN - 3)
        if len(ps) < 8:
            raise Exception('ps length too short')
        return '\x00\x01' + ps + '\x00' + T
4. Currently, Google App Engine only supports pycrypto v2.3. You may encounter issues with using openssl-generated keys to do RSA signature verification. The bug reports for pycrypto v2.3 are posted here: https://bugs.launchpad.net/pycrypto/+bug/898466 https://bugs.launchpad.net/pycrypto/+bug/702835 A bug report has been issued here: http://code.google.com/p/googleappengine/issues/detail?id=5302 If you find issues trying to decrypt/encrypt data, you can bypass this issue by importing your own custom RSA.py version patched from the 2.3 version. Here is the diff that is needed to patch the v2.3 RSA.py version.
--- a/RSA.py
+++ b/RSA.py
@@ -206,7 +206,7 @@ class RSAImplementation(object):
def generate(self, bits, randfunc=None, progress_func=None):
if bits < 1024 or (bits & 0xff) != 0:              # pubkey.getStrongPrime doesn't like anything that's not a multiple of 128 and > 512
-            raise ValueError("RSA modulus length must be a multiple of 256 and > 1024")
+            raise ValueError("RSA modulus length must be a multiple of 256 and >= 1024")
rf = self._get_randfunc(randfunc)
obj = _RSA.generate_py(bits, rf, progress_func)    # TODO: Don't use legacy _RSA module
key = self._math.rsa_construct(obj.n, obj.e, obj.d, obj.p, obj.q, obj.u)
@@ -221,7 +221,8 @@ class RSAImplementation(object):
der.decode(externKey, True)
if len(der)==9 and der.hasOnlyInts() and der[0]==0:
# ASN.1 RSAPrivateKey element
-               del der[6:8]    # Remove d mod (p-1) and d mod (q-1)
+               del der[6:]     # Remove d mod (p-1), d mod (q-1), and q^{-1} mod p
+                der.append(inverse(der[4],der[5])) # Add p^{-1} mod q
del der[0]      # Remove version
return self.construct(der[:]


Hopefully this explains how you can get RSA encryption and signature signing working on Google App Engine without the M2Crypto library! (The last issue was not easy to find. In fact, it seemed very puzzling that RSA signature verification worked fine on local hosts, but never seemed to work on GAE instances.)

Wednesday, January 18, 2012

Forcing Ubuntu 10.0.4 to renew a DHCP client after hibernate/sleep mode...

If you're on a network that has a short DHCP lease and your Ubuntu machine suspends/hibernates, you may find that you often may be IP conflicting with another system on the same network. You can run /etc/init.d/networking but often times you just want to force the DHCP renewal yourself.

One way is to modify /etc/default/acpi-support and add the "networking" module to the STOP_SERVICES declaration for the /etc/default/acpi-support file. However, if you have multiple networking interfaces that need to be restarted on bootup, this could cause more delays after resuming. It also seems strange that you have to taken down an entire networking module just to get things working again.

Another approach is to execute dhclient -r after resuming. As of Ubuntu 10.04, the power management modules actually use pm-utils, though there is a legacy /etc/acpi/ directory that often invokes pm-sleep, pm-hibernate and setups events depending on laptop keys pressed. You can find learn how to write your own power management hooks.

Here's how I got things to work:

1. sudo vi /etc/pm/sleep.d/00_network
#!/bin/bash
case $1 in
hibernate)
echo "Hey guy, we are going to suspend to disk!"
;;
suspend)
echo "Oh, this time we are doing a suspend to RAM. Cool!"
;;
thaw)
/sbin/dhclient -r
/sbin/dhclient
;;
resume)
/sbin/dhclient -r
/sbin/dhclient
;;
*) echo "Somebody is calling me totally wrong."
;;
esac
2. sudo chown root /etc/pm/sleep.d/00_network

To test, run sudo pm-sleep or sudo pm-hibernate. On bootup, take a look at tail -30 /var/log/pm-suspend.log and see if you see the 00_network script resume. If so, you should be good to go!

/etc/pm/sleep.d/00_network thaw hibernate:Internet Systems Consortium DHCP Client V3.1.3
Copyright 2004-2009 Internet Systems Consortium.
All rights reserved.
For info, please visit https://www.isc.org/software/dhcp/


Saturday, January 14, 2012

Upgrading from jQuery v1.4 to v1.5..

Apparently jQuery v1.5+ no longer allows extra data parameters. Instead, it's passed in as extra.data.
$("#mydiv").click(my_func, {'abc' : 123});  (old)
my_func = function(event) { alert(event.data); }
http://code.jquery.com/jquery-1.4.2.js
jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup error").split(" "), function( i, name ) {

// Handle event binding
jQuery.fn[ name ] = function( fn ) {
return fn ? this.bind( name, fn ) : this.trigger( name );
};

if ( jQuery.attrFn ) {
jQuery.attrFn[ name ] = true;
}
});
Note that the data parameter is no longer passed as an extra parameter:
http://code.jquery.com/jquery-1.7.js
jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {

// Handle event binding
jQuery.fn[ name ] = function( data, fn ) {
if ( fn == null ) {
fn = data;
data = null;
}

return arguments.length > 0 ?
this.bind( name, data, fn ) :
this.trigger( name );
};

Friday, January 13, 2012

Thursday, January 12, 2012

Dealing with zombie Facebook cookies...

Not able to logout in your Facebook Connect applications? Facebook recently pushed a change in the past day or so such that the domain= parameter is now added to your fbsr_ cookies. If you find that your cookies are not being removed after a user attemps to logout, chances are you're experiencing the repercussions of these recent changes.

In the timestamped version (1326413659,169943147), there is a section that was added to the Facebook Connect Library:

23402348 }
23412349 return b;
23422350 },
2351 loadMeta: function() {
2352 var a = document.cookie.match('\\bfbm_' + FB._apiKey + '="([^;]*)\\b'),
2353 b;
2354 if (a) {
2355 b = FB.QS.decode(a[1]);
2356 if (!FB.Cookie._domain) FB.Cookie._domain = b.base_domain;
2357 }
2358 return b;
2359 },
23432360 loadSignedRequest: function() {
23442361 var a = document.cookie.match('\\bfbsr_' + FB._apiKey + '=([^;]*)\\b');
23452362 if (!a) return null;

The regexp apparently fails to match because there is an extra quotation mark. Instead of:
var a = document.cookie.match('\\bfbm_' + FB._apiKey + '="([^;]*)\\b'), b;
It should be:
var a = document.cookie.match('\\bfbm_' + FB._apiKey + '=([^;]*)\\b'), b;
If you inspect document.cookie in a JavaScript console, you'll see no sign of how this regexp could match (i.e. the regexp would match fbm_1234="abcde" but not fm_1234=abcde). You can also use the Chrome/Safari Web Inspector, put breakpoints on this function, and use the deminifier feature (look for the {} icon at the bottom) to double-check.

Background info: Before the OAuth2 migration, the fbs_ cookie was used. Included in the fbs_cookie was a query string that needed to be decoded and the base_domain parameter used for the domain= cookie parameter.(For more background about how to set or delete cookies in JavaScript, see: http://www.quirksmode.org/js/cookies.html.)

Cookies be cleared by setting the expiration date to 01/01/1970 GMT. However, most browsers won't know how to delete the cookie unless the path= and domain= parameters are set correctly too. In other words, if you had a cookie named fbsr_1234, with domain=abc.com, the browser would not be able to delete it unless you also specified this parameter.

Until now, OAuth2 didn't include the domain= parameter in fbsr_ cookies. But with today's recent push, it is now being used. The result? If you had an old cookie without this domain= parameter and attempted to logout with this new JavaScript code, you might find that you're unable to clear them. You may also encounter strange logout issues in general and not see the fbsr_ cookie cleared correctly.

When you logout, a cross-domain request gets sent to Facebook to invalidate the session. When you hit reload and invoke another FB.init() command, another request gets sent to Facebook's site -- since FB recognizes the existing cookie as invalid, it correctly deletes the cookie from your browser (the cross-domain response appears to pass in the domain= parameter correctly). Unfortunately the clearing of the cookie initially doesn't work because the fbm_ cookie isn't parsed correctly because of this regexp bug.

Facebook will most likely fix this issue soon, though in the interim your users may not be able to logout of your app. One thing we've done is to use a server-side code to instruct the browser to clear the cookie, though it may not always work unless your page invokes FB.init() properly and receives back a cross-domain request from Facebook to set the domain= properly.

You can examine this code for how you can delete cookies from the server-side:

https://github.com/rogerhu/facebook_oauth2/blob/master/views.py

Want to track Facebook Connect JavaScript changes? Check out:

Tuesday, January 10, 2012

Using FTP/TLS with Python 2.6...

FTP over SSL/TLS can be supported on Python 2.6. If you have prod machines that are running Python 2.6 and you want to avoid upgrading, you can do the following:

wget http://www.python.org/ftp/python/2.7.1/Python-2.7.1.tgz

...untar the file and grab the Lib/ftplib.py file.

You can then do:
from ftplib import FTP_TLS

ftps = FTP_TLS('ftp.mysite.com')
ftps.login('userid', 'pw')
ftps.prot_p()
ftps.retrlines('LIST')

To download a file, you would do:
ftps.retrbinary('RETR file.bin', open('output.bin', 'wb').write)

or to upload a file, you would do something on the order of (borrowed from http://www.daniweb.com/software-development/python/threads/110195)
def upload(ftp, file):
  ext = os.path.splitext(file)[1]
  if ext in (".txt", ".htm", ".html"):
     ftp.storlines("STOR %s" % (file), open(file))
  else:
     print ftp.storbinary("STOR %s" % (file), open(file, "rb"), 1024)

jQuery UI 1.9

Cool slide presentation about jQuery UI 1.9..

http://ajpiano.com/widgetfactory/

Cisco Unified Communications 500 Series - Cisco Smart Business Communication Systems

https://supportforums.cisco.com/docs/DOC-9772

Can the T1/E1 module on UC500 terminate Voice and Data?
The VWIC-2MFT-T1/E1 card on UC500 can only be used for Voice T1 termination. It cannot be used for terminating Data T1s. On UC500, this card does not support multiplexing of Voice and Data. For Data connectivity, using an ISR based T1/E1 is recommended.

What are the total number of PSTN interfaces that the UC500 can support?
Depending on the UC500 model or SKU - can have any of the below:
4 to 12 FXO ports or 2 to 6 BRI ports (VIC slot can be used to add interfaces in certain model)
T1/E1 VWIC interface card for use in the 8-, 16-, and 32-user UC500 SKUs (VIC slot can be used to add this T1/E1 interface card)
UC500 48-user SKU is also available with integrated T1/E1 interface.

What T1 parameters are supported on the UC500 for voice termination?
The below parameters are supported:
T1 line coding = B8ZS
T1 framing = ESF
Interface switch Type for PRI - National with FAS (User side)
Hunt Type = two way, descending (channel 23 to 1)
Switch Protocol = See ISDN PRI switch-types above

Tuesday, January 3, 2012

Tracking the SWF file..

Facebook doesn't often change the SWF file that is used for its cross-domain handler, but sometimes it's useful to download automatically the right binary file:


SWF_FILE=`grep --color=none all_deminified.js | "_swfPath\": " | sed -E "s/(.*?)swfPath\": \"rsrc.php(.*?.swf)\"/\2/" | sed 's/\\\\//g'`
/usr/bin/wget "http://static.ak.fbcdn.net/rsrc.php/${SWF_FILE}" -O ${SWF_OUTPUT}


https://github.com/rogerhu/connect-js/blob/master/update_fb_github.sh

Monday, January 2, 2012