Friday 28 December 2012

Making a cheap concertina blind using greenhouse shading


Vertical blind, half open
Roof blinds - 1 closed, 1 open

For Christmas, I wanted to give my wife a set of greenhouse shading blinds for her new greenhouse.

However, when I priced them up, the cost was going to be over £500. I decided to make some instead.

Turns out they are not difficult to do, and, as I could find no instructions on the Web, I decided to write up how to do it here on this blog.

The blinds shown here can be used vertically (as in the picture above), or horizontally (on the roof). In either case the moving rail (white in the picture) always stays parallel to the top and bottom rails, due to the guide lines threaded through it.

The blinds can easily be opened and closed by pulling or pushing either end of the moving rail, and will stay in any position.

Materials

  • Greenhouse shade netting
  • Strimmer line
  • Plastic electrical conduit, oval section
  • Wood strips (about 1" x 1" - 25cm x 25cm)
  • Rust proof screws (brass or stainless steel)

Method

First measure the area to be covered. I suggest making the blinds no wider than about 4 ft (120 cm), as the electrical conduit which forms the handle to open and close the blind bends if you use longer lengths.
Cut the shade netting to the width to be covered, and about one and a quarter times the length.

Pleating the blind


Using a ruler to guide the pleating

Using an iron on the appropriate setting for your material (nylon for shade netting - not too hot, or it will melt), fold over about an inch of one end of the blind, and iron the fold in.

I used a long metal ruler about an inch wide to help me get the folds straight and the correct width.

Fold and iron again, and repeat until you have pleated the full length of the blind.

I found the weight of the material hanging off the ironing board tended to pull the pleats out, so I used some weights (a couple of square bottles of water in the picture) to stop this happening.

Obviously you will have to unfold earlier pleats as you go, otherwise the material gets too thick and difficult to handle.

The pleats do not have to be totally accurate, but it is important to make sure they are reasonable square to the edges of the material (and that the material is cut squarely).

When you have finished, gather the pleats up carefully (I found starting from the middle was easiest), and tie with some pieces of string to hold them in place. Make sure the edges are all squared up.

Make the mechanism

Pleated blind with top rail threaded


Cut 2 pieces of the wood the same width as the blind, and 1 piece of the electrical conduit.

Drill holes for the strimmer cord - one close to each end, and one in the middle. The holes in the 2 pieces of wood and the conduit should all line up. Also drill diagonal holes in the ends of each piece of wood as shown.

Thread the blind

Threading diagram

The 3 main strimmer lines should be a little longer than the drop of the blind (enough to allow plenty for knots, and a bit extra for adjustment). The 2 guide lines (the ones that go through the conduit) should be longer by the length of the conduit - best to allow a bit more for adjustment, as these lines should not be as tight as the main lines.

First thread the two guide lines though the length of the conduit. Then thread the 3 main strimmer lines through the top wooden rail, the blind, the conduit and the bottom rail as shown. Tie figure eight knots in the ends.

Thread the ends of the guide lines though the diagonal holes - notice that one line goes from top left, through the conduit, and down to bottom right, and the other vice versa. Again, tie them off with figure eight knots.
Now fasten the blind to the top rail (I used a staple gun), and to the conduit (I used gaffer/duck/duct tape).
Detail showing guide line (blue) threaded in top rail and conduit

Fit

Drill holes in the top and bottom rails for the fitting screws - the placing depends on what you are screwing them to. Fasten the top rail in place, and hold the bottom rail where it is to go. Adjust the length of the main lines so they will be quite tight (a little stretched) when the bottom rail is in place. Temporarily fasten the bottom rail, ready to adjust the guide lines. For horizontal blinds the guide lines can be quite loose, but for vertical blinds, they must be tight enough so the blind does not close under its own weight. Once you have measured and tested, undo the bottom rail and tie the knots to make the lines the right length. Professionally made blinds do not use knots, they have a small piece of metal tube with a grub screw, so you can pull the right length of line through the tube, and tighten the screw.



Wednesday 3 October 2012

Using reCAPTCHA from C++

I wanted to add reCAPTCHA to some old C++ CGI code, but I couldn't find a reCAPTCHA library for C++. Neither could a few other people I saw looking. So I wrote the following code, which I am happy to share:
#include <string>
#include <iostream>
#include <sstream>
#include <fstream>
#include <iomanip>
#include <curl/curl.h>

static ostringstream curl_result;

/* curl write callback, to fill tidy's input buffer...  */
uint write_cb(char *in, uint size, uint nmemb, void *out)
{
  int r = size * nmemb;
  in[r] = '\0';
  curl_result << in;
  return(r);
}

static bool recaptcha_check_answer(const char *private_key, const char *remoteip, 
 const char *challenge, const char *response, std::string &error) {
 if (!remoteip || !*remoteip) {
  error = "For security reasons, you must pass the remote ip to reCAPTCHA";
  return false;
 }
 //discard spam submissions
 if (!challenge || !*challenge || !response || !*response) {
  error = "Captcha challenge or response missing";
  return false;
 }

 CURL *curl;
 CURLcode res;
 struct curl_httppost *formpost=NULL;
 struct curl_httppost *lastptr=NULL;

 if(curl_global_init(CURL_GLOBAL_SSL)) {
  error = "Error initialising curl library";
  return false;
 }
 curl = curl_easy_init();
 if(!curl) {
  error = "Error initialising curl library";
  return false;
 }
 curl_formadd(&formpost,
  &lastptr,
  CURLFORM_COPYNAME, "privatekey",
  CURLFORM_COPYCONTENTS, private_key,
  CURLFORM_END);
 curl_formadd(&formpost,
  &lastptr,
  CURLFORM_COPYNAME, "remoteip",
  CURLFORM_COPYCONTENTS, remoteip,
  CURLFORM_END);
 curl_formadd(&formpost,
  &lastptr,
  CURLFORM_COPYNAME, "challenge",
  CURLFORM_COPYCONTENTS, challenge,
  CURLFORM_END);
 curl_formadd(&formpost,
  &lastptr,
  CURLFORM_COPYNAME, "response",
  CURLFORM_COPYCONTENTS, response,
  CURLFORM_END);

 const char *surl = "https://api-verify.recaptcha.net/verify";
 curl_easy_setopt(curl, CURLOPT_URL, surl);
 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1L);
 curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
 #ifdef SKIP_HOSTNAME_VERFICATION
 /*
  * If the site you're connecting to uses a different host name that what
  * they have mentioned in their server certificate's commonName (or
  * subjectAltName) fields, libcurl will refuse to connect. You can skip
  * this check, but this will make the connection less secure.
  */
 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
 #endif
 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);

 res = curl_easy_perform(curl);

 /* always cleanup */
 curl_easy_cleanup(curl);
 if(res && res != CURLE_RECV_ERROR)
  {
  std::ostringstream err;
  err << "Error " << res << " contacting captcha server";
  error = err.str();
  return false;
  }

 std::string resp = curl_result.str();
 std::string::size_type pos = resp.find("\n");
 std::string part1 = pos == std::string::npos ? resp : resp.substr(0, pos);
 if(part1 == "true")
  return true;
 error = pos == std::string::npos ? "Unknown captcha error" : "Captcha error:" + resp.substr(pos);
 return false;
}
This code relies on the curl library to handle the communication with the reCAPTCHA server.

Thursday 6 September 2012

Using the word breaker from Microsoft SQL Server 2005 in a stand-alone C# program


I have been writing a program which will query a full text search index on Microsoft SQL Server 2005. The data being indexed are large blobs of text (multiple lines).
I need to extract only those lines of text which contain the search words for display with the results.
Full Text Search uses a word breaker (IWordBreaker COM interface) and stemmer (IStemmer COM interface) to break the indexed text into words, and to enable matching of alternate word forms (e.g. "welcome" will also match "welcomes", "welcomed" and "welcoming").
I therefore need to use the Sql Server word breaker and stemmer to determine if each line of text from the blob matches the search string, as I believe the standard word breaker and stemmer in Windows search uses a different algorithm.
I thought if I copied the two dlls (infosoft and langwrbk) from a SQL Server installation to the machine on which I am running my program, and ran regsvr32 on them, they would be installed in the registry, and I would be able to use them. Unfortunately, this does not work, as regsvr32 does not add any information to the registry.
I therefore needed to load the COM components in the dlls "by hand". I started with John Jeffery's code to load COM components by hand. Once I had got over some 32-bit/64-bit problems (your DLL has to match the bit size of your calling code), it worked.
Here is the LinqPad script I used to test the two interfaces, in case someone else needs to do the same thing:

const string SqlServerDllFolder = @"C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Binn";
static LibraryModule langwrbk = LibraryModule.LoadModule(Path.Combine(SqlServerDllFolder, "LangWrbk.dll"));
static LibraryModule infosoft = LibraryModule.LoadModule(Path.Combine(SqlServerDllFolder, "infosoft.dll"));
static Guid stemmer = new Guid("D99F7670-7F1A-11CE-BE57-00AA0051FE20");
static Guid breaker = new Guid("173C97E2-AEBE-437C-9445-01B237ABF2F6");

void Main()
{
 WordBreaker breaker = new WordBreaker();
 string search;
 while((search = Util.ReadLine("Query string:").Trim()) != "") {
  Console.WriteLine("Original text:" + search);
  foreach (string word in breaker.Search(search)) {
   Console.WriteLine(word);
  }
 }
}

// COM Interface to Microsoft word breaker and stemmer
[Flags]
public enum WORDREP_BREAK_TYPE {
 WORDREP_BREAK_EOW = 0,
 WORDREP_BREAK_EOS = 1,
 WORDREP_BREAK_EOP = 2,
 WORDREP_BREAK_EOC = 3
}

[ComImport]
[Guid("CC907054-C058-101A-B554-08002B33B0E6")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IWordSink {
 void PutWord([MarshalAs(UnmanagedType.U4)] int cwc,
  [MarshalAs(UnmanagedType.LPWStr)] string pwcInBuf,
  [MarshalAs(UnmanagedType.U4)] int cwcSrcLen,
  [MarshalAs(UnmanagedType.U4)] int cwcSrcPos);
 void PutAltWord([MarshalAs(UnmanagedType.U4)] int cwc,
  [MarshalAs(UnmanagedType.LPWStr)] string pwcInBuf,
  [MarshalAs(UnmanagedType.U4)] int cwcSrcLen,
  [MarshalAs(UnmanagedType.U4)] int cwcSrcPos);
 void StartAltPhrase();
 void EndAltPhrase();
 void PutBreak(WORDREP_BREAK_TYPE breakType);
}

[ComImport]
[Guid("CC906FF0-C058-101A-B554-08002B33B0E6")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IPhraseSink {
 void PutSmallPhrase([MarshalAs(UnmanagedType.LPWStr)] string pwcNoun,
  [MarshalAs(UnmanagedType.U4)] int cwcNoun,
  [MarshalAs(UnmanagedType.LPWStr)] string pwcModifier,
  [MarshalAs(UnmanagedType.U4)] int cwcModifier,
  [MarshalAs(UnmanagedType.U4)] int ulAttachmentType);
 void PutPhrase([MarshalAs(UnmanagedType.LPWStr)] string pwcPhrase,
  [MarshalAs(UnmanagedType.U4)] int cwcPhrase);
}
[ComImport]
[Guid("fe77c330-7f42-11ce-be57-00aa0051fe20")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IWordFormSink {
 void PutAltWord([MarshalAs(UnmanagedType.LPWStr)] string pwcInBuf, [MarshalAs(UnmanagedType.U4)] int cwc);
 void PutWord([MarshalAs(UnmanagedType.LPWStr)] string pwcInBuf, [MarshalAs(UnmanagedType.U4)] int cwc);
}

[StructLayout(LayoutKind.Sequential)]
public struct TEXT_SOURCE {
 [MarshalAs(UnmanagedType.FunctionPtr)]
 public delFillTextBuffer pfnFillTextBuffer;
 [MarshalAs(UnmanagedType.LPWStr)]
 public string awcBuffer;
 [MarshalAs(UnmanagedType.U4)]
 public int iEnd;
 [MarshalAs(UnmanagedType.U4)]
 public int iCur;
}

// used to fill the buffer for TEXT_SOURCE
public delegate uint delFillTextBuffer([MarshalAs(UnmanagedType.Struct)]
 ref TEXT_SOURCE pTextSource);

[ComImport]
[Guid("D53552C8-77E3-101A-B552-08002B33B0E6")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IWordBreaker {
 void Init([MarshalAs(UnmanagedType.Bool)] bool fQuery,
  [MarshalAs(UnmanagedType.U4)] int maxTokenSize,
  [MarshalAs(UnmanagedType.Bool)] out bool pfLicense);
 void BreakText([MarshalAs(UnmanagedType.Struct)] ref TEXT_SOURCE pTextSource,
  [MarshalAs(UnmanagedType.Interface)] IWordSink pWordSink,
  [MarshalAs(UnmanagedType.Interface)] IPhraseSink pPhraseSink);
 void GetLicenseToUse([MarshalAs(UnmanagedType.LPWStr)] out string ppwcsLicense);
}

[ComImport]
[Guid("EFBAF140-7F42-11CE-BE57-00AA0051FE20")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IStemmer {
 void Init([MarshalAs(UnmanagedType.U4)] int ulMaxTokenSize, [MarshalAs(UnmanagedType.Bool)] out bool pfLicense);
 void GenerateWordForms([MarshalAs(UnmanagedType.LPWStr)] string pwcInBuf, 
  [MarshalAs(UnmanagedType.U4)] int cwc, 
  [MarshalAs(UnmanagedType.Interface)] IWordFormSink pStemSink);
 void GetLicenseToUse([MarshalAs(UnmanagedType.LPWStr)] out string ppwcsLicense);
}

// Word breaker to break a line of text into words, stem them, 
// and return a HashSet of all the words and stems
public class WordBreaker : IWordSink, IWordFormSink {
 IWordBreaker wordBreaker;
 HashSet words; // Words to search for
 IStemmer istm;
 
 public WordBreaker() {
  wordBreaker = (IWordBreaker)ComHelper.CreateInstance(langwrbk, breaker);
  bool pfLicense = true;
  wordBreaker.Init(true, 1000, out pfLicense);
  istm = (IStemmer)ComHelper.CreateInstance(infosoft, stemmer);
  pfLicense = true;
  istm.Init(1000, out pfLicense);
 }
 
 public HashSet Search(string text) {
  words = new HashSet();
  TEXT_SOURCE pTextSource = new TEXT_SOURCE();
  pTextSource.pfnFillTextBuffer = new delFillTextBuffer(pfnFillTextBuffer);
  pTextSource.awcBuffer = text.ToLower();
  pTextSource.iCur = 0;
  pTextSource.iEnd = text.Length;
  wordBreaker.BreakText(ref pTextSource, (IWordSink)this, null);
  return words;
 }
 
 #region IWordFormSink Members
 public void PutAltWord(string pwcInBuf, int cwc) {
  words.Add(pwcInBuf.Substring(0, cwc));
 }

 public void PutWord(string pwcInBuf, int cwc) {
  words.Add(pwcInBuf.Substring(0, cwc));
 }
 #endregion

 #region IWordSink Members
 public void PutWord(int cwc, string pwcInBuf, int cwcSrcLen, int cwcSrcPos) {
  istm.GenerateWordForms(pwcInBuf.Substring(0, cwc), cwc, this);
 }
 
 public void PutAltWord(int cwc, string pwcInBuf, int cwcSrcLen, int cwcSrcPos) {
 }

 public void StartAltPhrase() {
 }
 
 public void EndAltPhrase() {
 }

 public void PutBreak(WORDREP_BREAK_TYPE breakType) {
 }
 #endregion
}

static uint pfnFillTextBuffer(ref TEXT_SOURCE pTextSource) {
 // return WBREAK_E_END_OF_TEXT
 return 0x80041780;
}

// Code from https://gist.github.com/1568627
// By John Jeffery
static class ComHelper
{
 private delegate int DllGetClassObject(ref Guid clsid, ref Guid iid, [Out, MarshalAs(UnmanagedType.Interface)] out IClassFactory classFactory);

 public static object CreateInstance(LibraryModule libraryModule, Guid clsid)
 {
  var classFactory = GetClassFactory(libraryModule, clsid);
  var iid = new Guid("00000000-0000-0000-C000-000000000046"); // IUnknown
  object obj;
  classFactory.CreateInstance(null, ref iid, out obj);
  return obj;
 }

 static IClassFactory GetClassFactory(LibraryModule libraryModule, Guid clsid)
 {
  IntPtr ptr = libraryModule.GetProcAddress("DllGetClassObject");
  var callback = (DllGetClassObject) Marshal.GetDelegateForFunctionPointer(ptr, typeof (DllGetClassObject));

  var classFactoryIid = new Guid("00000001-0000-0000-c000-000000000046");
  IClassFactory classFactory;
  var hresult = callback(ref clsid, ref classFactoryIid, out classFactory);

  if (hresult != 0)
  {
   throw new Win32Exception(hresult, "Cannot create class factory");
  }
  return classFactory;
 }
}


[Guid("00000001-0000-0000-c000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
interface IClassFactory
{
 void CreateInstance([MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter, ref Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppvObject);
 void LockServer(bool fLock);
}

class LibraryModule : IDisposable
{
 private readonly IntPtr _handle;
 private readonly string _filePath;

 private static class Win32
 {
  [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
  public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

  [DllImport("kernel32.dll")]
  public static extern bool FreeLibrary(IntPtr hModule);

  [DllImport("kernel32.dll", SetLastError = true)]
  public static extern IntPtr LoadLibrary(string lpFileName);
 }


 public static LibraryModule LoadModule(string filePath)
 {
  var libraryModule = new LibraryModule(Win32.LoadLibrary(filePath), filePath);
  if (libraryModule._handle == IntPtr.Zero)
  {
   int error = Marshal.GetLastWin32Error();
   throw new Win32Exception(error, "Cannot load library: " + filePath);
  }

  return libraryModule;
 }

 private LibraryModule(IntPtr handle, string filePath)
 {
  _filePath = filePath;
  _handle = handle;
 }

 ~LibraryModule()
 {
  if (_handle != IntPtr.Zero)
  {
   Win32.FreeLibrary(_handle);
  }
 }

 public void Dispose()
 {
  if (_handle != IntPtr.Zero)
  {
   Win32.FreeLibrary(_handle);
  }
  GC.SuppressFinalize(this);
 }

 public IntPtr GetProcAddress(string name)
 {
  IntPtr ptr = Win32.GetProcAddress(_handle, "DllGetClassObject");
  if (ptr == IntPtr.Zero)
  {
   int error = Marshal.GetLastWin32Error();
   string message = string.Format("Cannot find proc {0} in {1}", name, _filePath);
   throw new Win32Exception(error, message);
  }
  return ptr;
 }

 public string FilePath
 {
  get { return _filePath; }
 }
}

Monday 23 April 2012

Naerok 12" bandsaw manual

I recently inherited a workshop containing a Naerok 12" bandsaw, and went looking on the Internet for a manual. Although I found manuals for similar saws, I couldn't find one for this model.

However, when tidying the workshop, I found the real manual! In order to help anyone else with the same model of bandsaw, I have uploaded a scan of the manual to http://www.trumphurst.com/naerok.zip.