Friday, September 16, 2016

Script to retrieve and sort Windows 10 Spotlight images

Windows 10 automatically downloads and displays beautiful images on your lock screen, but offers no obvious way to set them as wallpaper. This script tries to help by copying the current set of downloaded images to a folder on your desktop and doing some sorting for you.

You have to create the target folder on your desktop manually first as a sort of sanity check. By default it needs to be named 'spotlight exported'. Then run spotlight.vbs.

spotlight.vbs:

'************************************
'*                                  *
'*  find and copy spotlight images  *
'*                                  *
'************************************
'Important:
'You have to create this folder on your desktop: 
strImageFolderName = "spotlight exported"

'****************************
' Do stuff
'****************************
Const imgError = 0
Const imgLandscape = 1
Const imgPortrait = 2
Const imgSquare = 3
strDebug = ""

Set objShell = CreateObject("WScript.Shell")

strSrcFolder = objShell.ExpandEnvironmentStrings("%localappdata%") & "\Packages\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\LocalState\Assets"
strImgFolder = objShell.SpecialFolders("Desktop") & "\" & strImageFolderName

Set objFSO = CreateObject("Scripting.FileSystemObject")
If objFSO.FolderExists(strSrcFolder) Then
    If objFSO.FolderExists(strImgFolder) Then
        msgres = MsgBox ("This script will copy files to '" & strImgFolder & "'." & vbNewLine & "Do you want to continue?", vbYesNo, "Spotlight Image Copy Script")
        if msgres = vbYes then CopyStuff() else Wscript.Echo ("Quitting...")
    Else
        Wscript.Echo "Image folder does not exist " & vbNewLine & strImgFolder
    end if
Else
    Wscript.Echo "Source folder does not exist" & vbNewLine & strSrcFolder
End If

'****************************
' subroutine
' to copy stuff if all is well
'****************************
Sub CopyStuff()
Set objFolder = objFSO.GetFolder(strSrcFolder)
Set colFiles = objFolder.Files
iCopied = 0
iFailed = 0
iSkipped = 0
iTotal = 0
iLandscape = 0
iPortrait = 0
strDebug = ""

CreateFolders
For Each objFile in colFiles
On Error Resume Next
if objFile.size < 100000 then bTooSmall = true else bTooSmall = false
orientation = getOrientation (objFSO.GetAbsolutePathName(objFile))
if orientation = imgPortrait then iPortrait = iPortrait + 1 
if orientation = imgLandscape then iLandscape = iLandscape + 1 
if bTooSmall = false then
if copyFileSorted (objFile, strImgFolder, orientation) <> true then iFailed = iFailed + 1 else iCopied = iCopied + 1
else
iSkipped = iSkipped + 1 
End If
iTotal = iTotal + 1
Next
MsgBox "Copied " & iCopied & " file(s)." & vbNewLine & vbNewLine & "Skipped: " & iSkipped & vbNewLine & "Failed: " & iFailed & _
vbNewLine & vbNewLine & "Portrait: " & iPortrait & ", Landscape: " & iLandscape & _ 
vbNewLine & vbNewLine & "Processed " & iTotal & " file(s) in total.", , "Processing finished"
End Sub

sub CreateFolders()
If not objFSO.FolderExists(strImgFolder & "\Landscape") Then
objFSO.CreateFolder strImgFolder & "\Landscape"
end if
If not objFSO.FolderExists(strImgFolder & "\Portrait") Then
objFSO.CreateFolder strImgFolder & "\Portrait"
end if
end sub

function getOrientation(filepath) 
on error resume next
Err.Clear
Set objImage = CreateObject("WIA.ImageFile")
objImage.LoadFile filepath
If objImage.Width > objImage.Height Then
getOrientation = imgLandscape
elseif objImage.Width < objImage.Height then 
getOrientation = imgPortrait
else
getOrientation = imgSquare
End If
If Err.Number <> 0 Then getOrientation = imgError
Err.Clear
end function

function copyFileSorted(objFile, destFolder, orientation) 
dest2 = destFolder
if orientation = imgLandscape then dest2 = dest2 & "\Landscape"
if orientation = imgPortrait then dest2 = dest2 & "\Portrait"
Err.Clear
objFSO.copyFile objFSO.GetAbsolutePathName(objFile), dest2 & "\" & objFile.Name & ".jpg", false
If Err.Number <> 0 Then
copyFileSorted = false
Err.Clear
else
copyFileSorted = true
End If
strDebug = strDebug & dest2 & " " & orientation & ", "
end function


Sunday, May 8, 2016

Figuring out a proprietary quadrature encoder from an old Logitech mouse

If you wanted a quadrature encoder and can't make sense of the three-pin IR receiver component you found when you ripped up an old Logitech mouse, here's some help.

The receiver chip is likely a custom Logitech chip which uses a single wire interface to communicate with a custom controller. In my case the controller IC is CP5928AM, for either of which a datasheet is nowhere to be found. They be proprietary.

Fortunately, I found a reference to a relevant Logitech patent (US6552716) on Hackaday forums, and while it only explains general operation and doesn't offer exact timings, (and I don't own an oscilloscope), a little trial and error has produced working code.

Based on the patent, I found that data can be read as follows:

  1. You'll need two free I/O pins on your MCU. Connect one each to the IRLED (I'll call it LED) and the data pin of the receiver (I'll call it data pin). Use existing wiring to figure out the pins. Start with LED off, and data pin set to INPUT on the MCU.
  2. Pulse the LED on and then off. This produces a light pulse which changes the state of a couple of SR latches in the receiver to produce an output which you will now read off a parallel-to-serial shifter. 
  3. Change the data pin to OUTPUT and drive the data pin high and then low. Then change it back to INPUT. Now read the data pin. You have your first directional bit.
  4. Again, change the data pin to OUTPUT and drive the data pin high and then low. Then change it back to INPUT. Now read the data pin. You have your second directional bit. 
  5. Each bit indicates rotation in one direction. If both are 0, there is no movement. If both are 1, then you are not reading data fast enough. 
  6. repeat steps 2 to 5. 

Here's the code I wrote to test it with my Arduino:
/*************************************/
/*  Logitech Quadrature Encoder      */
/*  Single wire interface            */
/*  Code based on patent US6552716.  */
/*************************************/

// data, IRLED pins
#define QEDATAPIN 7
#define QELEDPIN 6

int qeOld1, qeOld2, turns=0, overflows=0;

void setup() {
  Serial.begin(115200);
  pinMode(QEDATAPIN, INPUT);
  //turn irled off -- high is OFF
  pinMode(QELEDPIN, OUTPUT);
  digitalWrite(QELEDPIN, HIGH);
  qeOld1=qeOld2=0;
}

//in microseconds, increase if they don't work
#define QE_SENSOR_PULSEWIDTH 2
#define QE_IRLED_PULSEWIDTH 2

inline void qeLEDlightPulse(){
  digitalWrite(QELEDPIN, LOW);
  delayMicroseconds(QE_IRLED_PULSEWIDTH);
  digitalWrite(QELEDPIN, HIGH);
}

inline byte getQEDataBit(){
  //switch to output mode and
  //pulse data line high and then low
  delayMicroseconds(QE_SENSOR_PULSEWIDTH/2);
  pinMode(QEDATAPIN, OUTPUT);
  digitalWrite(QEDATAPIN, HIGH);
  delayMicroseconds(QE_SENSOR_PULSEWIDTH);
  digitalWrite(QEDATAPIN, LOW);
  delayMicroseconds(QE_SENSOR_PULSEWIDTH);
  //switch back to input mode and
  //read data bit
  pinMode(QEDATAPIN, INPUT);
  delayMicroseconds(QE_SENSOR_PULSEWIDTH);
  return digitalRead(QEDATAPIN);
}

void loop() {
  qeLEDlightPulse();
  byte qe1 = getQEDataBit();
  byte qe2 = getQEDataBit();
  
  if(qeOld1!=qe1 || qeOld2!=qe2){
    if(qe1!=0)
      turns++;
    if(qe2!=0)
      turns--;
    if(qe1==qe2 && qe1==1)
      overflows++;
    Serial.print(turns);
    Serial.print("\t-- ");
    Serial.print(overflows);
    Serial.print("\t-- ");
    Serial.print(qe1);
    Serial.println(qe2);
  }
  qeOld1=qe1;
  qeOld2=qe2;
}

India =/= Hindi

 Blogger.com asks:

///"Why not blog in Hindi? With over 500 million speakers around the world, and a rapidly growing online audience, Hindi content could be the next opportunity to get you new readers. You can now use your existing AdSense account to monetize your Hindi content or just create a new one. Get started now."///

No thanks. Because, f*ck you, don't assume that I speak Hindi just because I live somewhere in India.