All weblog entries

2024-05-31 Microsoft Excel shortcut key

When I'm in Microsoft Excel on macOS, I often want to clean up and delete rows. The online documentation is weird, and mentions a non-working shortcut key. To delete a row with the keyboard, navigate to the row, then press Shift-Space, then Control-Minus. This works on macOS Sonoma 14.5 with Microsoft Excel 16.81.

2023-12-01 Unable to use public key authentication for ssh login on Raspberry Pi

TL;DR: Raspberry Pi OS will reject rsa keys, use another type of key, and copy it to the Pi:

  % ssh-keygen -t ecdsa
  % ssh-copy-id .ssh/id_ecdsa.pub <pi-hostname>

Explanation is as follows. Recently I installed a Raspberry Pi 4, and I flashed the SD card with the Raspberry Pi Imager. I used the default OS (which is titled "Raspberry Pi OS, a port of Debian Bookworm"). I wanted to use passwordless login to ssh, i.e. public key authentication. So I copied my existing public key to the Raspberry Pi with ssh-copy-id. However when accessing the Pi over ssh, I still had to enter my password.

This had me stumped for a while. In the end, I turned on debug logging:

  % sudo vim /etc/ssh/sshd_config

Then add the following line:

  LogLevel DEBUG3

Restart SSH daemon and follow logs:

  % sudo systemctl restart sshd
  % journalctl -f

Try and log in with your old RSA key, and you'll see the following log message:

  Dec 01 09:27:53 HL46528028 sshd[2025]: debug3: mm_answer_keyallowed: publickey authentication test: RSA key is not allowed

What you need to do, is generate a new key with a different type:

  % ssh-keygen -t ecdsa

The default is to save they keypair in the ~/.ssh directory and call it id_ecdsa and id_ecdsa.pub. Copy the public key to the Raspberry:

  % ssh-copy-id .ssh/id_ecdsa.pub <pi-hostname>

Now enjoy passwordless login:

  % ssh <pi-hostname>

Of course don't forget to remove the LogLevel line from the sshd configuration, and restart the daemon.

2023-09-18 SwiftUI Separate toolbar

In SwiftUI, it's easy to just keep coding and dump everything in one view. Toolbars especially are "bulky", they take up a lot of lines, and are not regular views so syntax-wise, they're a bit of a bother to separate away. Here's an example of a bottom toolbar, to help you split up those big views.

    struct MainView: View {
        var body: some View {
            Color.blue
                .ignoresSafeArea(edges: [.top, .leading, .trailing])
                .toolbar {
                    ActionToolbar()
                }
        }
    }
    struct ActionToolbar: ToolbarContent {
        var body: some ToolbarContent {
            ToolbarItem(placement: .bottomBar) {
                 Spacer()
             }
            ToolbarItem(placement: .bottomBar) {
                Button(action: { print("Plus") }) {
                    Image(systemName: "plus.app.fill")
                        .resizable()
                        .scaledToFit()
                }
            }
        }
    }

2023-08-30 Coordinator in preview

When you're using the Coordinator pattern in a SwiftUI project, you'll find yourself sometimes wanting to preview the initial start of a "flow" of screens. But that's quite a bit of work because inside the coordinator, some boilerplate code needs to be present, to wrap the SwiftUI view in a UIHostingController.

This bit of code is useful to plunk in the utilities folder of your project, and use in the preview section of your SwiftUI view.

    private class PreviewViewController: UIViewController {
        private let coordinator: NavigationControllerCoordinator
        init(coordinator: NavigationControllerCoordinator) {
            self.coordinator = coordinator
            super.init(nibName: nil, bundle: nil)
        }
        required init?(coder: NSCoder) {
            fatalError()
        }
        override func viewDidLoad() {
            guard let navigationController = self.navigationController as? NavigationController else {
                return
            }
            title = "Preview"
            var configuration = UIButton.Configuration.filled()
            configuration.title = "Start"
            self.view = UIButton(configuration: configuration, primaryAction: UIAction(handler: {_ in
                navigationController.present(self.coordinator.navigationController, animated: true)
                self.coordinator.start()
            }))
        }
    }
    struct PreviewCoordinator: UIViewControllerRepresentable {
        let coordinator: NavigationControllerCoordinator
        typealias UIViewControllerType = NavigationController
        func makeUIViewController(context: Context) -> NavigationController {
            let viewController = PreviewViewController(coordinator: coordinator)
            return NavigationController(rootViewController: viewController)
        }
        func updateUIViewController(_ uiViewController: NavigationController, context: Context) {}
    }

Use as follows;

    struct NetworkScanQRView_Previews: PreviewProvider {
        static var previews: some View {
            PreviewCoordinator(coordinator: NetworkScanQRCoordinator.mocked)
        }
    }

2023-06-14 SwiftUI pet peeves

This is my list of SwiftUI pet peeves. It's a work in progress.

  • Using .onTapGesture when you should actually use a button. Although it's nicely concise, a button will show visual feedback, and can easily be adjusted for accessibility.
  • Using a class when a struct suffices.
  • When naming a function that instantiates something, it should be prefixed with "make", not with "get" or something else. See also: https://www.swift.org/documentation/api-design-guidelines/
  • Hardcoded button sizes, when they actually should be a ScaledMetric.

2023-04-12 Natural breaks in Time Out application for macOS

I'm using a break reminder on macOS, it's called Time Out. https://dejal.com/timeout/

It's a very full-featured app, one of them is that it can skip breaks if it detects a natural break. Meaning, if you go and get a cup of coffee, it counts as a break.

The problem was, that this wasn't working. I found out how to fix this, and I'm documenting it here. Firstly, to see if the app is unable to detect the computer idle, open Time Out, and in the left navigation, select "Advanced". Then turn on "Output scheduler logging" and click the "Open Console" button. This will open Console, the macOS log viewer.

In Console, click the blue Start Streaming button, then in the top right, type: "process: Time Out" and press enter. You should now only see output from Time Out. If you don't move the keyboard or mouse, Time Out should detect that you leave the computer idle. In my case that wasn't happening, it just repeatedly logged the following lines, edited for brevity:

    idle (for 0 secs); Normal due 12/04/2023, 11:19 (in 11 min, 46 secs); .......
    idle (for 0 secs); Normal due 12/04/2023, 11:19 (in 11 min, 45 secs); .......
    idle (for 0 secs); Normal due 12/04/2023, 11:19 (in 11 min, 44 secs); .......

When I went back to Time Out, and in the Advanced screen, changed "Natural break detection method" from "Event Monitor" to "Event Source", then back to "Event Monitor", this fixed the problem! It should now detect idling correctly:

    idle (for 0 secs); Normal due ... not yet idle for at least 30 seconds
    idle (for 1 secs); Normal due ... not yet idle for at least 30 seconds
    idle (for 2 secs); Normal due ... not yet idle for at least 30 seconds

Note that perhaps on the same screen (Advanced), you'll have to adjust the "Natural break active threshold" as well, mine is set to 30 seconds. After the problem is fixed, be sure to disable "Output scheduler logging" as well.

2023-03-28 Contacts app macOS sorting

The Contacts app (the app previously known as Address Book) will sort by default on last name. If you prefer sorting on first name, and you're a command-line oriented person like me, you want to set your sorting preference from a script. Here's how:

    defaults write com.apple.AddressBook ABNameSortingFormat -string "sortingFirstName sortingLastName"

2023-02-22 Unselectable picker in SwiftUI

Today I spent too much time looking at a bit of code that wasn't working. Here's the problem: the following picker uses a struct to both display values, as well as save the selection. Only problem is; it doesn't work. It displays correctly, but you can't select it; essentially it's read-only.

    struct UnselectablePicker: View {
        struct Language: Identifiable, Hashable  {
            var title: String
            var id: String
        }
        
        var languages: [Language] = [
            Language(title: "English", id: "en-US"),
            Language(title: "German", id: "de-DE"),
            Language(title: "Korean", id: "ko-KR"),
            Language(title: "Dutch", id: "nl-NL")
        ]
        @State private var selectedLanguage = UnselectablePicker.Language(title: "German", id: "de-DE")
        
        var body: some View {
            Picker(selection: $selectedLanguage, label: Text("Front Description")) {
                ForEach(self.languages) {
                    Text($0.title)
                }
            }.pickerStyle(.segmented)
        }
    }

Did you spot it? The problem is in the ForEach. The picker works with the following correction:

    ForEach(self.languages, id: \.self) {
        // ...
    }

Alternatively, add a tag.

    ForEach(self.languages) {
        Text($0.title)
            .tag($0.title)
    }

2023-01-24 Moving server

Today, I'm moving this website to a new server with a new Debian version. This may mean some interruptions.

Update: the move was a success. The new provider is AlphaVPS and I hope I'll be set for a couple of years.

https://alphavps.com/

2022-12-12 When to use StateObject wrappedValue initializer

UPDATE 2023-04-28: Apple updated its documentation: https://developer.apple.com/documentation/swiftui/stateobject#Initialize-state-objects-using-external-data

On our team, we recently discussed the use of StateObject in SwiftUI. It's very useful for ViewModels. However you often want to initialize such a ViewModel with one or more parameters and you bump into the following bit of documentation:

"You don't call this initializer directly. Instead, declare a property with the @StateObject attribute in a View, App, or Scene, and provide an initial value"

Source: developer.apple.com

We were not the only ones struggling with this; see also this blog post by Sarun W, where they share a Slack screenshot quoting an Apple engineer saying that this initializer is supported.

Still, using that initializer means that if your view gets recreated, you lose all state since the ViewModel is destroyed and created as well. This is especially harrowing when you do network actions within the ViewModel.

Our conclusion is that StateObject(wrappedValue:) can be used at points in your app where you start a completely new flow. For instance in a popup where you create a screen with a NavigationView or NavigationStack. Otherwise, initialize elsewhere and use @EnvironmentObject.

2022-11-18 Example of hierarchical list

I actually didn't know that the SwiftUI List element natively supports hierarchical lists, until someone on Reddit asked for an example. Here's mine:

    struct Park: Identifiable {
        let id = UUID()
        let name: String
        let children: [Park]?
    }
    struct ContentView: View {
        private let parks = [
            Park(name: "Sierra Nevada", children: [
                Park(name: "Yosemite", children: [
                    Park(name: "Mount Dana", children: nil),
                    Park(name: "Mount Lyell", children: nil),
                ]),
                Park(name: "Stanislaus National Forest", children: [
                    Park(name: "Emigrant Wilderniss", children: nil),
                    Park(name: "Carson-Iceberg Wilderniss", children: nil),
                ])
            ])
        ]
        var body: some View {
            List(parks, children: \.children, rowContent: { Text($0.name) })
        }
    }

2022-09-15 Xcode linker error ld framework not found

Today I got the following error when trying to build my unittest for my Swift package:

  ld: framework not found CoreAPIKit

Note that the name of that particular framework (CoreAPIKit) doesn't matter; it's a framework that's only used internally at my client. Now on to the solution... In Xcode, we selected the project, and selected the unittest target, then selected the Build Phases tab. In that tab, there's the Link Binary With Libraries setting.

That setting listed "CoreAPIKit.framework"! So, why does this go wrong?

We removed that framework, then added it again. This time, it was saying "CoreAPIKit" (i.e. without the extension). Now, the unittest target correctly linked.

2022-09-14 Getting an unescaped JSON string from Xcode console

If you're debugging an app, you may want to copy raw JSON in Xcode, then paste it into a specialized app. The problem is, Xcode doesn't make it obvious how to actually do this.

In my first attempt, I set a breakpoint at the appropriate line of code, and Xcode listed the variables in the Variables View. In my case, that variable was called "response" of type "optional Data". So I first tried typing the following at the debugger prompt:

  ldb> po String(decoding: response.data ?? Data(), as: UTF8.self)

This however will print the string but it will escape all quotes with a backslash. To be able to select and copy unescaped JSON from the console, type the following:

  ldb> e print(String(decoding: response.data ?? Data(), as: UTF8.self))

2022-05-23 Xcode Package.resolved file is corrupted or malformed

Today Xcode gave me the error "Package.resolved file is corrupted or malformed; fix or delete the file to continue: malformed" when opening and running an xcworkspace with a Swift package and a sample app.

That is exactly after a successful pull request, so the project has built successfully before. The problem was fixed by manually removing the file, doing a "clean build folder", then quit Xcode and the simulator. Then started Xcode again and opened the workspace: it built. Just a tip for when you encounter this behavior.

In our case, Xcode actually downgraded some dependant package in (minor) version. So this may not actually be the final solution for you.

Note that this is NOT the same as the error that Xcode 13.2.1 or lower shows when someone else opened it with 13.3 or higher. From that 13.3 version and upwards, Xcode will upgrade the Package.resolved file to a new v2 format. But we never opened the project in any Xcode version higher than 13.2.1. That error reads: "Package.resolved file is corrupted or malformed; fix or delete the file to continue: unsupported schema version 2".

I'm not yet happy with the way Xcode handles Swift Packages, it feels unreliable in the current version. But it'll probably improve in the coming versions.

2022-03-03 Xcode Dependencies could not be resolved

Today, Xcode gave me the following error:

    Dependencies could not be resolved because root depends on 'customerkit' 13.0.0..<14.0.0 and root depends on 'customerkit' 11.0.0..<12.0.0.

The project had built correctly, then I wanted to edit code in a package so I added it as a local package.

The error was confusing to me, because it specifies that root (which means the current project, I assume) depends on both version 11.x and 13.x which is obviously not the case. So what this error message actually means, is: you added a local package, and both the main project and your local package rely on the same package (customerkit in this case), but they require different versions.

The fact that the error twice mentions "root depends on xxx", is thus wrong. If you get it, select your project in the left navigation, select the project again in the middle pane, select the Package Dependencies and look up the version of the dependency. In my case, that was CustomerKit, version "11.0.1 - Next Major". Now for the other packages, check their own dependencies and make sure these are equal.

2021-12-16 Switch with multiple cases with associated values

Lots of people know about switch cases with associated values, for example as follows:

    enum Emoji {
        case lol(String)
        case ohnoes(String)
        case nothing
    }

Some code that uses the above enum:

    let testcases: [Emoji] = [
        .lol("🙂"),
        .ohnoes("😒"),
        .nothing
    ]
    for testcase in testcases {
        switch testcase {
        case .lol(let emoji):
            print("Happy emoji: " + emoji)
        case .ohnoes(let emoji):
            print("Sad emoji: " + emoji)
        case .nothing:
            print("No emoji")
        }
    }

If however, you have common code that must run for both happy and sad emoji, it's possible to combine them into one case. However inside that case, you can still test for one specific case. The example below shows how you can print common code for both happy and sad emoji, but still detect happy emoji:

    for testcase in testcases {
        switch testcase {
        case .lol(let emoji), .ohnoes(let emoji):
            if case .lol = testcase {
                print("Happy emoji detected: \(emoji)")
            }
            print("This is code that runs on any emoji")
        case .nothing:
            print("No emoji")
        }
    }

2021-12-02 SwiftUI NavigationLink Extraneous argument label isActive in call

Today, in Xcode 13.1, I got the following error:

    Extraneous argument label 'isActive:' in call

Accompanied by:

    Type '() -> EmptyView' cannot conform to 'StringProtocol'

This can be easily reproduced with the following code:

    struct ContentView: View {
        @State private var navigate = false
        var body: some View {
            NavigationLink(isActive: self.$navigate,
                           destination: Text("Hello, world!")) {
                EmptyView()
            }
        }
    }

Can you spot the problem above? I couldn't.

An alternative is when you try and specify the label parameter:

    struct ContentView: View {
        @State private var navigate = false
        var body: some View {
            NavigationLink(isActive: self.$navigate,
                           destination: Text("Hello, world!"),
                           label: {
                EmptyView()
            })
        }
    }

Then your errors become somewhat more clear:

    Generic parameter 'Destination' could not be inferred
    Cannot convert value of type 'Text' to expected argument type '() -> Destination'

Namely: the expected argument type to the destination parameter is a closure. As follows:

    destination: { Text("Hello, world!") }

2021-10-20 Generic parameter Destination could not be inferred

Xcode 13.0. If you're banging out SwiftUI code, and you got the following error message:

    Generic parameter 'Destination' could not be inferred

and it'll follow up with the following error as well (if you tried to navigate to a Text view):

    Cannot convert value of type 'Text' to expected argument type '() -> Destination'

then you probably tried to create a NavigationLink and let auto-complete add the following code:

    NavigationLink(tag: Hashable, selection: Binding<Hashable?>, destination: () -> _, label: () -> _)

The solution is to not use the auto-complete version of NavigationLink, but instead use a different sequence of parameters:

    NavigationLink(destination: () -> _, tag: Hashable, selection: Binding<Hashable?>, label: () -> _)

2021-10-19 Constrained extension must be declared on the unspecialized generic type

Did you get the following error message in Swift?

    Constrained extension must be declared on the unspecialized generic type 'Optional' with constraints specified by a 'where' clause

I got that one, and I tried to extend an optional string as follows:

    extension Optional<String> {
        func ifEmpty(_ replacementString: String) -> String? {
            (self ?? "").isEmpty ? replacementString : self
        }
    }

The correct syntax is:

    extension Optional where Wrapped == String {
        func ifEmpty(_ replacementString: String) -> String? {
            (self ?? "").isEmpty ? replacementString : self
        }
    }

It's still painful that it must return an optional. That can be avoided by a static function, which isn't as nice of course :)

2021-09-30 CGRect appendInterpolation

In the category of errors that you google but don't get results.

Suppose you have the following state variable in a SwiftUI view:

    @State private var textFieldFrame1 = CGRect()    

And you want to display that somewhere, as follows:

    Text("Frame of textfield 1 = \(textFieldFrame1)")

You'll get the following compiler error:

    No exact matches in call to instance method 'appendInterpolation'

The solution is as follows:

    Text("Frame of textfield 1 = " + String(describing: self.textFieldFrame1))

2021-03-22 Debugging view sizes

Here's a tip if you wish to show the sizes of views in your SwiftUI previews. Add the following struct to your project.

    struct ViewSizePreferenceReader: View {
        private struct ViewSizePreferenceKey: PreferenceKey {
            typealias Value = [CGSize]
            static var defaultValue: [CGSize] = []
            static func reduce(value: inout [CGSize], nextValue: () -> [CGSize]) {
                value.append(contentsOf: nextValue())
            }
        }
        @Binding var sizeString: String
        var body: some View {
            GeometryReader { (geometry: GeometryProxy) in
                Color.clear
                    .preference(key: ViewSizePreferenceKey.self, value: [geometry.size])
            }
            .onPreferenceChange(ViewSizePreferenceKey.self) { preferences in
                if let size = preferences.first {
                    self.sizeString = String(format: "%.1f x %.1f", size.width, size.height)
                }
            }
        }
    }

Then add a @State variable for the string, and use the above view as a background, as follows. The preview below will show how the Dynamic Type setting influences SF Symbol based images.

    struct ContentView: View {
        @State private var sizeString = "0x0"
        var body: some View {
            VStack {
                Image(systemName: "magnifyingglass")
                    .imageScale(.large)
                    .background(ViewSizePreferenceReader(sizeString: self.$sizeString))
                Text("Size of button: \(self.sizeString)")
            }
        }
    }
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            Group {
                ContentView()
                ContentView()
                    .environment(\.sizeCategory,
                                 .accessibilityLarge)
            }
            .previewLayout(.fixed(width: 414, height: 200))
        }
    }

2021-01-22 Error in SwiftUI Previews part deux

Today Xcode refused to show previews for one view, displaying "Cannot preview this file" on top of the previews canvas.

This time it's showing the error "SomeUnitTest.swift must belong to at least one target in the current scheme in order to use previews". I have the feeling that this error is incorrect and masks a problem elsewhere, because unittests most certainly should not belong to any other target than the test target.

I have been able to temporarily fix the problem as follows:

  • Quit Xcode and simulator
  • Delete the ~/Library/Developer/Xcode/DerivedData folder
  • Start Xcode and simulator
  • Remove all unittests from the project (don't move to trash, simply remove references)
  • Compile the project
  • Add the unittests back to the project

2020-07-16 Error in SwiftUI preview

Today, one SwiftUI Preview would not show anything. The Diagnostics button showed the following error:

    GenericHumanReadableError: unexpected error occurred
    noPreviewInfos(arch: "x86_64", sdkRoot: "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.6.sdk")

After a bit of fiddling, I found out the cause. This project had multiple targets. And the file was not a member of the currently selected target.

Edit 2021-01-15: the above solution was only temporary. Another attempt involved removing all unittests also temporarily "fixed" this problem. At the moment, this seems to be a very rare error and I have not been able to find a definitive cause or solution.

2020-05-27 Fixing a slow build in Xcode

If your Xcode project isn't compiling as fast as you expect, open the build settings of target that you're building. Add the following to the "Other Swift Flags" setting:

    -Xfrontend -warn-long-expression-type-checking=100

This makes the compiler emit warnings when it takes more than 100 ms to type-check an expression.

2020-03-05 Capping an array in Swift

Do you want to cap an array in Swift to a certain size? Here's how:

    var array = ["a", "b", "c", "d", "e"]
    array.replaceSubrange(3..., with: [])

The array now contains three items. It'll crash when instead of 3, you enter an index that's beyond the array count. If you wish to avoid that, do something like the following:

    array.replaceSubrange([0, array.count, 3].sorted()[1]..., with: [])

2019-12-08 Linux VPS with TeamViewer

In 2017, I blogged about setting up a Linux VPS at Vultr, then installing TeamViewer. This is an admittedly uncommon thing to do; most Linux servers are administrated over SSH. However for remoting into a desktop usage, I find TeamViewer extremely user friendly on the client side. The client itself is excellent, includes copy/paste, file transfer, chat, and a host of features. And it works on multiple platforms.

Today I tried once more to set up a VPS at Vultr.com, with a Fedora 31 desktop and TeamViewer. Unfortunately, I can no longer get it working; TeamViewer installs fine but the client hangs upon connecting. I hope to update once I get it working.

Updated instructions for CentOS 8 are as follows. Note this fails for unknown reasons, see below.

Log in as root. First install the desktop, enable GUI to start after reboot, and download and install TeamViewer:

 # dnf groupinstall workstation
 # systemctl set-default graphical.target
 # wget "https://download.teamviewer.com/download/teamviewer.i686.rpm"
 # yum -y install ./teamviewer.i686.rpm

Edit the file /etc/gdm/custom.conf and remove the hash sign # before the line "WaylandEnable=false" so GDM uses X.org.

Add swapspace:

 # fallocate -l 1G /swapfile1
 # chmod 600 /swapfile1
 # mkswap /swapfile1
 # swapon /swapfile1

Add the following line to /etc/fstab:

 /swapfile1   swap    swap    sw  0   0

Add a user for yourself:

 # useradd -m -U mynewusername
 # passwd mynewusername

Add this new user to the sudo'ers file and reboot:

 # visudo
 # reboot

When done, reboot and log in at the Vultr website, because there, you can use your browser to access the graphical console. From there you can log into Gnome. It's important that you do this correctly; first click on the username. Now the password prompt appears. Next to the Sign In button, a settings icon appears. Click this icon, and make sure one of the options with "X11 display server" is selected! Continue to log in and you'll see the desktop. From here, you can start and configure TeamViewer.

Or at least, you should be able to. TeamViewer can be started, however the GUI never actually is displayed. The client will eternally hang in the "Connecting to <hostname>..." screen.

2019-09-16 Objective-C project without storyboard in Xcode 11

Create a new project and open the ViewController.h. Optionally add the following lines to viewDidLoad:

    self.view.backgroundColor = [UIColor greenColor];
    self.title = @"My first viewcontroller";

Then open the SceneDelegate.m file and add the following lines to the scene:willConnectToSession:connectOptions function:

    ViewController *vc = [ViewController new];
    UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:vc];
    
    UIWindow *window = [UIWindow new];
    [window setWindowScene:(UIWindowScene *)scene];
    window.rootViewController = nc;
    self.window = window;
    [window makeKeyAndVisible];

Run the project. You should now be able to see a green viewcontroller.

You can delete the Main.storyboard file, then go to the project, click on the target, and in the General tab, clear the "Main Interface" textfield. Then open the Info.plist, click open Application Scene Manifest, Scene Configuration, Application Session Role, Default Configuration, then click the minus button next to Storyboard Name.

2019-08-01 border is deprecated Use a RoundedRectangle shape

Moving from Xcode 11 beta 4 to beta 5, you may get a warning when you have borders in your code:

    Text("In publishing and graphic design, lorem ipsum is a placeholder text.")
        .border(Color.green, width: 2, cornerRadius: 16)

The warning is:

    'border(_:width:cornerRadius:)' is deprecated: Use a RoundedRectangle shape.

Here's a copy-paste solution to the above warning:

    Text("In publishing and graphic design, lorem ipsum is a placeholder text.")
        .background(RoundedRectangle(cornerRadius: 16).strokeBorder(Color.green, lineWidth: 2))

2019-07-15 Generic parameter subject could not be inferred

In a condensed form, I had the following SwiftUI code:

    struct Input {
        var someString = "OH HAI"
    }
    struct ContentView : View {
        @State private var input = Input()
        
        var body: some View {
            TextField("Enter string", text: $input.someString)
                .textFieldStyle(.roundedBorder)
                .padding()
        }
    }

Then I changed someString to a computed property:

    struct Input {
        var someString: String {
            return "OH HAI"
        }
    }

This results in the following error: "Generic parameter 'Subject' could not be inferred". It took me ten minutes to realize what was the problem: I forgot to add a setter.

2019-04-23 ISO date on macOS

To print the current date in ISO 8601 format on macOS:

  $ date +%Y-%m-%dT%H:%M:%S
  2019-04-23T14:44:46

To print it with the timezone information, append %z.

  $ date +%Y-%m-%dT%H:%M:%S%z
  2019-04-23T14:44:42+0200

Note that in the last case, you're missing the colon in the timezone bit. To correct this, we'll need to do some bash wizardry:

  $ zone=$(date +%z);datetime=$(date +%Y-%m-%dT%H:%M:%S); echo $datetime${zone:0:3}:${zone:3:2}
  2019-04-23T14:44:42+02:00

2018-08-27 Two useful Xcode shortcut keys

I found out about two extremely useful Xcode shortcut keys: Ctrl+Cmd+[ or ] to cycle through the active target, and Ctrl+Option+Cmd+[ or ] to cycle through devices.

2018-05-15 Non-existing dates

One William Woodruff wrote a blog entry in 2015 about non-existing dates: https://blog.yossarian.net/2015/06/09/Dates-That-Dont-Exist

I'm sad to say that Foundation allows you to construct these supposedly illegal Gregorian dates :(

    import Foundation
    var dateComponents = DateComponents()
    dateComponents.day = 6
    dateComponents.month = 10
    dateComponents.year = 1582
    dateComponents.timeZone = TimeZone(abbreviation: "UTC")
    if let date = Calendar(identifier: .gregorian).date(from: dateComponents) {
        print(date)
    } else {
        print("Not a date")
    }

The above Swift code can be pasted into a playground. Supposedly, it shouldn't print the date but it does. This is a pretty obscure corner case of course.

2018-01-11 Navigationbar below statusbar on iOS 10

I had the strangest issue today. When testing my current app on iOS 10, the navigation bar would sit below the status bar. In other words, the navigation bar would be 44 pixels high, instead of 64 pixels. On iOS 11, this issue wasn't present.

This happened because our app does not use storyboards, and we used the following AppDelegate.swift:

    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
        var window: UIWindow? = UIWindow(frame: UIScreen.main.bounds)
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
            let mainViewController = ViewController()
            let navigationController = UINavigationController(rootViewController: mainViewController)
            self.window?.rootViewController = navigationController
            self.window?.makeKeyAndVisible()
            
            return true
        }
    }

The problem is, that the window property is initialized too early. Moving it to didFinishLaunching fixes the problem:

    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
        var window: UIWindow?
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
            self.window = UIWindow(frame: UIScreen.main.bounds)
            let mainViewController = ViewController()
            let navigationController = UINavigationController(rootViewController: mainViewController)
            self.window?.rootViewController = navigationController
            self.window?.makeKeyAndVisible()
            
            return true
        }
    }

2017-12-23 Showing SVG image without libraries

Recently I had the need to show a logo in SVG format, but the project required that we did not include 3rd party libraries. The following Swift playground shows how you can show an SVG-based image using WKWebView.

Somehow, you need to know the image size beforehand -- or at least you need to be able to set width and height constraints. Lots of logos are square, thus there's no need to do anything special.

    import UIKit
    import PlaygroundSupport
    import WebKit
    class MyViewController : UIViewController {
        override func loadView() {
            let view = UIView()
            view.backgroundColor = .white
            let webView = WKWebView()
            webView.translatesAutoresizingMaskIntoConstraints = false
            let header =
    """
    <!DOCTYPE html><html style=\"overflow: hidden\">
    <head>
    <meta name="viewport" content="initial-scale=1.0" />
    <title>icon_people_search</title>
    </head>
    <body style=\"margin: 0;\">
    """
            let footer =
    """
    </body>
    """
            let svg =
    """
    <?xml version="1.0" encoding="iso-8859-1"?>
    <!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
    <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
         viewBox="0 0 495 495" style="enable-background:new 0 0 495 495;" xml:space="preserve">
    <g>
        <polygon style="fill:#1A6FB0;" points="247.5,0 247.5,40 455,40 455,455 247.5,455 247.5,495 495,495 495,0     "/>
        <polygon style="fill:#1E81CE;" points="40,455 40,40 247.5,40 247.5,0 0,0 0,495 247.5,495 247.5,455     "/>
        <path style="fill:#1E81CE;" d="M205.767,405h65.266V247.413h43.798c0,0,4.104-25.428,6.103-53.235h-49.647v-36.264
            c0-5.416,7.109-12.696,14.153-12.696h35.564V90h-48.366c-68.478,0-66.872,53.082-66.872,61.009v43.356h-31.771v53.029h31.771V405z"
            />
    </g>
    </svg>
    """
            
            webView.loadHTMLString(header + svg + footer, baseURL: Bundle.main.bundleURL)
            view.addSubview(webView)
            
            let constraints = [
                webView.widthAnchor.constraint(equalToConstant: 100),
                webView.heightAnchor.constraint(equalToConstant: 100),
                webView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                webView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
            ]
            view.addConstraints(constraints)
            
            self.view = view
        }
    }
    // Present the view controller in the Live View window
    PlaygroundPage.current.liveView = MyViewController()

2017-11-07 Close button on popover

On iPhone, you usually add a cancel/close button to a modal popover. On iPad, there's usually no need to do so. Users just tap outside the popover to dismiss it. However when you're building an app for the iPad, and you support Split View and Multitasking, you suddenly do need it.

UIAdaptivePresentationControllerDelegate

The following viewcontroller will display a popover, and if necessary a close button will be added.

    class PresentingViewController: UIViewController, UIPopoverPresentationControllerDelegate {
        
        // MARK: - UIPopoverPresentationControllerDelegate
        
        func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
            return .fullScreen
        }
        
        func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
            guard let navigationController = controller.presentedViewController as? UINavigationController else {
                fatalError("Unexpected type of controller")
            }
            
            let closeButton = UIBarButtonItem(title: "Close", style: .done, target: self, action: #selector(close))
            navigationController.topViewController?.navigationItem.leftBarButtonItem = closeButton
            return navigationController
        }
        
        @objc func close() {
            self.dismiss(animated: true, completion: nil)
        }
        
        // MARK: - View cycle
        
        @objc func popoverAction() {
            let pvc = PopoverViewController()
            let navigationController = UINavigationController(rootViewController: pvc)
            navigationController.modalPresentationStyle = .popover
            navigationController.popoverPresentationController?.delegate = self
            navigationController.popoverPresentationController?.sourceView = self.view
            navigationController.popoverPresentationController?.permittedArrowDirections = .up
            navigationController.popoverPresentationController?.sourceRect = CGRect(x: 40, y: 40, width: 1, height: 0)
            self.present(navigationController, animated: true, completion: nil)
        }
        override func viewDidLoad() {
            self.view.backgroundColor = UIColor.yellow
            let barButtonItem = UIBarButtonItem(title: "Popover", style: .plain, target: self, action: #selector(popoverAction))
            self.navigationItem.leftBarButtonItem = barButtonItem
        }
    }
    class PopoverViewController: UIViewController {    
        override func viewDidLoad() {
            self.view.backgroundColor = UIColor.green
        }
    }

My old solution

For historic accuracy, below you can find the manual solution. It's mainly obsolete.

    class PresentingViewController: UIViewController {    
        private var popoverViewController: PopoverViewController?
        
        override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
            self.popoverViewController?.doneButtonHidden = (self.traitCollection.horizontalSizeClass == .regular)
        }
        
        @objc func popoverAction() {
            let pvc = PopoverViewController()
            pvc.doneButtonHidden = (self.traitCollection.horizontalSizeClass == .regular)
            
            let navigationController = UINavigationController(rootViewController: pvc)
            navigationController.modalPresentationStyle = .popover
            navigationController.popoverPresentationController?.sourceView = self.view
            navigationController.popoverPresentationController?.permittedArrowDirections = .up
            navigationController.popoverPresentationController?.sourceRect = CGRect(x: 40, y: 40, width: 1, height: 0)
            self.present(navigationController, animated: true, completion: nil)
            self.popoverViewController = pvc
        }
    
        override func viewDidLoad() {
            self.view.backgroundColor = UIColor.yellow
    
            let barButtonItem = UIBarButtonItem(title: "Popover", style: .plain, target: self, action: #selector(popoverAction))
            self.navigationItem.leftBarButtonItem = barButtonItem
        }
    }
    class PopoverViewController: UIViewController {    
        var doneButtonHidden: Bool = false {
            didSet {
                let button = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissAction))
                self.navigationItem.leftBarButtonItem = self.doneButtonHidden ? nil : button
            }
        }
    
        @objc func dismissAction() {
            self.dismiss(animated: true, completion: nil)
        }
        
        override func viewDidLoad() {
            self.view.backgroundColor = UIColor.green
        }
    }

This works, but requires that you keep a reference to the PresentedViewController, to update the visibility of the close button. I don't much like having a class member variable when it could be a local variable, because it clutters up the code of the PresentingViewController. But this is the most concise and readable code I could come up with.

Note that when you use child ViewControllers, this does not seem to work. The reason is that traitCollectionDidChange() doesn't get called automatically. In that case, it could be acceptable to use viewDidLayoutSubviews().

2017-11-06 Xcode 9.1 unknown error

After upgrading to Xcode 9.1 (build 9B55), the following error would be shown in a modal dialog after opening an Xcode project:

    An unknown error occurred
    the path 'xxxxxx' exists but is not a tree (-3)

This particular path is included via a git submodule, but I'm not sure if that's related. The problem is fixed by removing the path its reference from Xcode, then add it again.

2017-10-09 using reduce in Swift

Here's a little playground that shows how to use reduce in Swift, specifically use it to report on a bunch of booleans. In the following code, we use reduce to determine whether all objects are enabled, or whether at least one is enabled.

    struct MyStruct {
        var enabled: Bool
        var text: String
    }
    let collection = [
        MyStruct(enabled: true, text: "one"),
        MyStruct(enabled: false, text: "two"),
        MyStruct(enabled: true, text: "three"),
    ]
    let enabledArray = collection.map { $0.enabled }
    let allEnabled = enabledArray.reduce(true) { $0 && $1 }
    print(allEnabled)
    let oneEnabled = enabledArray.reduce(false) { $0 || $1 }
    print(oneEnabled)

I've used this when I had a bunch of UITextField instances. Some of them were enabled, some not (i.e. property isEnabled set to true or false). Or for example when you have a bunch of UIView instances; are none of them hidden? That sort of stuff.

I could've posted this example with just an array of booleans as input, but I wanted to demonstrate the map as well. Often you don't just have a bare array of booleans.

One minor thing with the allEnabled variable in the example is that its result is meaningless when applied to an empty array. It'll return true. But what does that mean, right? You'll have to decide for yourself.

2017-10-06 reset display under macOS

Sometimes, you need to reset the display under macOS, without it being visible. Under Linux, this is incredibly easy; CTRL-ALT-F1 usually gives you a text console. You can then jump back to the GUI with alt-Left. Under macOS, this is slightly harder.

This does require preparation. First, you have to make a shortcut key to System Preferences:

  • Open System Preferences
  • Open the Keyboard applet
  • Open the Shortcuts tab
  • On the left, select App Shortcuts
  • On the right, below All Applications, add a shortcut key. The text should be exactly "System Preferences..." The shortcut key could be something like Ctrl-Cmd-,

In the future, it'll help you as follows: assuming you're logged in and for some reason don't have a display, do the following procedure:

  • Type Ctrl-Cmd-,
  • Hit Cmd-L to assure System Preferences is at the Show All view
  • Type "Displays" and hit space. This opens the Display Preferences.
  • Assuming the resolution is set to "Default for display", hit the Tab key twice and press Down arrow. This selects "scaled".
  • Hit tab three times. This selects the medium resolution. Hit space. This should reset the resolution and bring back your display.

2017-09-25 Swift example of a factory and a closure typealias

I couldn't find a nice, compact example of a closure typealias in Swift, so here is one you can paste straight into a Playground:

    import Foundation
    typealias MakeClosure = (_ a: Int, _ b: Int) -> Int
    class IntFactory {
        static let instance = IntFactory()
        var makeClosure: MakeClosure?
        func makeInt(a: Int, b: Int) -> Int {
            guard let closure = self.makeClosure else {
                fatalError()
            }
            let result = closure(a, b)
            return result
        }
        private init() {
        }
    }
    func sum(a: Int, b: Int) -> Int {
        let result = a + b
        return result
    }
    IntFactory.instance.makeClosure = sum
    IntFactory.instance.makeInt(a: 22, b: 20)
    // This also works
    let sum2: MakeClosure = { (a: Int, b: Int) in
        a + b
    }
    IntFactory.instance.makeClosure = sum2
    IntFactory.instance.makeInt(a: 22, b: 20)

2017-09-17 Replacing NSTimer in Swift 4

For repeated calling of a function, the Timer class is available (used to be called NSTimer). So perhaps you had the following code in your Swift 3 project:

    private var timer: Timer?
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        self.timer = Timer.scheduledTimer(
            timeInterval: 1.0, target: self,
            selector: #selector(self.timerAction),
            userInfo: nil,
            repeats: true)
    }
    func timerAction() {
        // Do something
    }

When you move your project to Swift 4 (via Xcode 9), then first you get the following warning:

    The use of Swift 3 @objc inference in Swift 4 mode is deprecated. Please address deprecated @objc inference warnings, test your code with “Use of deprecated Swift 3 @objc inference” logging enabled, and then disable inference by changing the "Swift 3 @objc Inference" build setting to "Default" for the "blah" target.

You can fix this warning by updating the project settings and update the code as follows:

    @objc func timerAction() {
        // Do something
    }

For some reason, I don't like seeing that @objc in my projects. There's an alternative, and that's not to use Timer at all, but instead move to GCD:

    private let timer = DispatchSource.makeTimerSource()
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        timer.schedule(deadline: .now(), repeating: 1.0)
        timer.setEventHandler {
            DispatchQueue.main.sync {
                self.timerAction()
            }
        }
        timer.activate()
    }

Have fun with Swift 4 :)

2017-07-25 Swift dictionary keys to array

The following code can be pasted into a Swift playground, and demonstrates how you can get a mutable array of (a copy of the) dictionary keys.

    let dict = ["Fritz": "Senior Engineer",
                "Mary": "Director of Safety",
                "John": "VP of this and that"]
    var keyArray: [String] = dict.keys.map { $0 }
    print(keyArray)
    if let index = keyArray.index(of: "Fritz") {
        keyArray.remove(at: index)
    }
    print(keyArray)

If you don't cast to an array of Strings, you'll get the following error:

    error: value of type 'LazyMapCollection<LazyMapCollection<Dictionary<String, String>, String>, String>' has no member 'remove'

2017-06-07 Make a list of files left after installing software

Sometimes, Mac software is distributed as an installer. Potentially that leaves a bunch of files strewn all over your Mac its filesystem. One tip is to run the following command before installation:

 $ find -x . /  > before.txt

Then install the software, and run it once. Quit, then run the following two commands:

 $ find -x . /  > after.txt
 $ diff before.txt after.txt > filelist.txt

Now open the filelist.txt and you'll see a rough list of all installed files. Both as a result of the installation, but also takes into account whatever the software wrote in your ~/Library folder.

2017-05-29 Creating a Swift module

When creating a custom view, I find it very useful to make it into a framework and include an example project. This enables quickly iterating over the new component. Plus, when you're done, it's very easy to import the result into other projects. To set this up, take the following steps:

  • Create new project, and choose Cocoa Touch Framework
  • Type some name that reflects a library name such that "import yourlibname" looks logical and good
  • Create a new Swift file, and add your code there
  • Then go to menu File -> New -> Target and create a Single View Application
  • Name it YourlibnameExample or something
  • Select the project, then the new target, and in the Embedded Binaries section, click the plus
  • Select the previously created framework, and click Add
  • Then edit the new ViewController and type "import yourlibname"
  • Then instantiate a class from your framework somewhere; no errors should occur

2017-05-09 Check PDF file header with Swift

Here's a little Swift playground that shows how you can check whether a Data object (for example from a file) is a PDF. It's done by checking the first couple of bytes.

    import UIKit
    // http://stackoverflow.com/a/26503955/1085556
    func dataWithHexString(hex: String) -> Data {
        var hex = hex
        var data = Data()
        while(hex.characters.count > 0) {
            let c: String = hex.substring(to: hex.index(hex.startIndex, offsetBy: 2))
            hex = hex.substring(from: hex.index(hex.startIndex, offsetBy: 2))
            var ch: UInt32 = 0
            Scanner(string: c).scanHexInt32(&ch)
            var char = UInt8(ch)
            data.append(&char, count: 1)
        }
        return data
    }
    struct HeaderError: Error {
    }
    // http://stackoverflow.com/a/17280876/1085556
    let smallestPDFasHex = "255044462D312E0D747261696C65723C3C2F526F6F743C3C2F50616765733C3C2F4B6964735B3C3C2F4D65646961426F785B302030203320335D3E3E5D3E3E3E3E3E3E"
    let data = dataWithHexString(hex: smallestPDFasHex)
    let headerRange: Range<Data.Index> = 0..<4
    let header = data.subdata(in: headerRange)
    guard let headerString = String(data: header, encoding: .ascii) else {
        print("Header not found")
        throw HeaderError()
    }
    if headerString == "%PDF" {
        print("It's a PDF")
    } else {
        print("It's NOT a PDF")
    }

2017-04-11 UIStackView playground

Here's a nice way to play with a UIStackView. Copy and paste the following code into a playground, and next to the final line, click the screen icon to permanently show it. Then adjust the parameters of the playground where necessary.

    import UIKit
    var view = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
    view.backgroundColor = UIColor.yellow
    let label1 = UILabel()
    label1.text = "label 1"
    let label2 = UILabel()
    label2.text = "label 2"
    let spacer1 = UIView()
    spacer1.backgroundColor = UIColor.green
    spacer1.translatesAutoresizingMaskIntoConstraints = false
    spacer1.widthAnchor.constraint(equalToConstant: 3).isActive = true
    let spacer2 = UIView()
    spacer2.backgroundColor = UIColor.gray
    let stackView = UIStackView(arrangedSubviews: [label1, spacer1, label2, spacer2])
    stackView.distribution = .fill
    stackView.axis = .horizontal
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.spacing = 20
    view.addSubview(stackView)
    stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
    stackView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
    stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
    view.layoutIfNeeded()
    view

The result should look something like this:

uistackview playground 2017-04-11.png

2017-02-21 Linux VPS with TeamViewer

Here are my short notes on creating a Linux VPS (virtual private service) which can be remotely accessed via TeamViewer. I prefer TeamViewer over other ways of remotely accessing a desktop, because it works cross-platform and through NAT and firewalls.

The big problem is often that a Linux VPS doesn't have a virtual graphical card. For remote access to the GUI, most VPS providers advise VNC while I prefer TeamViewer.

I've tried a number of Linux distributions on a number of VPS providers. The instructions below fail on VPSes at Scaleway or DigitalOcean, but the combination of Fedora 25 and Linode or Vultr made this all very easy.

First of all, register or log in at Linode or Vultr, and create a VPS. You'll need at least 1 GB of memory.

After it's started, log in via SSH as root and do the following:

  # yum -y groupinstall "Fedora Workstation"

Then get the TeamViewer download URL and install the package:

  # wget "https://download.teamviewer.com/download/teamviewer.i686.rpm"
  # yum -y install ./teamviewer.i686.rpm

Next reboot, make sure the GUI starts:

  # systemctl set-default graphical.target

If your VPS runs at Vultr, disable Wayland. Edit the file /etc/gdm/custom.conf and remove the comment for the line WaylandEnable=false so GDM uses X.org. Linode already has this correctly set.

Usually you'll also want to add swap space; this adds a gig:

 # dd if=/dev/zero of=/swapfile1 bs=1024 count=$1024*1024]
 # mkswap /swapfile1
 # swapon /swapfile1
 # chmod 600 /swapfile1

Add following line to /etc/fstab:

 /swapfile1 swap swap defaults 0 0

Then, add a user for yourself:

  # useradd -m -U mynewusername
  # passwd mynewusername

Add the new user to the sudoers, and reboot:

  # visudo
  # reboot

If you're on Linode, open remote access via glish, which is Linode's way of giving you graphical access to your VPS, through your browser. Log into the Linode management console, click on the node, then in the tab Remote Access, click the link "Launch Graphical Web Console".

If you're on Vultr, open remote access by navigating to the Instances tab in the management console, then click the three dots at the right of your server and in the menu, click "View Console":

vultr 2017-02-21.png

You should see the graphical Linux login screen. In the top left corner, set shell to "Gnome Xorg" (this is important!) and then continue to log into Gnome.

Linode login 2017-02-21.png

In Gnome, start TeamViewer. Check the three boxes to enable remote access. You'll need to provide TeamViewer username/password, as well as click the link in the email you'll get, to confirm adding this VPS to your TeamViewer device list.

Done!

Note: a current disadvantage is that you're constricted to pretty low resolutions. On Linode, you can't set the resolution to something bigger than 1280x768. On Vultr, the maximum is 1280x1024.

2017-02-03 Create your own laptop battery test

Recently I wanted to test how long the battery of my 2013 MacBook Air lasts. The quickest solution I found, is as follows:

  • Download Firefox.
  • Get the iMacros for Firefox extension.
  • Make a list of, say, ten websites.
  • After installation, click the iMacros button so the sidebar appears.
  • Open a new tab for the purpose of recording the macro.
  • Click record.
  • Type in these ten sites. Just type the URL in the address bar. Clicking anywhere will often result in failed playback. Searching for a keyword in Google is fine, though.
  • Stop recording and edit the macro. Add the line "WAIT SECONDS=5" a couple of times, to simulate the time spent reading.
  • Play the macro once to make sure you won't have errors occuring during playback.
  • In the settings of iMacros, set playback speed to slow.

Now to test the battery:

  • Click on the battery icon in the menubar and click "show percentage". It should obviously show 100% at this point because we want to start fully charged.
  • In System Preferences, turn off the screensaver. Under Energy Saver, set "turn display off after..." to "Never".
  • Note the current time somewhere, then remove power cord.
  • Set the screen brightness to something reasonable, like 75%.
  • Set the loop field to 10000 or some other high number.
  • Click the Play Loop button.
  • Every half hour, have a look at the battery level and write it down.

So this is my ghetto battery test. It's a bit rough around the edges, but should give you an idea of how many hours the battery lasts. Don't take the results too serious, this is meant to get a ballpark figure.

2017-02-02 Swift app without Interface Builder

Here's an example of an AppDelegate.swift, for those who don't like Interface Builder:

    import UIKit
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
        var window: UIWindow?
        func application(_ application: UIApplication,
            didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
            self.window = UIWindow(frame: UIScreen.main.bounds)
            let vc = ViewController()
            let nc = UINavigationController(rootViewController: vc)
            self.window?.rootViewController = nc
            self.window?.makeKeyAndVisible()
            
            return true
        }
    }

Remove the Main.storyboard file from the project. And in the Info.plist of your project, remove the entry called "Main storyboard file base name:

main storyboard file base name 2017-02-03.png

2016-12-25 Firefox crashes on late 2016 MacBook Pro

I've been using Firefox on a late 2016 MacBook Pro, running macOS 10.12.2. Every time the laptop wakes from sleep, Firefox has crashed. To remedy this situation, I've created a Hammerspoon script to kill Firefox when the system goes to sleep, and start it upon waking. In Firefox Preferences -> General, you can configure a setting called "When Firefox starts". Set this to "Show my windows and tabs from last time". Then install Hammerspoon, create the folder .hammerspoon in your home directory and create the file init.lua in that new folder. Then paste the following code and in Hammerspoon, reload the configuration.

    local log = hs.logger.new('mywatcher','debug')
    function callback(eventType)
        if (eventType == hs.caffeinate.watcher.systemWillSleep) then
            local firefox = hs.application.find('Firefox')
            if not (firefox == nil) then
                firefox:kill()
            end
        elseif (eventType == hs.caffeinate.watcher.systemDidWake) then
            local firefox = hs.application.find('Firefox')
            if (firefox == nil) then
                firefox = hs.application.open("/Applications/Firefox.app")
            end
        end
    end
    local mywatcher = hs.caffeinate.watcher.new(callback)
    mywatcher:start()
    log.i("Started")

2016-11-29 Firefox on macOS

To have Firefox look a little more at home on macOS, check out the following addons:

I've also noticed that Firefox 50.0.2 sometimes crashes when disconnecting the external monitor from my 2016 MacBook Pro. After restarting, it brings up the Restore Session screen with the "Well this is embarrassing" message. To turn this off, go to about:config, search for resume_from and double-click on the value browser.sessionstore.resume_from_crash so it's set to false.

2016-11-14 Questions when looking for a coworking space

I've heard the figure that every four hours, a new coworking space is created. Personally, I've got a permanent desk in a coworking space, which allows me to put down a decent monitor, some personal stuff, a USB hub with a decent keyboard and mouse, et cetera. If you're also looking for a nice (semi-)permanent desk somewhere, here's a list of questions to get a feel of the place:

  • Who is the day-to-day floormanager?
  • Who has access to this room?
  • Are desks and chairs supplied, or should I bring my own?
  • Do I get an ethernet cable? This is very important because WiFi can be iffy.
  • Is there a lock on the door? What quality is the lock and the door itself? Can I get additional keys? Can I change the lock if necessary?
  • What's the speed of the internet at work times? If they don't know, ask whether you can place high-quality Skype and Facetime calls.
  • What's the level of sound during the day? Are there people here whose main job it is to call around?
  • What kind of businesses are next to this room? Above and below it?
  • Can I enter the building 24/7 or are there limits?
  • Is cleaning included? How often is this done? What's the cost?
  • What's the cost of parking a car? Are their spots to charge an electric car?
  • Any additional costs for internet, heating, et cetera?

Be sure to write down these answers. I've had the unfortunate situation where an office manager would give me a room next to a studio, and forgot to mention this. Luckily a desk was available in another coworking room.

2016-11-08 AutoLayout in a playground

What got me confused today, was how to play with AutoLayout in a Playground. The trick is to first create a main view, akin to the view in a View Controller, that is sized with a frame (without constraints).

After that's set up, add subviews that use AutoLayout. The following works for Xcode 8.1:

    import UIKit
    let view = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
    view.backgroundColor = UIColor.white
    view.layer.borderWidth = 2.0
    let licensePlateView = UIView()
    licensePlateView.translatesAutoresizingMaskIntoConstraints = false
    licensePlateView.backgroundColor = UIColor.gray
    view.addSubview(licensePlateView)
    licensePlateView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    licensePlateView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
    licensePlateView.widthAnchor.constraint(equalToConstant: 200).isActive = true
    licensePlateView.heightAnchor.constraint(equalToConstant: 50).isActive = true
    view.layoutIfNeeded()
    view

Screenshot:

autolayout in Xcode 2016-11-08.png

2016-10-31 Mice and keyboards with USB-C

After seeing Apple coming out with new 13" and 15" machines with only USB-C connections, I wondered whether there are mice with USB-C. This could be a choice when you don't want or can't use Bluetooth due to preference, regulations or interference. I was very surprised that it's very slim pickings. Big brands like Logitech haven't gotten around to those, and the only one I've been able to find is this one:

http://www.trust.com/en/product/20969-usb-c-retractable-mini-mouse-black

Currently I'm unable to find keyboards with USB-C, so a good option is to get one of those keyboards with a detachable cable and simply replace it with a USB-C cable. The CoolerMaster keyboards usually (all?) have detachable cables.

2016-10-17 Compare floats in Swift

To compare floats to zero, or each other, use the following functions:

    func fequal(_ a: Double, _ b: Double) -> Bool {
        return fabs(a - b) < DBL_EPSILON
    }
    func fequal(_ a: Float, _ b: Float) -> Bool {
        return fabs(a - b) < FLT_EPSILON
    }
    func fequal(_ a: CGFloat, _ b: CGFloat) -> Bool {
        return fabs(a - b) < CGFloat(FLT_EPSILON)
    }
    func fequalzero(_ a: Double) -> Bool {
        return fabs(a) < DBL_EPSILON
    }
    func fequalzero(_ a: Float) -> Bool {
        return fabs(a) < FLT_EPSILON
    }
    func fequalzero(_ a: CGFloat) -> Bool {
        return fabs(a) < CGFloat(FLT_EPSILON)
    }

To test, paste the above and the below code in a playground:

    var a: Double = 0.1
    var b: Double = 0.2
    var c: Double = 0.3
    let d = a + b
    if c == d {
        print("Equal!")
    } else {
        print("Not equal")
    }
    if fequal(c, d) {
        print("Equal!")
    } else {
        print("Not equal")
    }

2016-10-06 Walking through a String in Swift 3

This is how you walk through a string in Swift 3:

    let str = "Hello, playground"
    let stepsize = 3
    let len = str.characters.count
    for start in stride(from: 0, to: len, by: stepsize) {
        var end = start + stepsize
        if end > len {
            end = len
        }
        
        let startIndex = str.index(str.startIndex, offsetBy: start)
        let endIndex = str.index(str.startIndex, offsetBy: end)
        print("From \(start) to \(end)")
        print(str[startIndex..<endIndex])
    }

Note: not optimized in any way.

2016-09-22 Swift 3 naming

Swift 3 is different from version 2 in the naming department. The short version is that the first label of a function is now mandatory, and that the function signature should reflect this.

Old:

    func pageViewsfromContent(content: ENFormContent) -> [VIFormPageView]

New:

    func pageViews(fromContent content: ENFormContent) -> [VIFormPageView]

See also: https://swift.org/documentation/api-design-guidelines/

2016-09-13 weird error when creating constraint in Swift 3

Today, I've transitioned a project to Swift 3 and I bumped into a crash. The crash was as follows:

    -[_SwiftValue nsli_superitem]: unrecognized selector sent to instance 0x170249a50
    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_SwiftValue nsli_superitem]: unrecognized selector sent to instance 0x170249a50'
    *** First throw call stack:
    (0x1816741c0 0x1800ac55c 0x18167b278 0x181678278 0x18157259c 0x1820ab104 0x1820a9948 0x1820a879c 0x1820a8340 0x100176cc0 0x10012071c 0x100123474 0x1874b55c8 0x1874cd15c 0x1876556ac 0x18756dd74 0x18756d9dc 0x18756d940 0x1874b2738 0x18497b40c 0x1849700e8 0x18496ffa8 0x1848ecc64 0x1849140d0 0x1874a7e08 0x1816217dc 0x18161f40c 0x18161f89c 0x18154e048 0x182fd1198 0x187520818 0x18751b550 0x10002387c 0x1805305b8)
    libc++abi.dylib: terminating with uncaught exception of type NSException

Turns out this happens at the end of the following bit of code:

 scrollView = UIScrollView()
    scrollView.translatesAutoresizingMaskIntoConstraints = false
    self.view.addSubview(scrollView)
    contentView = UIView()
    contentView.translatesAutoresizingMaskIntoConstraints = false
    scrollView.addSubview(contentView)
    let viewDict = [
        "contentView": contentView,
        "scrollView": scrollView,
    ]
    let vFormat = "V:|[contentView]|"
    let constraints = NSLayoutConstraint.constraints(withVisualFormat: vFormat, options: [], metrics: nil, views: viewDict)

Yeah, I've been creating UIView objects by hand instead of using Interface Builder. Like a savage! After playing with the debugger, it turns out the compiler infers the type of the viewDict as a [String: UIView?] and do notice that UIView optional.

The problem is fixed by declaring the viewDict as follows:

  let viewDict: [String: UIView] = .....

Just leaving this here for DuckDuckGo, plus I've answered a similar question on StackOverflow.

2016-08-16 Filter array in Swift

Here's a bit of code that you can throw in a playground. Given an array with a bunch of objects, it'll filter the array based on a property. Then it'll print the result.

    class MyObj: CustomStringConvertible {
        var enabled = true
        var text = ""
        
        init(enabled: Bool, text: String) {
            self.enabled = enabled
            self.text = text
        }
        
        var description: String {
            return "MyObj=\(text)"
        }
    }
    let collection = [
        MyObj(enabled: true,  text: "ja"),
        MyObj(enabled: true,  text: "jawohl"),
        MyObj(enabled: false, text: "no"),
        MyObj(enabled: false, text: "nope"),
        MyObj(enabled: false, text: "non"),
        MyObj(enabled: true,  text: "yep"),
    ]
    print(
        collection.filter { $0.enabled }
                  .map { $0.text }
    )

2016-08-11 UIImage nsli superitem

Got this weird error today while futzing around with AutoLayout, or more specifically VFL, Visual Format Language:

    2016-08-11 11:05:00.915 TestTruckButton[74067:6491323] -[UIImage nsli_superitem]: unrecognized selector sent to instance 0x7fb8fbc4ae80
    2016-08-11 11:05:00.924 TestTruckButton[74067:6491323] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIImage nsli_superitem]: unrecognized selector sent to instance 0x7fb8fbc4ae80'
    *** First throw call stack:
    (
    0   CoreFoundation                      0x000000010301cd85 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x0000000104dc3deb objc_exception_throw + 48
    2   CoreFoundation                      0x0000000103025d3d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
    3   CoreFoundation                      0x0000000102f6bcfa ___forwarding___ + 970
    4   CoreFoundation                      0x0000000102f6b8a8 _CF_forwarding_prep_0 + 120
    5   Foundation                          0x00000001034037af -[NSLayoutConstraintParser findContainerView] + 49
    6   Foundation                          0x00000001034033ae -[NSLayoutConstraintParser finishConstraint] + 691
    7   Foundation                          0x0000000103402986 -[NSLayoutConstraintParser parse] + 375
    8   Foundation                          0x00000001034025c6 +[NSLayoutConstraintParser constraintsWithVisualFormat:options:metrics:views:] + 91
    .......
    .......
    .......
    libc++abi.dylib: terminating with uncaught exception of type NSException

This happened because you tried to use an UIImage instead of an UIImageView in VFL. Just leaving this here so AltaVista & Co. can find it.

2016-06-12 Veertu the hidden gem

For my last project, I've been using Veertu. I consider it the hidden gem of desktop virtualization. The big boys in this space are basically Parallels, VMWare and VirtualBox. They all require loads of kernel drivers, and personally I like my Mac as bog-standard as possible.

But since last year, there is Veertu. It uses Apple's own Hypervisor framework and it's distributed through the Mac App Store. I consider this a major accomplishment. Recently some Mac apps have been leaving the store citing limitations of one or the other, but these guys have managed to get their virtualization app running in the sandbox that the App Store requires!

Amazing, really. And it's a good product as well. I've been using it to test Ansible scripts. The backend of my client's project was deployed via Ansible but the script hadn't been maintained for some time. It was very nice to kick up a fresh Ubuntu server instance, test the Ansible script and log any issues that occurred so our backend guy could have a look.

And this is a small thing, but shows attention to detail: the IP address is shown in the list of running VMs: veertu5 shows IP-fs8 2016-06-12.png

Veertu is first and foremost an OS X app so looks and behaves very much like you'd expect, unlike for instance VirtualBox. I've read about some limitations related to graphics performance and USB support. For my purposes, I haven't run into these. I did notice it doesn't yet support sparse files, in the sense that when you reserve 20 GB for your VM, it really does reserve 20 gigs on disk. In my case that wasn't a problem; my VMs don't have a long life.

They're updating it all the time; last update was roughly three weeks ago. So for instance the sparse files thing seems to have been fixed.

In any case, since Veertu is free for basic Linux usage, why not try and see if it supports your use case? To give it a shot with the least amount of hassle, install and start Veertu, add a VM and pick Ubuntu Gnome 16.04 desktop. After downloading, the VM will be started. Then instead of installing, select "Try Ubuntu":

veertu ubuntu 2016-06-12.png

Some links:

2016-06-02 Good error messages in Xcode Run Scripts phase

Xcode has an option to run a script when building. This is necessary for example when using Carthage. Reading the instructions, Carthage says: "Create a Run Script in which you specify your shell (ex: bin/sh), add the following contents to the script area below the shell: /usr/local/bin/carthage copy-frameworks"

However, to make things easy for your (new) colleagues, I urge you to create an extremely clear error message. You can do this as follows. First, when adding a Run Script phase, change the shell to /bin/bash. Then add an error message by checking for prerequisites, and printing to stderr combined with an "exit 1". For Carthage you could do the following:

    CARTHAGE="/usr/local/bin/carthage"
    if [[ -e "$CARTHAGE" ]]; then
        /usr/local/bin/carthage copy-frameworks
    else
        echo "--------------------------------------------------------"  1>&2
        echo 1>&2
        echo "Please install carthage, see also:"  1>&2
        echo "https://github.com/Carthage/Carthage#installing-carthage"  1>&2
        echo 1>&2
        echo "--------------------------------------------------------"  1>&2
        exit 1
    fi

This results in the following very obvious message when Carthage is not installed:

carthage build error 2016-06-06.png

I don't think anybody would object to such a nice pointer in the right direction.

2016-05-26 Printing on a Kyocera FS2020D from OS X

Here's how you can install a Kyocera FS2020D printer on OS X.

First, download and install the driver: https://www.kyoceradocumentsolutions.co.uk/index/products/download_centre.false.driver.FS2020D._.EN.html#MAC This will cause an additional icon to appear in the System Properties app. It's labeled "Select PDL". You can ignore it.

If you have the hostname or IP address of the printer, great. If not, find out the IP address of the printer as follows. On the printer, press the menu button, then arrow right and arrow down. The display should say "status page". Press OK and a single status page is printed. It'll mention a couple of things, but most importantly its IP address.

On OS X, in System Preferences, go to Printers & Scanners, add a printer with the plus button, go to the second tab and fill in the IP address. Set protocol to Line Printer Daemon.

That should be all you have to do. If you wish, adjust the name, then click the Add button.

Happy printing.

2016-05-25 Quote from Philip Morgan

Here's a nice quote from Philip Morgan:

"If you look at your marketing message and say, “wow, there are probably only 10 companies out there that fit that profile”, then you are in the right ballpark in terms of specificity. The reality is that there will be far more than 10 candidates, and those 50 or 100 truly ideal clients will find your marketing extremely compelling. Trust me, they won’t find it compelling if you don’t have the courage to be extremely specific.”

2016-05-24 home shortcut key for Firefox on OS X

If for some reason you are moving from Safari to Firefox on OS X, you might miss the Cmd-Shift-H shortcut key to go to the homepage. On Firefox, this is by default Option-Home but since there's no Home key on Apple keyboards, this becomes the Fn-Option-LeftArrow shortcut which I can never remember.

An alternative is to press Option-K, Enter. It'll focus on the search field in the toolbar, and pressing enter will take you to the search engine home page.

If you have removed the search field from the toolbar, then simply pressing Option-K will be enough. Very useful when you don't want to close the last remaining tab, but want to clear the current page. Update 2016-11-22: this last option no longer works because it'll open the options menu and focus on the search field there.

2016-05-24 Swift example of CoreData max query

Here's a copy/paste example of doing the equivalent of a SQL MAX() function. I know, I know "CoreData is an object graph". It's in a project where I had to keep track of the sequence in which entities were added. As far as I know, there's no equivalent of AutoIncrement in CoreData.

    func nextSequence(moc: NSManagedObjectContext) -> Int {
        let quantityExpression = NSExpressionDescription()
        quantityExpression.name = "max.sequence"
        quantityExpression.expression = NSExpression(format: "@max.sequence")
        quantityExpression.expressionResultType = .Integer32AttributeType
        
        let predicate = NSPredicate(format: "form == %@", self.form)
        
        let request = NSFetchRequest()
        request.entity = NSEntityDescription.entityForName("UMAQuestion", inManagedObjectContext: moc)
    //  request.propertiesToGroupBy = ["sequence"]
        request.resultType = NSFetchRequestResultType.DictionaryResultType
        request.propertiesToFetch = [quantityExpression]
        request.predicate = predicate
        
        var maxSequence = 0
        
        do {
            let results = try moc.executeFetchRequest(request) as? [[String:AnyObject]]
            if let maxSequenceDict = results!.first {
                if let maxSequenceResult: AnyObject = maxSequenceDict["max.sequence"] {
                    maxSequence = maxSequenceResult as! Int
                } else {
                    fatalError("Couldn't find max.sequence in dict")
                }
            } else {
                fatalError("Didn't get first result")
            }
        } catch let error as NSError {
            fatalError("Error fetching max sequence: \(error)")
        }
        return maxSequence + 1
    }

Paste this in your model file. Adjust the predicate or uncomment and adjust the propertiesToGroupBy.

2016-05-16 Renewing letsencrypt certificates via crontab

Last time, I showed how I installed the LetsEncrypt certificates but not how to automatically renew them. To do so, create a file and call it, say, "letsencrypt.sh" or something, then move it to a directory of your choice. /opt was empty so I put it there. Adapt the webroot and domain parameters of course. This works on Debian 8.0 so your mileage may vary.

  #!/bin/sh
  letsencrypt certonly --force-renewal --webroot -w /var/www/example.com -d example.com
  service apache2 reload

The --force-renewal is there because we renew monthly, while the certs are valid for three months and the client warns about this.

Now create a file in /etc/cron.d so the script gets called. Be sure to not just call it on midnight the 1st of every month -- we want to call it at some particular randomly chosen moment so the letsencrypt people don't get hit with a deluge of calls every 1st of the month. So create a file named "letsencrypt" in /etc/cron.d with the following line:

  33 5 21 * *     root   /opt/letsencrypt.sh

This particular example will call the script on the 33rd minute, on the 5th hour, and on the 21st day of the month. In other words, it'll get called once on 5:33 AM every month. Make up some values for your particular case.

It's prudent to now put a marker after this date in your calendar, to actually check whether the renewal succeeded.

As an aside, the letsencrypt package is called certbot nowadays. It's at version 7. It might be better to follow their instructions :)

2016-04-28 USB speakers

Since ages, I've had these Logitech V20 speakers (serial number S-0155A) which get both their power and their audio over USB:

https://secure.logitech.com/assets/22857/22857.png

There are a couple of awesome things about these:

  • If you have a laptop and want something close to a docking station, then just get a USB hub, connect these speakers along with your other peripherals and save yourself the connecting/disconnecting of the 3.5 mm audio jack each morning.
  • If you have a device without a 3.5 mm audio jack, but does have USB, this is a great way to get sound. For example most Synology disk stations support these speakers.
  • If you have a PC with a terribly noisy audio jack, these speakers give you a great way to get decent quality audio without the interference.
  • They're supported on every OS that I know: Linux, OS X, Windows.

Until recently, people would ask me where to get these. But unfortunately they long since went out of stock everywhere and you'd have to rely on the second-hand market. But lately I found out that Logitech makes a new model that strictly uses USB for both audio and power: the Logitech S150.

https://business.logitech.com/assets/20161/20161.png

Edit 2016-11-28: a colleague pointed me to other speakers that are hooked up via USB only:

Edit 2016-12-07: I've tested the Logitech S150 and they're not comparable in sound quality to the old v20 speakers. I'd only advise these as a step up from laptop speakers.

2016-04-24 Swift solution to StoneWall

This is a 100% scoring solution to the Codility StoneWall problem. Note the bunch of print statements which I've left included for now.

  //: https://codility.com/programmers/task/stone_wall/
  import UIKit
  public func solution1(inout H : [Int]) -> Int {
    var stack: [Int] = []
    var nBlocks = 0
    var i = 0
    var lastHeight = 0
    
    func append(height: Int) {
      print("append \(height)")
      stack.append(height)
      lastHeight = height
      nBlocks += 1
    }
    
    while i < H.count {
      let currHeight = H[i]
      print("i=\(i), currHeight=\(currHeight)")
      if i == 0 {
        append(currHeight)
      } else {
        if currHeight == lastHeight {
          print("currHeight == lastHeight")
        } else if currHeight > lastHeight {
          append(currHeight)
        } else { // currHeight < lastHeight
          // Keep popping
          stack.popLast()
          var found = false
          while !stack.isEmpty {
            let lastAppendedHeight = stack.last!
            
            if lastAppendedHeight == currHeight {
              print("lastAppendedHeight == currHeight == \(currHeight)")
              lastHeight = currHeight
              found = true
              break
            } else if lastAppendedHeight < currHeight {
              print("break")
              break
            }
            print("popLast")
            stack.popLast()
          }
          if !found {
            append(currHeight)
          }
        }
        
      }
      
      i += 1
    }
    
    return nBlocks
  }
  var H = [8,8,5,7,9,8,7,4,8]
  H = [1,2,3,4,5]
  H = [1,1,1,1,1,1,1,1,1]
  H = [1,2,3,3,3,2,2,2,2,2,1]
  H = [5,4,3,2,1]
  H = [1,2,1,2,1,2]
  solution1(&H)

2016-04-16 Codility supports Swift

If you didn't know Codility: it's a company that devises programming tests that are used to test applicants for a job opening. Besides that, other uses are simply to exercise oneself, gaining skills in solving programming puzzles.

And now it supports Swift! I've been having fun doing some small puzzles here and there. For example, this is my solution to the Number of Intersecting Disks problem.

And for Lesson 7, the Brackets test, here is the solution I came up with:

    //: Playground - noun: a place where people can play
    import UIKit
    enum Bracket: Character {
        case squareOpen = "["
        case squareClose = "]"
        case roundOpen = "("
        case roundClose = ")"
        case curlyOpen = "{"
        case curlyClose = "}"
    }
    public func solution(inout S : String) -> Int {
        var bracketStack: Array<Bracket> = []
        
        for bracketChar in S.characters {
            guard let bracket = Bracket(rawValue: bracketChar) else {
                return 0
            }
            
            switch bracket {
            case .squareOpen, .roundOpen, .curlyOpen:
                bracketStack.append(bracket)
            case .squareClose:
                if let b = bracketStack.popLast() {
                    if b != .squareOpen {
                        return 0
                    }
                } else {
                    return 0
                }
            case .roundClose:
                if let b = bracketStack.popLast() {
                    if b != .roundOpen {
                        return 0
                    }
                } else {
                    return 0
                }
            case .curlyClose:
                if let b = bracketStack.popLast() {
                    if b != .curlyOpen {
                        return 0
                    }
                } else {
                    return 0
                }
            }
        }
        
        if bracketStack.count > 0 {
            return 0
        }
        
        return 1
    }
    var validString = "{[()()]}"
    var invalidString = "([)()]"
    solution(&validString)
    solution(&invalidString)

2016-03-18 Updating Xcode

Erica Sadun about updating Xcode. The Mac App Store prompted her to update Xcode, and that's what she did. Unfortunately, the download is very big and you don't really have control over the download and update process.

I've learned long ago that it's much better to simply download the .dmg file by logging in as a developer, going to the Xcode downloads page, click on the link "Additional tools", and download the .dmg file from there.

I rename the existing installation to, for example /Applications/Xcode-7.2.app so I always can go back. And from the .dmg, copy the new version to /Applications. Then if my current project nicely compiles and deploys, I remove the old Xcode installation.

As an aside, I really hope Apple will give developers a way to download that multi-gigabyte file from the commandline or perhaps in chunks of 500 megs or something. The whole root of the XcodeGhost problem was that developers were downloading Xcode from a fast 3rd party server, because the usual route wasn't feasible.

2016-03-18 Lets Encrypt

Another day, another TLS certificate from the friendly folks at Let's Encrypt! I'm running Debian 8.2 (Jessie) so I took the following steps:

Add the following lines to /etc/apt/sources.list

 # Backports repository
 deb http://httpredir.debian.org/debian jessie-backports main contrib non-free

Update the lot, then install the client:

 $ sudo apt-get update
 $ sudo apt-get install -t jessie-backports letsencrypt

I'm running Apache and had SSL configured already. So I ran the client:

 $ sudo letsencrypt certonly --webroot -w /var/www/vankuik/www/ -d www.vankuik.nl

Then updated the domain configuration to the following lines:

 SSLCertificateFile    /etc/letsencrypt/live/www.vankuik.nl/cert.pem
 SSLCertificateKeyFile /etc/letsencrypt/live/www.vankuik.nl/privkey.pem
 SSLCertificateChainFile /etc/letsencrypt/live/www.vankuik.nl/chain.pem

Restart Apache:

 $ sudo service apache2 restart

And then reload in your browser. Et voilà, the results are in:

letsencrypt.png

See also 2016-05-16 Renewing letsencrypt certificates via crontab.

2016-03-15

Just a tip for all you Linux-loving fiends out there: after installing a server, no matter how small, you'd be wise to install sshguard. It's available on Debian, Ubuntu and all other Debian-derivates.

After installation, simply type:

 $ sudo apt-get install sshguard

This package will block bruteforce attacks and the like.

2016-02-19 Swift example of regex

Here's a quick-and-dirty copy-paste example of a regex in Swift 2.0. It pulls the width and height as integers from the string in the first line.

  var str = "codecType: 'avc1' dimensions: 640 x 360} extensions: {<CFBasicHash 0x128511ae0 [0x19f53cb68]>{type = immutable dict, count = 15"
  let regex = try! NSRegularExpression(pattern: "[0-9]{2,4} x [0-9]{2,4}", options: [])
  if let result = regex.firstMatchInString(str, options: [], range:  NSMakeRange(0, str.characters.count)) {
      let dimensionString = (str as NSString).substringWithRange(result.range)
      let dimensionArray = dimensionString.componentsSeparatedByString(" x ")
      let width = Int(dimensionArray[0])
      let height = Int(dimensionArray[1])
      // Now do something with the width and height integers
  }

2016-02-18 Swift error Instance member cannot be used on type

Today, Swift gave me the error:

  Instance member "membername" cannot be used on type "Classname"

The code was as follows:

  public class SomeClass {
      public let defaultGreeting = "Hello, playground"
      public var greeting = defaultGreeting
  }

The solution is as follows:

  public class SomeClass {
      public let defaultGreeting = "Hello, playground"
      public var greeting: String!
  
      init() {
          self.greeting = defaultGreeting
      }
  }

The reason you're getting the error, is because the class member defaultGreeting is declared but not yet initialized at the class scope. However, when you hit init() then it's available.

2016-02-14 Cleaning the Microsoft Sculpt Ergonomic

I've always been a fan of the Microsoft ergonomic keyboards. They offer the best keyboards in the "ergonomic" category for their price. The Sculpt Ergonomic got a great review from Marco Arment and I totally agree with Mr. Arment on all points.

Recently however, I spilled some tea-with-milk on the keyboard. It was really just a sip, and it hit the right Alt key. But somehow gravity took that liquid and ran it down to the arrow keys and the backspace button. I opened the keyboard but unfortunately it's a scissor-switch keyboard where the rubber keypad cannot be reached. It's backed by an metal underside that is melted to the top case of the keyboard. See the picture below; the black dots are studs whose tops have been melted flat.

MS Sculpt Ergonomic 1 small.JPG

Larger version: MS Sculpt Ergonomic 1.JPG

However I did succeed in getting it working again!

Prerequisites: I bought a 1 liter (33 oz) bottle of demineralized water and a 125 ml (4 oz) bottle of alcohol.

Then take the following steps:

  • Turn around the keyboard.
  • Remove the batteries.
  • Remove the rubber feet and unscrew the five phillips screws below. Note the position of the three longer screws.
  • Turn the keyboard back up and remove the wrist pad. Besides three screws, it's also held in place with a couple of clips. Gently apply force.
  • There's about a dozen screws now accessible below the wrist pad. Unscrew these too.
  • The keyboard top and bottom case are now held together with a dozen or so clips. You can take it apart with a broad screwdriver and some gentle force in the correct direction. Be careful however: the bottom case contains the battery holder, and it's connected to the top case.
  • Gently remove the battery connector from the 5 x 5 cm printed circuit board (PCB) below the top case.
  • Now remove the flat cable from the PCB by pulling back the black lock thus loosening the flat cable.
  • Unscrew the PCB and put it aside. Since these screws have a different length, it might be best to screw them back in after removing the PCB.

MS Sculpt Ergonomic 3 small.JPG

Larger version: MS Sculpt Ergonomic 3.JPG

  • Now remove the key caps from the keys that you spilled on. Use a broad screwdriver, and put the head in the space between the keyboard and the top of each key cap. The reason is that the scissor switch is fragile, and the key caps are clicked to the switch at the top, but slide-in at the bottom side, see details. But note: in this particular picture, the key cap is rotated 90 degrees.

MS Sculpt Ergonomic 4 small.JPG

Larger version: MS Sculpt Ergonomic 4.JPG

  • Put the top case with the metal side down in the kitchen sink.
  • Heat the water to, say, 50 degrees Celsius or something, and then pour it into the key caps you removed. Make sure you avoid dousing the flat cable, simply because it's sensitive to heat.
  • Now rinse with the alcohol.
  • Let it dry for a couple of days, and put it back together again.

2016-01-17 The new Mac Pro

As an app developer, I've always been following the new Mac Pro (usually shortened to nMP, as opposed to the cMP, where c stands for classic). For portability reasons, clients usually give me a retina MacBook Pro to work on, but the Mac Pro is a desktop computer. Thus it offers all that desktops usually offer: more power.

If you're a desktop person then for some time, the Mac Pro hasn't been the first choice; usually the 5K iMacs would be a better choice. App developer Marco Arment explains why: The Retina iMac versus the Mac Pro.

And then there's the fact that the nMP couldn't really be called "new" at this point. Both the CPU and the GPU are pretty old tech. And its SSD is easily beaten by the SSD in the 2015 rMBP. It hasn't been updated since its release end of 2013 and the MacRumors Buyer's Guide maintains a disheartening running tally since its last update.

For 2016 however, the stars might align and Apple may give some love to the nMP. Over at the MacRumors forums, enthusiasts have been speculating and there has been long-running thread with the subject Will there be a new Mac Pro in 2016. User Stacc gave an excellent summary of that thread which explains what we can expect of the new Mac Pro.

2016-01-09 Compress PNG files with pngquant

A little trick if you want to save some space and/or bandwidth; compress your PNG files. Although PNG is a lossless format, you can compress them if the image in question doesn't have a lot of colors. This is often the case when creating screenshots.

In yesterday's screenshot of my terminal window, there's obviously not a whole lot of color. OS X by default saves the image with 24-bits colors, but the image looks perfectly fine with just 8 bits. After compression, file sizes looked as follows:

 -rw-r--r--@ 1 bartvk  staff    88K Jan  8 12:11 Image_2016-01-08_dd_progress.png
 -rw-r--r--  1 bartvk  staff    17K Jan  8 12:13 Image_2016-01-08_dd_progress-fs8.png

That's not so bad. The pngquant homepage is here and on OS X, it's easily installed with a quick

 $ brew install pngquant

2016-01-08 BSD dd progress

The commandline utilities that come installed with OS X originate from the BSD world. This means that option-wise, they're fairly spartan as opposed to GNU userland.

Today, I needed to create a bootable USB stick to install Ubuntu. It works fine and basically tells you to use dd to copy a bootable image to a USB stick.

Now if you have a slow USB stick, this can take ages and you're wondering if it's making progress. To see what it's doing, open another terminal and find the process number of dd:

  $ ps -ef | grep "dd if"
  501 76765   412   0 11:29AM ttys000    0:16.19 dd if=/Users/bartvk/Desktop/ubuntu-15.10-desktop-amd64.img of=/dev/disk1 bs=1m
  501 76786 75363   0 11:29AM ttys002    0:00.00 grep "dd if"

The second number of the first line is the process ID, in this case, 76765. Now run the following command:

 $ while true; do sudo kill -SIGINFO 76765; sleep 5; done

This will cause the running dd to print out statistics (roughly) every 5 seconds.

Image 2016-01-08 dd progress.png

2016-01-06 About an old Google Drive document

John Gordon writes about losing images in an old document on Google Drive:

A Google Doc I wrote in 2010 has lost its images

I encountered this issue right when the transition was made from the old editor to the new editor, which might have been around 2010 (not sure about that). Since then, I had gotten weary. Whenever I'd create an important document, I'd create a folder on Google Drive, put the images and other assets in there, then include them in the document. In other words, they would never be just in that document.

With Google Drive, I first thought that all files (Goole Docs, Sheets, etc.) would be synced locally, and that my local backup solution (Time Machine) would save a copy. But to make sure that happened, I tried to figure it out and create a matrix of what was viewable and editable offline: 2013-09-28 Criticism of Google Drive. In the end, I figured it was very hard to get a reliable backup of Google Docs, Sheets etc.

Back then, I hated the Microsoft Office format lock-in. Google seemed somewhat more open, and it was. But only in the sense that they had an export to the Open Office format. Their own format is very much closed, and has remained so for all this time. With Microsoft Office at least your local backup solution works fine, and the format is understood to a certain degree and imported by a number of software packages, some of them open source.

2015-12-11 Measuring startup time

The startup time of your app is determined by a bunch of things, but one that you can easily and directly influence, is the time spent in your AppDelegate's function didFinishLaunchingWithOptions. You may have put a number of things in there, and if you want to find out which step is taking up all that time, it's not always useful to fire up Instruments. A simple way is to just dump a bunch of logging statements.

Paste the following code at the top of the AppDelegate.swift file.

	var logTimer: CFTimeInterval = -1.0
	var logStep = 1
	func logAppProgressInit() {
		logTimer = CACurrentMediaTime()
	}
	func logAppProgress() {
		let stepTime = CACurrentMediaTime() - logTimer
		NSLog("Step %d took %.3f seconds", logStep, stepTime)
		logStep++
		logTimer = CACurrentMediaTime()
	}

Then paste the following line at the top of your didFinishLaunchingWithOptions:

	logAppProgressInit()

And the following line after each (functionally meaningful) line of code in didFinishLaunchingWithOptions:

	logAppProgress()

This code is just for a quick round of debugging, and must be removed afterwards. NSLog itself is slow and wasteful in this phase.

2015-12-08 gem install fastlane taking a long time

If you wanted to install fastlane and you issued the following command:

 $ sudo gem install fastlane

... you might have interrupted the command because it seemed to hang. In fact, it is working but may take an awfully long time due to the dependencies. Install with the verbose flag and see what's going on:

 $ sudo gem install -V fastlane
 HEAD https://rubygems.org/latest_specs.4.8.gz
 302 Moved Temporarily
 HEAD https://rubygems.global.ssl.fastly.net/latest_specs.4.8.gz
 200 OK
 ...
 ...
 ...
 Installing ri documentation for scan-0.3.2
 Parsing documentation for fastlane-1.46.1
 Parsing sources...
 100% [161/161]  lib/fastlane/version.rb
 Installing ri documentation for fastlane-1.46.1
 62 gems installed
 $

2015-11-26 Delay initialization of constant variable in Swift

Recent versions of Swift allow making a variable constant (with the let keyword), without directly initializing it.

This makes the following code possible:

	let identifier: String
	if flight == self.nextFlight {
		identifier = RosterListActivityCellReuseIdentifier.NextFlight.rawValue
	} else {
		identifier = RosterListActivityCellReuseIdentifier.Flight.rawValue
	}

Nice!

2015-11-20 Xcode tip

A nice tip for when you're pair programming and you're both sitting somewhat farther from the monitor than usual: big fonts in Xcode.

Go to Xcode -> Settings, then select the Fonts & Colors tab. There's a preset style available called "Presentation".

xcode presentation style.png

2015-11-06 Creating an OS X virtual machine

Automatically creating an OS X virtual machine is getting quite easy and automated nowadays.

If you haven't installed Homebrew, install it according to the command shown at http://brew.sh

Additionally, install Homebrew Cask:

  $ brew install caskroom/cask/brew-cask

Then install packer. If you haven't used packer before, it's a tool for automating the creation of Virtual Machine images.

  $ brew install packer

And via Cask, install vagrant and VirtualBox. Vagrant is a tool for managing development environments using Virtual Machines, i.e. starting, stopping et cetera.

  $ brew cask install vagrant
  $ brew cask install virtualbox

Now continue with rmoriz' instructions.

Check the results:

  $ vagrant box list
  macosx-10.10 (virtualbox, 0)

Initialize vagrant and start the VM:

  $ vagrant init macosx-10.10
  A `Vagrantfile` has been placed in this directory. You are now
  ready to `vagrant up` your first virtual environment! Please read
  the comments in the Vagrantfile as well as documentation on 
  `vagrantup.com` for more information on using Vagrant.

Now open the file Vagrantfile with an editor and search for the line:

  config.vm.box = "macosx-10.10"

Below, add the following lines:

  config.vm.provider "virtualbox" do |vb|
    config.vm.synced_folder ".", "/vagrant", type: "rsync"
  end

Then start the VM:

 $ vagrant up
 Bringing machine 'default' up with 'virtualbox' provider...

See if it's really running:

  $ vagrant ssh
  Last login: Fri Nov  6 04:17:05 2015
  osx-10_11:~ vagrant$ uname -a
  Darwin osx-10_11.vagrantup.com 15.0.0 Darwin Kernel Version 15.0.0: Sat Sep 19 15:53:46 PDT 2015; root:xnu-3247.10.11~1/RELEASE_X86_64 x86_64

Alternatively, start VirtualBox, select the VM on the left side and click the Show button.

VirtualBox.png

OSX Virtual Machine.png

Congratulations! Have a drink 🍻

2015-10-08 start of a day with NSDate

There's a nice new function since iOS 9, startOfDayForDate. Some examples:

To get the NSDate for this morning:

 Calendar.current.startOfDay(for: Date())

And to get the end of the previous day:

 let thisMorning = Calendar.current.startOfDay(for: Date())
 let yesterdayEvening = Calendar.current.date(byAdding: .second, value: -1, to: thisMorning)

And since iOS 8, there's isDateInToday. For example:

 if Calendar.current.isDateInToday(flight.departureDate) {
     ....
 }

2015-10-06 Testing NTP server under OS X

When you configure a different time server (NTP server) under OS X, the System Settings panel doesn't actually tell you that the address is valid or not. To check this, open a terminal window and use the ntpq command as follows:

 $ ntpq -p ntp1.example.com

The time server should list its sources and the command should display something roughly as follows:

      remote           refid      st t when poll reach   delay   offset  jitter
 ==============================================================================
 +kl10abav.xx.xxx 193.79.237.14    2 u 1030 1024  377    1.904    4.659   0.150
 -kl10ab9n.xx.xxx 193.79.237.14    2 u  924 1024  377    1.805    4.721   0.100
 *ntp1qvi.xxxxxxx .GPS.            1 u  921 1024  377   24.874   -1.048   0.330
 +ntp1tls.xxxxxxx .GPS.            1 u  705 1024  377   28.317   -1.072   0.211

If there isn't actually a time server running, then ntpq will report:

 nodename nor servname provided, or not known

2015-09-15 Xcode eating up diskspace in Library folder

Edit 2015-09-25: Don't remove simulators as suggested below. It may stop Xcode from running Playgrounds.

Xcode can eat up quite an amount of disk space. An easy fix may be to remove projects from the Projects window as well as remove Simulators. This only deletes data that can be recreated but as usual you're expected to keep a backup, like any professional does. Except Linus Torvalds, who is excused.

Before you do this, see the amount of disk space that's currently occupied by the Library folder with this Terminal command:

  $ du -sm Library/
  27966	Library/

Take the following steps:

  • Go to the Xcode menu bar, pick Window -> Projects. Remove old projects there.
  • Go to Window -> Devices. Remove simulators of old versions, or simulators which you're currently hardly using (for example because you're working on an iPhone-only app right now)

Now check the disk space again:

  $ du -sm Library/
  25227	Library/

Nice, more than 2.5 gigs cleaned up :)

2015-09-04 View all non-standard kernel extensions on OS X

If you want to list all the drivers (kernel extensions in OS X jargon) that didn't come pre-installed with OS X, open a terminal window and type the following command:

 $ kextstat| grep -v com.apple

You'll always get the following line, which are the column headers:

   Index Refs Address            Size       Wired      Name (Version) <Linked Against>

That means there are only Apple-provided drivers present on your system. As an example, I've got the Logitech OS X driver installed, plus I've got VirtualBox and two keyboard remapper tools installed:

 $ kextstat| grep -v com.apple
  Index Refs Address            Size       Wired      Name (Version) <Linked Against>
   70    0 0xffffff7f80df6000 0x46000    0x46000    com.Logitech.Control Center.HID Driver (3.9.1) <69 67 37 31 5 4 3>
  130    0 0xffffff7f82a9d000 0x28000    0x28000    org.pqrs.driver.Karabiner (10.8.0) <31 5 4 3 1>
  131    0 0xffffff7f82ac5000 0x6000     0x6000     org.pqrs.driver.Seil (11.3.0) <31 5 4 3 1>
  132    3 0xffffff7f82acb000 0x58000    0x58000    org.virtualbox.kext.VBoxDrv (4.3.24) <7 5 4 3 1>
  133    0 0xffffff7f82b23000 0x8000     0x8000     org.virtualbox.kext.VBoxUSB (4.3.24) <132 95 37 7 5 4 3 1>
  134    0 0xffffff7f82b2b000 0x5000     0x5000     org.virtualbox.kext.VBoxNetFlt (4.3.24) <132 7 5 4 3 1>
  135    0 0xffffff7f82b30000 0x6000     0x6000     org.virtualbox.kext.VBoxNetAdp (4.3.24) <132 5 4 1>

As an aside, if you want to remap some keys on your keyboard, be sure to check out the donation-supported utilities Karabiner and Seil.

2015-08-31 Centered popover on the iPad

Below is a nice centered popover on the iPad:

popover1.png

You can do this by configuring the segue in Interface Builder by:

  • (obviously) configuring as "Present As Popover"
  • Uncheck all directions
  • Set the anchor to the main view of the view controller

This is in Xcode 6.4:

xcode popover.png

It also nicely shifts up when the keyboard appears:

popover2.png

2015-08-27 Swift Switch must be exhaustive

In Swift, the switch/case statement must contain a default case, otherwise you'll get the following compiler error:

    Switch must be exhaustive, consider adding a default clause

We used to put a non-sensical log statement in there, but today, a colleague found out that you can use the break keyword.

Example:

	switch (self.type!.integerValue) {
	case CustomerTypeNormal.integerValue:
		name = "\(self.lastname?.lowercaseString.capitalizedString), \(self.firstname?.lowercaseString.capitalizedString)"
	case CustomerTypeCrew.integerValue:
		name =  "Crew"
	case CustomerTypeAnonymous.integerValue:
		name = "Anonymous"
	default:
		break
	}

2015-08-27 Open underlying SQLite database when using Core Data

During development of an app using Core Data, you often want to have a quick peep in the underlying SQLite store. Paste the following bit somewhere in your AppDelegate:

	// Log path (to manually open SQLite database) when in simulator. In the File -> Open dialog of
	// SQLPro for SQLite, type Shift-CMD-G and paste this path
	if TARGET_IPHONE_SIMULATOR == 1 {
		let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).last! as! String
		println("Documents folder: \(path)")
	}

You can use the commandline sqlite client, but I like SQLPro for SQLite. Start your app, copy the string from the console in Xcode, then go to SQLPro and click the Open button. In the dialogue, type Shift-Cmd-G and you can paste the path of the Document folder of your currently running app. You can then open the SQLite database and peek inside.

2015-08-21 lastPathComponent is unavailable

You may get something like the following error when you open your Swift code in Xcode 7 beta:

  'lastPathComponent' is unavailable: Use lastPathComponent on NSString instead.

Solution is to do something like this:

  let lastPathComponent = (fileName as NSString).lastPathComponent

2015-08-07 Swift Reflect example

Halfway last month, Erica Sadun wrote a blog entry where she used the reflect() function: Swift: Just because

She just gave the code and didn't comment any further so below, I've liberally sprinkled the code with comments:

	// Define a struct to represent a point
	struct Pt {
		let x: Int
		let y: Int
		init(_ x: Int, _ y: Int) {self.x = x; self.y = y}
		init(){self.init(0, 0)}
	}
	// Declare an instance of Pt
	let p = Pt(2, 3)
	// Any means: any reference (i.e. class) or value type (i.e. struct, number, etc)
	// So this function will take an item that can be basically anything in Swift
	// and look through the names of its members to see if the string matches.
	func valueForKey(item : Any, key: String) -> Any? {
		// The built-in Swift reflect() function is used by Xcode to aid in debugging
		let mirror = reflect(item)
		// It returns an array, walk through it
		for index in 0..<mirror.count {
			// Each item in the array has a member called 0 and 1
			// This assignment is only there for readability's sake
			let child = mirror[index]
			// The .0 member is a string and it holds the name of our struct member
			if key == child.0 {
				// The .1 member holds information about our struct member
				// If it's not an optional, return its value with the .value property
				return child.1.value
			}
		}
		return nil
	}
	// Try it out
	valueForKey(p, "x")

Instead of using the valueForKey() function, you can also open a Playground and try out a few things yourself:

	let mirror = reflect(p)
	println(mirror[0])
	println(mirror[0].0)
	println(mirror[0].1)
	println(mirror[0].1.value)

swift reflect.png

2015-08-07 Core Data sum example

I had some trouble last week finding a Swift example to sum across order line entities in Core Data. In the app I'm working on, there's the usual Order and OrderLine entities, and they're exactly what you think they are: clients can use the app to shop, and when they put stuff in their shopping cart, we slowly build an order with order lines.

Note 1: in the code below, order lines are called order items because "legacy".

Note 2: the predicate in the fifth line passes self as the order. That works in our case, because the function is located in the Order class. Adjust as necessary.

Without further ado:

	func orderItemsQuantity(managedObjectContext: NSManagedObjectContext) -> Int {
		let quantityExpression = NSExpressionDescription()
		quantityExpression.name = "totalQuantity"
		quantityExpression.expression = NSExpression(format: "@sum.quantity")
		quantityExpression.expressionResultType = .Integer32AttributeType
		
		let predicate = NSPredicate(format: "order == %@", self)
		
		let request = NSFetchRequest()
		request.entity = NSEntityDescription.entityForName("OrderItem", inManagedObjectContext: managedObjectContext)
		request.propertiesToGroupBy = ["order"]
		request.resultType = NSFetchRequestResultType.DictionaryResultType
		request.propertiesToFetch = [quantityExpression]
		request.predicate = predicate
		
		var results:[[String:AnyObject]]?
		var error: NSError? = nil
		var totalQuantity = 0
		if let results = managedObjectContext.executeFetchRequest(request, error: &error) as? [[String:AnyObject]] {
			if error != nil {
				fatalError("Unresolved error \(error), \(error!.userInfo)")
			}
			if let totalQuantityDict = results.first {
				if let totalQuantityResult: AnyObject = totalQuantityDict["totalQuantity"] {
					totalQuantity = totalQuantityResult as! Int
				}
			}
		}
		return totalQuantity
	}

2015-07-09 Short list of VPS providers

Here's my 2015 short list of VPS providers:

  • TransIP Reasonable price, good CPU performance, but I've had storage reliability problems in 2013 (for which they compensated me)
  • DigitalOcean Good price, don't like their CPU performance -- starting a Grails instance took 90 seconds, as opposed to 60 seconds on a TransIP node in the AMS2 data center
  • Linode No experience with them, but good reviews from people I trust
  • Scaleway No experience with them; ARM cores
  • Vultr No experience with them; not a lot of disk space for my price range

2015-07-05 withUnsafeBufferPointer

Update 2016-10-06; a very nice (and updated for Swift 3) blog about this: http://technology.meronapps.com/2016/09/27/swift-3-0-unsafe-world-2/

I couldn't find a nice example for Array<T>.withUnsafeBufferPointer, so here's one which you can paste right into a Playground:

	//: Playground - noun: a place where people can play
	import UIKit
	var buf = [UInt8](count: 10, repeatedValue: 0)
	// Fill buffer with A to J
	for (var i = 0; i < buf.count; i++) {
		buf[i] = 65 + UInt8(i) // 65 = A
	}
	// Calculate pointer to print contents
	buf.withUnsafeBufferPointer { (pbuf: UnsafeBufferPointer<UInt8>) -> UnsafePointer<UInt8> in
		for (var j = 0; j < pbuf.count; j++) {
			let p = pbuf.baseAddress
			print(Character(UnicodeScalar((p+j).memory)))
			println(String(format:" = 0x%X", (p+j).memory))
		}
		return nil
	}

Output:

	A = 0x41
	B = 0x42
	C = 0x43
	D = 0x44
	E = 0x45
	F = 0x46
	G = 0x47
	H = 0x48
	I = 0x49
	J = 0x4A

2015-06-21 Swift 2.0 error handling

Here is a nice article by Erica Sadun on the error handling in Swift 2.0.

I really like how a try statement can be forced, i.e. try! will tell the compiler you ignore the error and this will cause a crash when the exception trips during runtime. From my Java experience, I remember that there were plenty of exceptions that basically couldn't be recovered from; you'd log them and then exit. I'm glad Apple recognized this.

The keyword defer is also interesting, delaying execution until the end of the scope. Notably, multiple statements after each other have a sequence, and these statements are executed in reverse order. On the Debug podcast, Don Melton (former Safari product manager) commented that he thought it was taken from the D programming langauge. A buddy of mine had been developing in D in the past, so I asked him what he thought about it. He remarked that he hadn't actually ever used that particular keyword in D... I wonder if I'll find much use of it.

2015-06-19 QuickLook for mobileprovision files

Craig Hockenberry wrote a Quick Look plug-in for .mobileprovision files (i.e. Provisioning Profiles). Hugely useful, because you can just select a provisioning profile and press space in Finder to see which UDIDs (devices) are included.

To debug provisioning profiles, don't let Xcode manage them. Instead when you need to run your app on a new device, take the following steps:

  • In the Member Center, go to Certificates, Identifiers & Profiles
  • Add the device for this particular App ID
  • Generate either a development or a distribution profile and download it manually
  • Use the above Quick Look plugin to verify that the downloaded profile does indeed contain the UDID of the new device
  • Double-click the new .mobileprovisioning file
  • Check with the terminal that it is now located in ~/Library/MobileDevice/Provisioning Profiles

Now go to Xcode. Since version 6, you can view provisioning profiles by going to menu Xcode -> Preferences. In the Accounts tab, select your Apple ID on the left, select your team on the right and click View Details. The new profile should be there, or else you can click the refresh button. The expiration column should show the date as exactly one year later.

If you want the .ipa file, check out this answer on StackOverflow.

2015-06-16 AutoLayout in WWDC 2015

Be sure to check out these videos on AutoLayout:

There's some stuff in there that you may already know, but they also explain new stuff. My highlights:

  • First use stackviews, then for the rest, use constraints
  • Don't add and remove constraints, activate and deactivate them
  • For text, you might want to use firstBaseline and lastBaseline
  • Use "alignment rects" if you need to ignore extra stuff in your views (like shadows)

And in part 2:

  • Use the .identifier property on views when debugging, it's a string that'll show up in the log
  • Use method in the debugger called constraintsForAxis or something
  • There's a trace method in the debugger: po [self.view _autolayoutTrace] (can show ambiguous layouts); then on the LLDB prompt: po [object exerciseAmbiguityLayout]
  • While your app is running, check the menu Debug, option View Debugging

2015-06-12 Shortcut to lock OS X

There isn't any standard function to have a shortcut key to lock your screen in OS X. The best way to do this without involving the screensaver, is the second method that's mentioned in this link: How to Quickly Lock Your Screen in Mac OS X with Keyboard Shortcut

Do take special care: the command being called won't work if you just copy/paste it from that page. Use the following line instead:

  /System/Library/CoreServices/”Menu Extras”/User.menu/Contents/Resources/CGSession -suspend

Their CMS apparently replaces the standard minus-sign with a special en-dash. The above line is correct.

2015-06-06 Dial down the transmit power of your Airport Express

In my neighborhood, there are about 20 WiFi networks visible when I click the WiFi symbol on my MacBook's top menu. Needless to say, WiFi is wonky and unstable. The usual advice of a great WiFi deployment is: multiple access points (APs) with the same SSID, where you dial down the transmit power so you cover only the exact area for that particular access point.

It's thus a damn shame that the latest version of the AirPort Utility (version 6) does not allow you to influence the transmit power. The older versions did provide an option to do so, but it doesn't run anymore on the latest versions of OS X.

Corey J. Mahler automated a workaround, which is described and can be downloaded on his website. If you do, I encourage you to send a small donation to him. Scroll down on that page and click the green dollar sign. He's been keeping this solution in the air for multiple years now.

2015-05-09 discovering bitrot with MD5

Except for modern filesystems such as ZFS and btrfs, your files aren't protected against so-called "bitrot". If, for some reason, a file is corrupted then you won't discover this and your normal backups might not have the correct file anymore. This is because the corrupted file has been backed up and your archives don't go back in time far enough.

There are a number of ways to protect against this:

  • Use a modern file system, which may not be practical at this moment.
  • Calculate hashes for your archived files and check to see if they haven't been changed somehow, with md5deep.
  • Create parity files that can not only check for damaged files but can also repair them: Parchive.

I went with the second option. md5deep is easily installable via Homebrew:

 $ brew install md5deep

To generate a file with hashes:

 $ md5deep -r -l testdirectory > testdirectory.md5deep

Explanation: -r means recursive, -l will use relative paths. This will create a file called "testdirectory.md5deep", where all files are written with their path and their hash.

To print all changed (damaged) files:

 $ md5deep -r -l -x testdirectory.md5deep testdirectory

Regularly, check the hashes against a directory that shouldn't ever change. For example, your archive of last year's family pictures. If one of the pictures got corrupted, then you know you should restore it from backup.

If you don't mind using a bit of extra space (as well as taking a bit of additional CPU time), then you can use par2. It installs nicely via Homebrew as well:

 $ brew install par2

Example command inside the directory with the files of your choice:

 $ cd testdirectory
 $ par2create par2file *

To verify:

 $ cd testdirectory
 $ par2verify par2file.par2

As an indication, a directory containing 1836 megabytes of photos and videos resulted in a couple of par2 files that took 93 megs, so about 5% of extra storage is necessary.

To go through all subdirectories and create a par2 file, I use the following one-liner on MacOS:

 $ START=$(pwd);IFS=$'\n'; for i in $(find . -type d); do if [ "$i" == "." ]; then continue; fi ;cd "${i##./}"; pwd; par2create par2file *.*; cd "$START"; done

The *.* after par2create is there to prevent subfolders being included in the parameters to par2create. I.e. with *.* we are not globbing subfolders.

2015-05-09 DenyHosts no longer available on Debian 8.0 Jessie

When I was configuring a new Debian 8.0 ("Jessie") server, I noticed the very useful DenyHosts package is no longer available in the package repository. The package "sshguard" however, is available and according to my testing, works fine.

These packages block an IP address after a number of failed login attempts. This is very useful to counter brute-force attacks on your SSH server.

2015-05-05 Stop the new Photos app from opening automatically

Since the last Yosemite update, the new Photos app will open automatically when you hook up your iPhone or iPad.

To stop this from happening, open Image Capture (available in your Applications folder), connect your iOS device, select it in the upper left, and in the lower right corner, click the arrow up. Then change the dropdown to not do anything.

Stop Photos from opening.gif

2015-05-03 HealthKit error when uploading to the App Store

Currently I'm getting an error when uploading a new binary to the App Store:

Apps that use the entitlements [com.apple.developer.healthkit] must have a privacy policy URL for [English, Dutch]. If your app doesn’t use these entitlements, remove them from your app and upload a new binary.

I've been playing around with HealthKit, but to my knowledge I removed all traces from the project in Xcode. When I have a solution, I'll post it here.

Edit: StackOverflow to the rescue

2015-04-30 edit in vi from Xcode

Purely looking at the editing part, I find vi much more powerful than Xcode. If you want to do a quick edit in vi, then simply drag the file from the project navigator to an open terminal, and edit away!

quick vi from Xcode.gif

2015-03-13 Connecting to Oodrive via FTPS on OS X

For a client, I have to save files to Oodrive. They offer several methods for uploading, one of them being FTPS, i.e. FTP-over-SSL. It turns out it's quite a hassle when you use OS X.

Taking the following steps allowed me to connect.

First install Homebrew as usual, if you haven't yet installed it. Then edit the lftp recipe:

  $ brew edit lftp

Comment out the line that says "--with-openssl" and add a new line saying

  "--with-gnutls"

Then install this recipe as follows:

  $ brew install --build-from-source lftp

See if you were successful:

  $ lftp -v
  .....
  Libraries used: Readline 6.3, Expat 2.0.1, GnuTLS 3.3.13, libiconv 1.11, zlib 1.2.5

Note the final line, where it should say "GnuTLS".

Now add the appropriate settings; create the .lftprc in your home directory, and paste the following lines (courtesy of the Reliable Penguin blog):

  set ftps:initial-prot ""
  set ftp:ssl-force true
  set ftp:ssl-protect-data true

Then, create the .lftp directory and create a file named "bookmarks" with the
following line:

  oodrive ftps://username:password@easyftp.oodrive.com:990/your/directory/here

To test the result, start lftp on the commandline and type "open oodrive",
then type "ls".

If you see the error "Fatal error: Certificate verification: Not trusted", you may want to add the following line to the .lftp settings file:

  set ssl:verify-certificate no

2015-02-06 Swift enum raw values as an array

Here's a quick example of using Swift's map() function. Here it's used to put the raw values of an enum into an array. Paste this into a playground to see the result.

	import Cocoa
	enum ContentType : Int {
		case Unknown = 0
		case PAS = 1
		case PASGENERIC = 2
		case PASROUTE = 3
		case PASBUNDLE = 4
		case TOPIC = 5
		case NEWS = 6
	}
	let contentTypeArray = [ContentType.PAS, ContentType.PASROUTE, ContentType.TOPIC]
	let intArray = contentTypeArray.map({type in type.rawValue})
	println(intArray)

Even shorter:

	let intArray = contentTypeArray.map({$0.rawValue})

How it looks in a playground:

swift enum raw values as array playground.png

2015-02-05 Swift example of a batch update in Core Data

Here's a Swift example of doing a batch update in Core Data:

	let updateRequest = NSBatchUpdateRequest("some_entity_name")
	
	let predicateIsRead = NSPredicate(format: "isRead = %@", NSNumber(bool: true))
	updateRequest.predicate = predicateIsRead
	updateRequest.propertiesToUpdate = [
		ContentItemAttributes.isRead.rawValue: NSNumber(bool: false)
	]
	updateRequest.resultType = NSBatchUpdateRequestResultType.UpdatedObjectsCountResultType
		
	var error: NSError?
	let updateResult = objectContext.executeRequest(updateRequest, error: &error) as NSBatchUpdateResult?
	if let result = updateResult {
		let count = result.result as Int
		println("Updated \(count) rows")
	} else {
		println("Error during batch update: \(error?.localizedDescription)")
	}

Note that you'll need to update the objects in memory. The above code just prints the row count. If you have stuff displayed in the user interface, use this answer on Stack Overflow to adjust the above code.

2015-02-01 Layout of a tableviewcell depending upon device orientation

Last week, a refresh of the design of my client's app called for a layout that changed depending on the device orientation. We're using AutoLayout and didn't want to use any deprecated methods.

Problem was that it called for layout changes within a UITableViewCell. I thought of a way to do this with AutoLayout but the constraints would become quite complex. Instead, I took the following approach:

  • Create a custom UITableViewCell by subclassing it
  • To this subclass, add outlets for the constraints that you want to influence
  • In the view controller, detect whether we're in portrait or landscape, then update the constraints of the cells using the IBOutlet properties

Thus the UITableViewCell subclass looks as follows:

	class CustomTableCell : UITableViewCell {
		
		@IBOutlet weak var dateLabel: UILabel!
		@IBOutlet weak var nameLabel: UILabel!
		@IBOutlet weak var trailingConstraint: NSLayoutConstraint!
		
	}

Problem is: where can you set properties all the currently visible cells? The easiest way I could come up with, is in the cellForRowAtIndexPath() function. The view controller class has the following property:

	var size: CGSize = CGSizeZero

The viewWillTransitionToSize() function stores the new screen size and asks the UITableView to reload:

	override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
		super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
		
		self.size = size
		NSLog("viewWillTransitionToSize self.size = \(self.size)")
		self.tableView!.reloadData()
	}

And when dequeueing new cells, we set the constraint its constant:

	override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
		let cell = tableView.dequeueReusableCellWithIdentifier("TestCell") as CustomTableCell!
		
		NSLog("self.size=\(self.size)")
		
		cell.nameLabel.text = self.items[indexPath.row]
		if self.size.width > self.size.height { //Landscape
			NSLog("Landscape")
			cell.trailingConstraint.constant = 100
		} else { //Portrait
			NSLog("Portrait")
			cell.trailingConstraint.constant = 0
		}
		
		return cell
	}

Because viewWillTransitionToSize() is not called when the View Controller is run for the first time, I also added the following code to viewWillLayoutSubviews():

	override func viewWillLayoutSubviews() {
		super.viewWillLayoutSubviews()
	
		// Only run once, upon first display
		if self.size == CGSizeZero {
			self.size = self.tableView!.frame.size
			NSLog("viewWillLayoutSubviews self.size = \(self.size)")
		}
	}

Result:
layout uitableviewcell depending upon device orientation.gif

Download the example project: TestTableViewCellSizing.zip

Note: I created the above truly breathtaking animated gif by taking a screen recording with QuickTime Player, running the iOS Simulator, then converting the resulting .mov file with the following ffmpeg command:

  ffmpeg -i example.mov -r 15 example.gif

ffpmeg was installed via Homebrew.

2015-01-29 Update interface when app is opened

If you're developing in Swift and want to update your app's interface when it's reopened, put the following piece of code in the App Delegate:

	var appLastUsedTime = CFAbsoluteTimeGetCurrent()
	func applicationWillEnterForeground(application: UIApplication) {
		let timeSinceLastUsage = CFAbsoluteTimeGetCurrent() - self.appLastUsedTime
		log.verbose("App last used \(timeSinceLastUsage) seconds ago")
		
		//If it's been at least a day since the user accessed the app, send out a notification
		if timeSinceLastUsage > (24 * 60 * 60) {
			log.verbose("Posting notification \(MYFApplicationDidBecomeActiveAfterTimeoutNotification)")
			NSNotificationCenter.defaultCenter().postNotificationName(MYFApplicationDidBecomeActiveAfterTimeoutNotification, object: nil)
			self.appLastUsedTime = CFAbsoluteTimeGetCurrent()
		}
	}

Above the App Delegate, add the following global:

    let ApplicationDidBecomeActiveAfterTimeoutNotification = "ApplicationDidBecomeActiveAfterTimeoutNotification"

In the view controller, add the following code in viewWillAppear():

	NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateInterface",
			name: ApplicationDidBecomeActiveAfterTimeoutNotification, object: nil)

And the following code in viewDidDisappear()

	NSNotificationCenter.defaultCenter().removeObserver(self, name: ApplicationDidBecomeActiveAfterTimeoutNotification, object: nil)

Note that this notification doesn't trigger when activating the app for the first time. If you need that, add the following code to the view controller in question, in its viewDidAppear() function:

	if !self. didInitializeAtStartup {
		self.updateInterface()
		didInitializeAtStartup = true
	}

And the following class member variable:

    	private var didInitializeAtStartup = false

2015-01-20 button title truncated

There's a weird situation that when you add an image to a standard UIButton and add some space between the image and the label, the label text gets truncated:

moreinfo 1.png

The reason is that you used the image edge inset. This edge inset is not used to calculate the intrinsic button size.

For the correct steps, check my answer on StackOverflow.

2015-01-16 RestKit Error

Today I got a weird exception in the Xcode console of my iOS project:

  *** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
 reason: 'Cannot perform object mapping without a source object to map from'

Turns out I had a couple of missing commas in my JSON. In case your editor doesn't validate JSON, have a look at this online JSON editor.

When I fixed that, I got the same error again. This time, I opened the file in vi, which showed that there were a number of control characters (CTRL-P) in the JSON file:

control characters in json.png

When I removed those, the error disappeared.

2014-12-15 Shadows and rounded corners

The app I'm currently working on, requires a textfield with shadow and rounded corners. That doesn't work on a UITextField; you'll have to make it transparent, then add it to a plain UIView and style that.

I got curious and tried to add shadows and rounded corners to a number of elements in UIKit. This is the code:

        import UIKit
	class ViewController: UIViewController {
		
		@IBOutlet weak var contentView: UIView!
		@IBOutlet weak var button: UIButton!
		@IBOutlet weak var textfield: UITextField!
		@IBOutlet weak var textView: UITextView!
		@IBOutlet weak var datePicker: UIDatePicker!
		@IBOutlet weak var toolbar: UIToolbar!
		@IBOutlet weak var imageView: UIImageView!
		override func viewDidLoad() {
			super.viewDidLoad()
			self.addShadowAndCornerRadius(self.contentView)
			self.addShadowAndCornerRadius(self.textfield)
			self.addShadowAndCornerRadius(self.button)
			self.addShadowAndCornerRadius(self.textView)
			self.addShadowAndCornerRadius(self.datePicker)
			self.addShadowAndCornerRadius(self.toolbar)
			self.addShadowAndCornerRadius(self.imageView)
			
		}
		
		func addShadowAndCornerRadius(view: UIView) {
			let radius:CGFloat = 20.0
			view.layer.cornerRadius = radius
			view.layer.shadowColor = UIColor.darkGrayColor().CGColor
			view.layer.shadowPath = UIBezierPath(roundedRect: view.bounds,
			    cornerRadius: radius).CGPath
			view.layer.shadowOffset = CGSize(width: 0, height: 0)
			view.layer.shadowOpacity = 1.0
			view.layer.masksToBounds = true
			view.clipsToBounds = false
		}
	}

Below is the result in the simulator. Note that the light-blue empty element is a plain UIView.

shadow and rounded corners uikit.png

As you can see, adding shadow and rounded corners doesn't work on UIImageView, UITextField and UIToolbar. It does work on a plain UIView, UIButton, UITextView and UIDatePicker.

2014-11-06 From mov to animated gif

On OS X, the standard QuickTime Player application can record (a part of) your screen, or record the screen of an iOS device over the lightning cable.

You then get a .mov file. You can convert this into an animated gif of slightly smaller size with ffmpeg. You'll need to install homebrew first, and using the brew command, install ffmpeg as follows:

 $ brew install ffmpeg

Then open a terminal and change to the desktop folder or wherever you saved the movie file, and issue the following command:

 $ ffmpeg -i demo.mov -r 15 demo.gif

Drag the resulting demo.gif to Safari to view it. The quality is pretty bad, but hey, animated gifs are all the rage nowadays.

Edit 2020-08-27: it's possible to improve the resulting output with an additional option. Example:

 $ ffmpeg -i demo.mov -filter_complex "[0]split[vid][pal];[pal]palettegen[pal];[vid][pal]paletteuse" demo.gif

2014-10-09

On iOS 8, user permission is required for scheduling local notifications (of class UILocalNotification). Here's an Objective-C example.

To check whether you have permission:

  - (BOOL)appHasPermissionForLocalNotifications
  {
      UIUserNotificationSettings *settings = [[UIApplication sharedApplication] currentUserNotificationSettings];
      if(settings.types & (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge)) {
          NSLog(@"Permission present: 0x%ulX", settings.types);
          return YES;
      } else {
          NSLog(@"Permission not present: 0x%ulX", settings.types);
          return NO;
      }
  }

To request permission:

  [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];

2014-09-03 iStat Menus 5

Bjango released iStat Menus 5. I really like this app, but found a minor issue today. I've already sent the folks an e-mail, but for the sake of the internet, I'm documenting it here as well.

iStat Menus can show a graph of your CPU usage along with a nice quick lookup table with the most busy process. It seems it tries to do something helpful with the process name, but in the case of ClamXav, a free and open source virus scanner, it says: ScheduleHelper. The process name in Activity Monitor is more to the point: clamscan.

istat menus process comparison.png

2014-07-25

Today, I found out that on Debian Wheezy, when using XFCE on the desktop and accessed via VNC, the Tab key is disabled for some reason.

Solution is here.

2014-07-11 Xauthority doesn't exist

If you're connecting via SSH from OS X to Linux, you might get the following error:

  /opt/X11/bin/xauth:  file /Users/yourusername/.Xauthority does not exist

You can fix this by installing (or just running) XQuartz, available here.

2014-05-16 VNC on Debian Wheezy

I recently discovered that VNC on Debian Wheezy is a bit of a mess.

VNC can be used in two modes: to mirror the actively running instance of X (i.e. the display on :0), or to have X running headless, in any resolution ("geometry") you want, on :1, :2, etc.

The version that comes with Wheezy by default, is an old version of TightVNC server. It doesn't support the alpha channel, so modern desktops like KDE 4 will look pretty bad.

Development has stopped on TightVNC, and continues in the TigerVNC project. There are packages available here, but these are pretty old now: version 1.1, while development has passed 1.3.

In and of itself that wouldn't be a problem, except the 1.1 version has a horrible bug where moving a window to the left will result in display artifacts.

You may also have found TurboVNC. Development seems to move slower but they do provide Debian packages. Unfortunately, even for the latest version it seems it suffers from the same bug.

There's a couple of options:

  • Build TightVNC yourself from the source files in the TigerVNC SourceForge downloads section. I tried this, and it's pretty daunting. I gave up after an hour of futzing.
  • Get the generic, statically linked binaries from the TigerVNC SourceForge downloads section, and then just unpack these in your filesystem root. This dumps some binaries here and there, and your sysadmin (or you yourself) will definitely consider this abuse of the sudo command.
  • Use the old TightVNC 1.1 version, which is nicely packaged, and live with the obvious bugs. Note that the above mentioned bug doesn't seem to manifest itself when exporting the currently running display on :0. On our in-house built software however, there's another issue where portions of the screen sometimes don't refresh.

I'm currently looking at other solutions such as X2Go, which seems to provide decent packages.

Edit 2014-08-26: For the last two months or so, I started using the standard VNC again, and I've switched to XFCE as the desktop environment. This solves most problems, except you run into an easily fixable bug: the tab key doesn't work to auto-complete. This link
solves the problem.

2014-05-07 Debian Wheezy interface without an IP address

Today, I needed to configure a machine so it brought up its ethernet interfaces without an IP address.

This is useful if you use such an interface with plain ethernet packets. For example, network sniffing or bridging stuff, but also when you communicate with custom electronics that speak plain ethernet.

To configure Debian Wheezy to bring up these interfaces without assigning an IP address, add the following stanza to /etc/network/interfaces:

 auto eth1
 iface eth1 inet static
 address 0.0.0.0

You might also want to remove any IPv6 addresses from these interfaces. Stack Overflow told me to add the following line for each interface to /etc/network/interfaces:

 net.ipv6.conf.<interface>.disable_ipv6 = 1

2014-04-16 Python vi keybindings on OS X

If you're used to vi keybindings, and you use Python interactively, you're going to do a lot of cursing on OS X. This is because the readline library (responsible for keybindings and history) is GPL licensed and thus not distributed by Apple.

Here's a quick fix.

Add a file called .editrc with the following content:

 bind -v

Add a file called .pythonrc with the following lines:

 import rlcompleter
 import readline

Add a line to your .bashrc which makes Python run .pythonrc when it starts interactively:

 export PYTHONSTARTUP="$HOME/.pythonrc"

Start python from the commandline and voilà, enjoy history and vi keybindings and all that good stuff. Tested on 10.9.2 (Mountain Lion).

2014-01-29 tmux and X11

Recently I've been playing around with tmux . For all you neckbeards, that's the modern equivalent of GNU Screen.

On the surface, it looks pretty cool. Especially because iTerm2 can talk to tmux. From the iTerm2 wiki: When you run "tmux -CC", a new tmux session is created. An iTerm2 window opens and it acts like a normal iTerm2 window. The difference is that when iTerm2 quits or the ssh session is lost, tmux keeps running. You can return to the host you were ssh'ed into and run "tmux -CC attach" and the iTerm2 windows will reopen in the same state they were in before.

That's pretty useful!

A problem that I'm now bumping into, is that when I'm SSH'ing into the remote machine where tmux runs, I'm forwarding X11. This is useful because vim supports the X11 clipboard. That way, if I copy text from vim and paste it on my local machine, layout is retained.

Without the X11 clipboard, I'd have to simply "hard-copy" whatever is on the terminal screen. Vim's line numbers will be copied along. I also couldn't copy more than one screen.

In order to make this work, when ssh opens a session and starts the shell, it sets the environment variable $DISPLAY. Vim then reads it when it starts.

However when you detach tmux, log out and log in again via ssh, DISPLAY is set again to some other value. All shells in your tmux session now have an old/stale value in DISPLAY. Tmux has some commands for this, but it's not automatic. And if that would work, vim has to be restarted again as well.

It would be nice if you could simply configure the DISPLAY variable but this doesn't work -- the ssh server has an option called X11DisplayOffset but the client doesn't. So there's no way to configure this based on the user.

In summary: X11 forwarding and tmux don't work very well.

2013-12-20 Performance test of USB ethernet adapters on OS X

I've got two USB-based network adapters laying around here: the official Apple USB 2 Ethernet Adapter and this USB 3.0 ethernet adapter based on the Asix AX88179 chipset.

Then I connect over a VPN, and use scp to see how much time it takes to pull over a 100 MB test file. With the Apple adapter:

  $ scp testbox:~/tmp/a.log .
  a.log                    100%  100MB   3.0MB/s   00:33

And now with the USB 3 based adapter:

  $ scp testbox:~/tmp/a.log .
  a.log                    100%  100MB   2.9MB/s   00:34

Huh weird, no difference. I'll go and check where the bottleneck lays. VPN? scp?

Not using VPN gives the following result:

  $ scp gateway.company.tld:a.log .
  a.log                    100%  100MB  16.7MB/s   00:06

That's somewhat better. Testing without scp, but with wget instead shows roughly the same number:

  $ wget http://www.company.tld/some_video.mov
  100%[====================>] 126,805,954 16.0MB/s   in 7.6s

Now I test it on a different machine, within the company network:

 $ wget http://www.company.tld/some_video.mov
 100%[====================>] 126,805,954 64.1M/s   in 1.9s

That's better. So the bottleneck is apparently the physical (ethernet) connection I've got, which is VLAN'ed to provide a guest network. Oh well.

Long story short: better check this guy's test results. You should be able to get up to 110 MB/s.

2013-10-25 What is new in OS X Mavericks

I'm running a little comparison of the file system of a Mavericks install and a Mountain Lion install, to see what's new and what has disappeared.

So far, I found that Mavericks added the following drivers:

Mavericks also added Ruby 2.0 (up from 1.8 in Mountain Lion).

I found a number of apps in /System/Library/CoreServices that are new:

  • Install Command Line Developer Tools -- calling any of the binaries related to development (like /usr/bin/cc) will instead call this Xcode installation utility
  • Keychain Circle Notification -- App that allows you to grant other devices access to this Mac its keychain, used for iCloud syncing of passwords (for example those used in the browser)
  • Pass Viewer -- "partial support for Passbook passes, letting you view emailed passes on your Mac" -- source: MacWorld: Get to know OS X Mavericks: Apple Mail 7.0

There's also a number of new drivers in the /System/Library/Extensions folder:

  • AppleHSSPIHIDDriver, AppleHSSPISupport, AppleHWAccess: would seem to be for communication with keyboards, trackpads, and other buttons (like a hinge-button) that use an SPI bus
  • AppleIntelFramebufferAzul: obviously a display driver, but don't know what exactly
  • Driver for the new Intel HD5000 stuff
  • AppleIntelHSWVA driver, HSW is Haswell, but VA?
  • AppleIntelLpssDmac, AppleIntelLpssGspi, AppleIntelLpssSpiController: LPSS is Intel's Low Power SubSystem stuff, the Linux kernel uses this acronym when talking about Intel's Bay Trail architecture
  • AppleThunderboltIP, that's the driver for tunneling IP over Thunderbolt, duh :D I'm confused if this has anything to do with the Thunderbolt ethernet driver, I don't think so.
  • AppleTopCase, ?
  • GeForce Tesla driver, maybe a driver for the Mac Pro
  • IOReportFamily driver, ?
  • vecLib, see also [2]

In the /System/Library/Frameworks folder, there are a number of new libraries:

  • AVKit.framework
  • GameController.framework
  • MapKit.framework
  • MediaAccessibility.framework
  • MediaLibrary.framework
  • SpriteKit.framework

In /usr/bin, there are a bunch of new executables:

  • afida -- Audio File Image Distortion Analyzer
  • footprint -- gathers memory information about a process or set of processes
  • ipmitool -- utility for controlling IPMI-enabled devices
  • ippfind -- find internet printing protocol printers
  • json_pp -- a Perl-based utility for working with JSON
  • jvisualvm -- ?
  • lsappinfo -- Control and query CoreApplicationServices about the app state on the system
  • ncdestroy -- Destroy kernel NFS credentials
  • pluginkit -- manages the PlugInKit subsystem for the current user. It can query for plugin matches, as well as explicitly change the plugin database.
  • powermetrics -- gathers and display CPU usage statistics
  • timer_analyser.d -- ?
  • timerfires -- analyze timers as they fire
  • wdutil -- Wireless Diagnostics command line utility
  • zipdetails -- display the internal structure of zip files

In /usr/sbin:

  • ioalloccount -- Summarize IOKit memory usage.
  • purge -- force disk cache to be purged (flushed and emptied)
  • sndiskmove -- Xsan File System Disk Mover Utility
  • systemstats -- shows a selection of statistics about system power usage
  • taskpolicy -- execute a program with an altered I/O or scheduling policy. Not sure how this relates to the standard Unix commandline utility "nice"

2013-10-13 Extend drive space in your MacBook Air 2013

Recently I got a MacBook Air, but it wasn't within my budget to get the bigger SSD. The standard SSD is advertised as 128 GB. In practice, this means 112 GiB, so I went looking for additional ways to get some extra storage space.

First and most cheapest, you can get an external harddisk. On one hand, it's very cheap. On the other hand, you have to remember taking it with you, and it's another wire and another device on your desk.

As an extension to that, there's the various wireless external harddisks. This Lifehacker article lists a few: Add Wireless Storage to Phones, Tablets, and Laptops with Wi-Fi Drives.

There's a number of USB-sticks that are so small, you can just leave them in. Here's a nice list of them:

  • Sandisk Cruzer Fit, currently max. 32 G (warning: I've seen a forum post that these are very slow)
  • Kingston DataTraveler Micro, maximum 64 GB
  • Patriot Autobahn currently also 32 GB
  • Verbatim Store 'n' Stay, maximum 32 GB

Of these, I really like the Verbatim Store 'n' Stay because it seems the smallest of the bunch.

There's also a number of solutions that use the SD-card slot. They're basically adapters that take a micro-SD card. They're sized such that they sit flush with the outside of the MacBook.

  • The Nifty Minidrive which looks best and is most expensive, with different options for each MacBook (pro retina, air, pro)
  • The MiniDrive a knock-off with mixed reviews on Amazon
  • BaseQi, another knock-off sold via eBay

Of these three, I am now testing the last one, and it sits perfectly flush with the MacBook Air's chassis:
baseqi.gif
baseqi detail.gif

I've got it fitted with a 16 GB micro-SD and it seems to work fine. There is no loss of speed noticeable, when comparing with the micro-SD-to-standard-SD-adapter that came with this card.

2013-09-29 MacBook Air 2013 SD card speed test

Since the SD card slot on the 2013 MacBook Air is connected via the USB 3 bus (instead of USB 2), it shouldn't be limited anymore by USB2 speeds (i.e. theoretical maximum of 480 Mbit/s). The new limit is 5 Gb/s.

So I ran a little test of whatever I've got lying around. I used the Black Magic disk speed test, with a 1 GB test file.

Card Read speed Write speed Remarks
SanDisk 2 GB micro-SD card 17.4 MB/s 3.4 MB/s Came with my Android phone
Kingmax 16 GB micro-SD card21.0 MB/s 9.3 MB/s Bought to replace the small SD card above
Nikon-branded 2GB SD card11.3 MB/s 5.6 MB/s Came with my Nikon point-and-shoot camera
No-name 2GB SD 13.7 MB/s 5.2 MB/s

It's not a very interesting test; none of these is above the theoretical maximum of 60 MB/s for USB 2.

2013-09-28 Criticism of Google Drive

Update 2013-12-12

There have been some improvements to Google Sheets, including offline editing in Chrome.

Update 2013-11-16

The release notes mention that since Google Drive version 1.9, "you’ll be able to access your most recent Google documents on drive.google.com, even while offline. Simply open any Google document from your local Google Drive folder and Chrome will display the file for you to edit".

Note that this goes for documents, not for slides or sheets.

Original text follows

Here's a couple of problems I encountered with Google Drive.

The major problem I have is the offline feature of Google Drive. The obvious problem is that although Google drive seems to sync everything to your computer, that's not the case for the Google Apps documents (docs, drawings, sheets and slides). These are just links to their online version. Thus Google Drive is two things: it syncs normal files to between Google and local storage. And it lists all Google Apps documents. In that last instance, no real two-way syncing there. You can't create a local Google Apps document or something.

You can, however, use Google Chrome (not any other browser), and say that you want to be able to work offline. You can then go to http://drive.google.com/offline.

This feature doesn't come with Google Drive, nor is it enabled by it. It's a completely separate thing, and it will download your complete document storage again on your local drive, in your browser's cache. Being able to work offline means that you've got everything replicated twice on your local harddrive.

The second major problem is related to usage of this offline feature. I have tried to use the offline feature in three scenarios:
- Via a severely limited EDGE connection (i.e. not 3G or HSDPA, just plain old EDGE) with a maximum speed of 80 Kbit/s
- Via a crowded wireless network, which my MacBook kept dropping
- Via a wireless network in the train, which in its turn was routed through a 3G connection

In neither of these instances, Google Docs was able to keep up. Sometimes, the interface doesn't load. Sometimes, it thinks it loses connection because the speed is in the range of 8 to 40 Kbit/s. Often, it fails to save the last typed lines and says I should reload the document (which then loses the last sentences entered). All the time, it displays errors about not being able to connect, a vague error about server error, or about reconnecting.

All in all, it works when you have a steady net connection. But the technology isn't there yet.

QuickOffice

Small update. Google introduced QuickOffice back into the App Store. It allows you to edit Microsoft Office documents. This sounds great in principle. There's a couple of things I find weird with this. Firstly, this app is separate from the Google Drive app and I can't seem to find a good reason why. Secondly, the iPad now offers something that Google Docs (in your browser) does not offer. Thirdly, the QuickOffice app has a weird 'file cache', a storage area on the iPad itself, where you can create and edit documents. You can mail and print them, but you can't copy them to Google Drive.

Device & software Docs Sheets Slides Drawings MS Office Editable offline
iPad Google Drive app Edit Edit Edit View View Per document
iPad QuickOffice app View View View View Edit No
Browser Edit Edit Edit Edit View Only in Chrome
PC with MS Office --------Edit Always

In my opinion, this is too complicated.

Conclusion

In my opinion, it would be much better when you could simply say: "In your browser as well as on your iPad, Google Drive can edit all sorts of files in your browser, including MS Office".

We're not there yet. And I wonder if we'll ever see a full-fledged offline editing solution for Google Docs. After all, why would they? Then Google has to open their file format, just like Microsoft was forced by law to do with the Office file formats. Now you can say that Google is a lot more open, because you can easily download the documents in other formats. But you can't really -- they're not natively editable anymore after exporting. You could export, then import them again, but that is a bit strange for a document that's yours, and you'll lose lots of markup in the process.

I think Google sees the complex and weird situation that now exists, and I am very curious about the future.

2013-09-26 Speed comparison of different ciphers on an SSH tunnel

Today I got curious what the speed differences are when you use a different cipher for an SSH tunnel.

First, create a 100 MB testfile on the remote box:

 you@remote $ cd /var/www
 you@remote $ sudo dd if=/dev/urandom of=./testfile100mb.bin bs=1M count=100

Then set up a tunnel from your local desktop to your remote webserver, and use wget to test it:

In one terminal (using the default cipher here):

 you@local $ ssh -L8080:localhost:80 remote

In the other:

 you@local $ wget http://localhost:8080/testfile100mb.bin
 (3.87 MB/s) - ‘testfile100mb.bin’ saved

Now exit the tunnel and retry with another cipher:

 you@local $ ssh -c arcfour256 -L8080:localhost:80 remote
 you@local $ wget http://localhost:8080/testfile100mb.bin
 (3.87 MB/s) - ‘testfile100mb.bin.1’ saved

Hmmm, that's not much. And not encrypting anything doesn't work:

 you@local $ ssh -c none -L8080:localhost:80 remote
 No valid ciphers for protocol version 2 given, using defaults.

Then we'll just have to trust the developers:
http://blog.famzah.net/2010/06/11/openssh-ciphers-performance-benchmark/

2013-08-26 Testing real capacity of an USB stick

I've read about fake oversized thumb drives / USB sticks, which report a much bigger size than they actually are.

To test the real capacity of an USB stick under OS X, we'll create a test file and write it to the USB stick a number of times. Then we'll verify what has been written with a couple of terminal commands.

This is a pretty brute-force test and I'm sure it can be done much smarter. I'll give an idea on speeds, and I assume USB 2.0, and also assume a maximum write speed of 15 MB/s, and maximum read speed of 30 MB/s. Furthermore, we're working with 1 GB equalling 1 thousand million bytes, because that's what they use on the packaging.

First, we format the disk. Start Disk Utility and erase it. This is to remove any files in the Trash.

disk utility format usb stick.png

Notice the capacity in the lower right. In this case, it's 16 GB. We'll need that later.

Now open a terminal window. See under which directory the stick is mounted:

 $ ls -l /Volumes
 total 24
 lrwxr-xr-x  1 root    admin     1 Aug 20 07:12 Macintosh HD -> /
 drwxrwxrwx@ 1 root  staff  8192 Aug 26 12:07 STORAGE

In the case above, the USB stick is called 'storage'. Go there.

 $ cd /Volumes/STORAGE

First create a 100 megabyte test file on the USB stick like so (optimally, this takes 1 minute and 10 seconds):

 $ dd if=/dev/urandom of=test.bin bs=1000000 count=100
 100+0 records in
 100+0 records out
 100000000 bytes transferred in 9.029708 secs (11074555 bytes/sec)

Now copy the test file a number of times, as much as the number of gigs you expect on the USB stick, in this example, 16 gigabytes. To write a gigabyte, we need to write our 100 megabyte testfile ten times. 16 gigabyte times 10 is 160. A gigabyte takes about 1 minute and 10 seconds to write optimally. The OS might be able to completely cache the source test.bin file in memory, which saves on reading time.

 $ for i in $(seq 1 160); do echo "Writing file $i"; cp test.bin test$i.bin; done

Probably, the last file will fail because although the stick might be 16 gigabytes, the filesystem also uses up some space.

Display the checksum of the first file. This can take half a minute.

 $ md5 test.bin
 MD5 (test.bin) = 0d3c4fe338109b09b61fcea3e11b0e4b

Now test the checksums of all files, excluding the correct results. My 16 GB stick took 8 minutes.

 $ md5 *.bin | grep -v 0d3c4fe338109b09b61fcea3e11b0e4b

If you're testing a big SSD drive or something similar, better use xargs (because simply using an asterisk might be too much for the shell):

 $ ls *.bin | xargs md5 | grep -v 0d3c4fe338109b09b61fcea3e11b0e4b

If something went wrong, then you should either see the OS give errors, or some files should give a different MD5 hash. As mentioned above, you can probably ignore the last file, which may not be fully written to disk.

2013-08-12 Page up and page down with a Logitech Marble on Linux

On Debian 7.x (Wheezy), I'm using the Logitech Trackman Marble. Mine has product number P/N 810-000767, a fantastic and long-lasting device. It has two small up/down buttons above the normal left/right buttons

I wanted these working under Linux, and give them the PageUp/PageDown function. I went the imwheel route, which involves the following steps:

1) Install imwheel with:

 $ sudo apt-get install imwheel

2) Edit the file /etc/X11/imwheel/startup.conf to enable imwheel when you log into KDE. Find the line that starts with IMWHEEL_START, and set it to 1.

3) Edit the file /etc/X11/imwheel/imwheelrc and at the bottom, add the following lines:

 ".*"
 , Thumb1, Page_Down
 , Thumb2, Page_Up

4) Logout and log into KDE again

http://www.logitech.com/assets/14756/14756.png
The Logitech Trackman Marble trackball.

2013-08-12 Measure the AC gain part 2

I've worked some more on a Python script that measures the gain (transfer) of the AC bias of the SQUIDs. To summarize my previous posting on this subject, this measurement assumes that we have put the array SQUID in flux-locked-loop. This also implies that we're dealing with a dual-SQUID configuration

Dual SQUID configuration.png

More on this procedure later.

2013-06-15 Automatic updating of Google Drive

On my bog standard MacBook Pro, it seems that there's an update problem with Google Drive. It fails to automatically update itself. Perhaps it has to do with the fact that I'm running as a standard user, not the Administrator that OS X supplies by default.

I found others who share this problem, and decided to write a little shell script that checks the current version against the new version. Run it once a week and see whether you need to update or not.

  #!/bin/sh
  URL="http://dl-ssl.google.com/drive/installgoogledrive.dmg"
  DEST=/tmp/installgoogledrive.dmg
  DESTMOUNT="Install Google Drive"
  wget --quiet --no-check-certificate -O "$DEST" "$URL"
  hdiutil mount -quiet "$DEST"
  PREV_VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "/Applications/Google Drive.app/Contents/Info.plist")
  [ $? -ne 0 ] && exit 1
  VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "/Volumes/$DESTMOUNT/Google Drive.app/Contents/Info.plist")
  [ $? -ne 0 ] && exit 1
  #echo "old version: [$PREV_VERSION], new version: [$VERSION]"
  if [ "$PREV_VERSION" != "$VERSION" ]; then
      echo "Please install update!"
  else
      echo "Google Drive is up to date."
      hdiutil unmount -quiet "/Volumes/$DESTMOUNT"
      rm "$DEST"
  fi

Copy the script into a file called "checkGoogleDriveVersion.sh" and put it in your Documents folder, for example. Drop to the commandline and make it executable:

  $ cd Documents
  $ chmod ugo+x checkGoogleDriveVersion.sh

Give it a test drive:

  $ ./checkGoogleDriveVersion.sh

If it finds that you need to update, switch to finder and see that there's an opened DMG. Stop Google Drive, install the update and have fun.

2013-05-02 Test of current version of SAFARI scripts

Today, we ran a little test of the SAFARI demux/fee-scripts. It's the second time this week, and I'm quite happy how it all turns out.

We're finding some issues, but it's developing into a base from which we build a complete system. First we tested the menu for the testpulse generator.

This is an important part, because the testpulse generator can simulate a detector (with a pulse, it's like a particle hit the bolometer). It's fed into the feedback. Besides pulses, we can also generate a square wave. This allows the user to inject high frequencies into the system, which is apparently used to assess its robustness. For now, I'm just taking note of that.

When that appeared to work, we continued with the pixel menu. This is a part of the script that allows you to put a carrier wave routed to a certain pixel. You can change characteristics such as amplitude and frequency. We found some bugs there, but nothing serious.

We then turned on pixel 1, and routed the testpulse generator to that pixel, configured to generate pulses.

This resulted in the following picture on the scope:
demux test script scope.jpg

We then used the "demux test" part of the script, and configured it to grab data, triggered on the testpulse generator. That resulted in the following plot:
demux test script plot.jpg

The plot option has a number of options. The above plot is a "normal" plot (in the time domain). If you would zoom in, you'd see a sinus. The plot shows 400,000 samples or so, thus we'd just see a dense area plotted. The first part of the plot is pre-trigger data. Then the test signal generator pulses, and the amplitude drops. The logic behind that, is that our sensor behaves like a resistor. Upon particle impact, the resistance raises, thus the amplitude of our biasing signal is lowered. And that's why you see the "trough" in the plot above.

It's also possible to get a spectrum (i.e. plot in the frequency domain). We played with that too, and found some issues there.

Finally, we used the Demux Test menu choice, and configured it to grab data from ADC 1, which measures the error signal. This is the difference between the feedback and the input of the SQUID. The error signal is the cause of overdriving the SQUID. Because it's so sensitive to this, we want to eliminate the error signal as much as possible -- thus it's important that users can measure and plot this signal.

All in all, a nice result to head into the weekend.

2013-04-26 Asynchronous data from the SAFARI Demux board

Previously, I described how we measure the transfer of the AC bias.

To recap, we assume the user has characterized the DC biasing of the SQUID(s). When we measure the transfer of a single Array-SQUID or a dual-SQUID (i.e. pre-SQUID plus array-SQUID), we need to take the following steps:

  • Set a DC bias on the SQUID(s)
  • Set an AC bias current on the feedback coil
  • Measure back the AC output voltage
  • Increase the DC bias, and repeat the steps until we have covered a specified range (for example, from -5uA to 5uA in 20 steps)

Why do we put a test AC signal on the feedback coil, instead of sending it straight through a pixel on the sensor? Because each pixel has a hardwired LC filter, we could say. But that feedback coil is there, because the SQUIDs hardly have a dynamic range.

Let me put this in other words, because it's extremely important to understand the whole setup.

The SQUID is a very sensitive magnetometer. Thus the smallest change of flux will cause it to change its output. The output looks like a sine wave, thus it has an extremely small dynamic range (namely, the flank of a sinus). This means that we need a feedback loop that holds the SQUID locked to an output range. Because we have more than one pixel, we would run the SQUID out of its dynamic range. Thus the feedback loop ensures that, within its dynamic range, the SQUID can add all the pixel carrier waves.

To make this measurement, we use a set of registers that allow us to configure the Demux board to start spewing data over the second optical network link. It's very flexible and can be viewed as a number of digital data taps located on several points of the Demux board. You can set the data source, the amount of data, the trigger source, and finally whether you want a specified number of data blocks or just continuous data.

The data source can be configured to the ADC, the DACs, or the demodulated output of pixel 1 to 16. The ADC is of course what we need currently, but you can also read out the DACs. This is useful to check that the FPGA algorithms (more on that later) are correctly working. Finally to read the demodulated output of pixels is hugely useful, because (1) (see below).

The trigger source is worth a different topic, which involves explaining what the sensor actually measures (X-rays or infrared signals). The options currently are: testpulse generator (2), level triggering of selected pixels, both of these options, or just auto (get all the data).

For our purpose, we need to configure it as following:

  • Set the data source to read out the ADC that's on the Demux board (there's two ADCs for historical reasons, but we only use the first one).
  • Set the amount of data. We need to experiment with the amount that we actually need to request.
  • As the trigger source, we currently set it so data is always, and immediately, returned.

Footnotes:
(1) Demodulation saves us bandwidth as well as processing on the PC.
(2) The testpulse generator is used to generate fake detector signals, to test the complete signal chain. You can test demodulation and/or test the electronical system. It generates the ideal detector signal. You can send the testpulse generator signal to the feedback coil of the SQUID and measure the PPM drift of the signal chain.

2013-04-15 State machine in Objective-C

Here's my version of a state machine in Objective-C, useful for your iOS or Mac OS X projects. There are many like it, but this one is mine. I've modeled an alarm clock here.

What I like about it: it's pretty small and light on the objects. The state transitions are the only thing that's done with an object, and even these could be replaced with a struct, but that causes problems with ARC (which doesn't like function pointers in structs).

The code below is WTFPL-licensed. Just so you know.

The header file:

    /* Enums that we need throughout this class to maintain state */
    enum WSAlarmState {WSAlarmStateInactive, 
        WSAlarmStateActive, WSAlarmStatePlaying};
    enum WSAlarmAction {WSAlarmActionStart, WSAlarmActionPlay,
        WSAlarmActionSnooze, WSAlarmActionStop};
    /* Describes how one state moves to the other */
    @interface WSAlarmStateTransition : NSObject
    @property enum WSAlarmState srcState;
    @property enum WSAlarmAction result;
    @property enum WSAlarmState dstState;
    @end
    /* Singleton that maintains state for an alarm  */
    @interface WSAlarm : WSNotification
    @property enum WSAlarmState currentState;
    @end

The header file contains the enums for the states and the actions. Note that these actions are both used as a return value and as an input value.

The implementation file starts with the init method, which sets up the state transition table. Basically, this table says: given a state and a resulting action, what is the next state?

Furthermore, it contains a function that does all transitions, a function that looks up the next state, and the state methods.

    #import "WSAlarm.h"
    @implementation WSAlarmStateTransition
    - (id)init:(enum WSAlarmState)srcState :(enum WSAlarmAction)action :(enum WSAlarmState)dstState
    {
        if (self = [super init]) {
            // Do initialization here
            DLog(@"init");
            self.srcState = srcState;
            self.result = action;
            self.dstState = dstState;
        }
        return self;
    }
    @end
    #pragma mark -
    #pragma mark WSAlarm class
    @implementation WSAlarm {
        NSArray *stateMethods;
        NSArray *stateTransitions;
    }
    - (id)init
    {
        if (self = [super init]) {
            // Do initialization here
            DLog(@"init");
            self.currentState = WSAlarmStateInactive;
            
            /* This array and enum WSAlarmStates must be in sync! */
            stateMethods = @[
              [NSValue valueWithPointer:@selector(noAlarmActiveState)],
              [NSValue valueWithPointer:@selector(alarmActiveState)],
              [NSValue valueWithPointer:@selector(playingAlarm)]
            ];
            stateTransitions = @[
               [[WSAlarmStateTransition alloc] init:WSAlarmStateInactive
                                                   :WSAlarmActionStart
                                                   :WSAlarmStateActive],
               [[WSAlarmStateTransition alloc] init:WSAlarmStateActive
                                                   :WSAlarmActionPlay
                                                   :WSAlarmStatePlaying],
               [[WSAlarmStateTransition alloc] init:WSAlarmStateActive
                                                   :WSAlarmActionStop
                                                   :WSAlarmStateInactive],
               [[WSAlarmStateTransition alloc] init:WSAlarmStateActive
                                                   :WSAlarmActionForegrounded
                                                   :WSAlarmStatePlayedInBackground],
               [[WSAlarmStateTransition alloc] init:WSAlarmStatePlaying
                                                   :WSAlarmActionStart
                                                   :WSAlarmStateActive],
               [[WSAlarmStateTransition alloc] init:WSAlarmStatePlaying
                                                   :WSAlarmActionStop
                                                   :WSAlarmStateInactive],
               [[WSAlarmStateTransition alloc] init:WSAlarmStatePlayedInBackground
                                                   :WSAlarmActionStart
                                                   :WSAlarmStateActive],
               [[WSAlarmStateTransition alloc] init:WSAlarmStatePlayedInBackground
                                                   :WSAlarmActionStop
                                                   :WSAlarmStateInactive],
            
        }
        return self;
    }
    - (void)registerDefaults
    {
        DLog(@"entry");
    }
    #pragma mark -
    #pragma mark Convenience methods
    - (void)start:(NSDate*)notifDate
    {
        self.notificationDate = notifDate;
        [self transitionToState:WSAlarmStateActive withAction:WSAlarmActionStart];
    }
    - (void)stop
    {
        [self transitionToState:WSAlarmStateInactive withAction:WSAlarmActionStop];
    }
    - (void)play
    {
        [self transitionToState:WSAlarmStatePlaying withAction:WSAlarmActionPlay];
    }
    - (void)snooze
    {
        [self transitionToState:WSAlarmStateActive withAction:WSAlarmActionSnooze];
    }
    #pragma mark -
    #pragma mark State machine
    // Walk through table of transitions, and return new state
    - (enum WSAlarmState)lookupTransitionForState:(enum WSAlarmState)state
        withResult:(enum WSAlarmAction)action
    {
        enum WSAlarmState newState = -1;
        
        for(WSAlarmStateTransition *t in stateTransitions) {
            if(t.srcState == state && t.result == action) {
                // We found the new state.
                newState = t.dstState;
                break;
            }
        }
        
        if(newState == -1) {
            NSString *msg = [NSString stringWithFormat:
                @"Can't transition from state %@ with return code %@",
                alarmStateString[state], alarmActionString[action]];
            @throw [NSException exceptionWithName:@"TransitionException"
                                           reason:msg
                                         userInfo:nil];
        }
        
        return newState;
    }
    - (void)transitionToState:(enum WSAlarmState)newState withAction:(enum WSAlarmAction)result
    {
        NSValue *stateMethodValue = (NSValue*) stateMethods[newState];
        SEL stateMethod = [stateMethodValue pointerValue];
        
        // We need these because otherwise we get a warning
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        NSNumber *param = [NSNumber numberWithInt:result];
        enum WSAlarmAction nextAction = [self performSelector:stateMethod withObject:param];
    #pragma clang diagnostic pop
        
        self.currentState = [self lookupTransitionForState:self.currentState withResult:nextAction];
    }
    #pragma mark -
    #pragma mark States
    - (enum WSAlarmAction)noAlarmActiveState:(enum WSAlarmAction)action
    {
        // Some code to stop the alarm
        return WSAlarmActionStop;
    }
    - (enum WSAlarmAction)alarmActiveState:(enum WSAlarmAction)action
    {
        if(action == WSAlarmActionSnooze) {
            // User tapped "snooze", stop the sound
        } else if(action == WSAlarmActionStart) {
            // No alarm active, user starts alarm
        } else {
            // We reached state alarm active with a weird action
        }
        return WSAlarmActionStart;
    }
    - (enum WSAlarmAction)playingAlarmState:(enum WSAlarmAction)result
    {
        // Some code to play a sound
        return WSAlarmActionPlay;
    }
    @end

2013-04-11 measure the AC gain

A little intro

Another routine in the system is the AC gain curve measurement.

This routine is different from the previous one, 2013-04-09 Characterizing the transfer of a voltage biased SQUID. In that routine, we just looked at the DC part of the amplifier chain.

In this measurement, we assume those measurements were successful, and that the amplifier chain was set according to optimal parameters.

And since our instrument basically uses AC biased TESes (as explained here), we want to measure the transfer of the AC bias, after the SQUID has been configured optimally. This is very interesting, because that means you see the instrument as a whole, instead of characterizing a small part.

The sensor:
safari sensor.jpg

The measurement

So what we're going to do, is sweep the AC bias and then measure back the result.

To do this, we have to send the FPGA the appropriate commands. First, some schematics:
Squid-AC-Bias-Gain.png

On the Demux board, there are two DACs that can drive the SQUID in the cryostat. There is also an ADC to measure back the result. The FEE board is in between here, but for now we can ignore it. The cryostat is where the above sensor is located. It has an array of TESes, each with its own LC filter.

If we want to feed in an AC bias, we don't want to use DAC2, because we don't want it filtered through the LC filters. Instead, the FPGA exposes a register where we can switch the input line of both DACs, so you can feed the AC bias signal to DAC1. Note that this AC bias signal is still digitized, otherwise we couldn't feed it into a DAC :-)

Settings

The user needs to be able to adjust the AC bias signal. The FPGA exposes a number of parameters for this, but we just need the frequency and amplitude, it's a really small signal: 2 MHz at 1 uA. Both are adjustable, and I'll add a setting for 1 and 5 MHz as well.

The data

We retrieve the data asynchronously. This is rather different from our usual way of getting data; i.e. normally we'd just polling a particular register.

In this case, the hardware has a special function called the "science switch". A possible analogy could be that this science switch acts as a number of water faucets, where you'd just let the raw data stream out of the board, from specific points. Since water and electronics don't mix much, it's actually a number of registers. I'll write more on that later.

2013-04-09 Characterizing the transfer of a voltage biased SQUID

Currently, I'm finishing a software routine to characterize the transfer (see below) of a voltage based SQUID. Simplified, our electronics could schematically be shown as follows:

presquid.jpg

Firstly, about this schema.

On the left, you see a single SQUID. This is the pre-SQUID. In the middle, you see an array of SQUIDs. We functionally treat that last one as a single squid, calling it "the" array-SQUID. These two types of SQUIDs are biased differently; the pre-SQUID is voltage-biased, and the array-SQUID is biased with a current.

You can view this combination as one "dual-stage SQUID".

So what we do, is: we set a current over the shunt on the left. This will put a voltage on the pre-SQUID and a magnetic field will form on the inductor next to the array-SQUID. We'll read out an output voltage on the right.

Now normally, the amount of voltage we put on the pre-SQUID would overdrive the array-SQUID. Thus, we put it in flux-locked loop. This is a mode where the array-SQUID simply passes current that results from the changes in flux.

Because the array-SQUID is in flux-locked-loop (FLL), we can measure the output current of the pre-SQUID without a dynamic range limitation (+/- 500 uA). This flux-locked-loop is nothing more than a feedback circuit which forces the bias of the array-SQUID down to zero.

Note that this is all DC-biased. The SQUIDs are part of the amplifier chain, and the whole amplifier chain is DC-biased. After we've characterized the amplifier chain, our primary purpose is to read out the sensor, consisting of a TES array that's AC-biased, see also 2012-07-02 Minimizing amplifier chain offset.

Secondly, about the routine itself.

I said "characterize the transfer". The transfer means: what is the effect of the input on the output. If you draw this function, you'd ideally see a sinus. We want to know in which voltage range our pre-SQUID behaves the best (i.e. where it has the highest dynamic range. In other words, you want to pick the voltage that's right in the middle on a flank. Because that way, with a little bit of input, you get a nice stretch of usable output.

Compare it to a radio that can receive a lot of stations.

2013-03-27 warning Setting locale failed

I was getting the error from all kinds of Linux programs about "setting locale failed". For example, running Perl gave me the following output:

 user@machine:~$ perl --version
 perl: warning: Setting locale failed.
 perl: warning: Please check that your locale settings:
 	LANGUAGE = (unset),
 	LC_ALL = (unset),
 	LC_CTYPE = "UTF-8",
 	LANG = "en_US.UTF-8"
     are supported and installed on your system.
 perl: warning: Falling back to the standard locale ("C").

The easy solution

If you're running Debian Linux or some derivative, you may also see the following warning when you run some command:

 Warning: locale not supported by C library, locale unchanged

The solution is to install all locales:

 $ sudo apt-get install locales-all

Continuing

If the above didn't help, or isn't applicable, continue.

What it comes down to, is that I was using SSH from my MacBook (running OS X Mountain Lion 10.8.3) to log into my Debian Linux server (running Squeeze).

Apparently, after logging remotely into Linux, the SSH client sets the LC_CTYPE environment variable to the same value as OS X. This conflicts with the LANG environment variable that Debian sets by default. That's because under Linux, the LANG is an overarcing variable, from which others, like LC_CTYPE, can be derived. That wouldn't be a problem, except the LC_CTYPE variable its contents are differently formatted under OS X and Linux.

The warning says that it's "falling back to the standard locale". That means nothing will be translated, and only ASCII will be used when printing characters. No unicode support, I guess?

The irritating warning can be removed with a small change on the Linux side, by unsetting the LC_CTYPE in your .bashrc. Add the following line:

 unset LC_CTYPE

However, this gave me problems when using SVN from the Macbook; whenever I'd do some remote action, I'd get something like the following:

 user@macbook:trunk$ svn log --limit 1
 svnserve: warning: cannot set LC_CTYPE locale
 svnserve: warning: environment variable LC_CTYPE is UTF-8
 svnserve: warning: please check that your locale name is correct 

Thus alternatively, you can fix this with a similar change on the OS X side, by adding the following two lines to .bash_profile:

 unset LC_CTYPE
 export LANG="en_US.UTF-8"

Instead of unsetting LC_CTYPE, you can also go to the Terminal app its preferences, open the Settings tab, select your profile on the left side, go to the Advanced tab, and uncheck "Set locale variables on startup".

2013-03-13 Minimize cellular usage on iPhone

There's a number of guides on the internet that talk about minimizing cellular usage. Often, they go into generalities, like "don't watch streaming video". Well, duh. I'm not going to offer general tips, but instead list a few settings for the iOS and some popular apps so your iPhone (or iPad) prefers WiFi whenever possible.

In the Settings app:

  • iTunes & App Stores -> Use Cellular Data -> OFF
  • iCloud -> (several settings) -> OFF
  • Mail, Contacts, Calendar -> Fetch New Data -> Manually
  • Music -> iTunes Match -> OFF
  • Music -> Lyrics & Podcast Info -> OFF
  • iBooks -> Sync Bookmarks -> OFF

Separate apps:

  • Podcasts: do not use this app; it has a setting to only use WiFi, but seems to have a bug in this feature
  • Instapaper: tap Settings -> Advanced -> Update -> On Wi-Fi Only
  • Evernote: tap Evernote icon -> tap username -> General Settings -> Sync on WiFi only

There's some things I'm not sure whether they use cellular data, but I'm listing them here:

  • In the Settings app: General -> About -> Diagnostics & Usage -> Don't Send

Edit: there's a much better, much longer list here:
TidBITS: What’s Behind Mysterious Cellular Data Usage in iOS 6?
TidBITS: Mysterious iOS 6 Cellular Data Usage: A Deeper Look

2013-03-05 Forcibly drain your cellphone battery

If you want to forcibly drain your iPhone battery for some reason, first configure the display. Turn off the auto-lock, set the auto-brightness off and manually turn up the brightness to 100%.

Then go and warm up that little CPU by running a JavaScript benchmark, either with SunSpider if it's already pretty empty. Or go ahead and release the Kraken, which is a heavier test.

2013-01-07 Onward flight not checked-in

When flying internationally with multiple legs, I've ran into the situation where I'd use the web check-in but get the error message:

  Onward flight not checked-in. [HR 2606]

The possible reason is, that you're trying to check in too early. So although you might be able to check in for the first leg, you're too early for the second leg.

This situation had me stumped twice, until I realised the above reason is what might go wrong. The error message is not very helpful of course, so I'm hoping this is the actual reason, and documenting for everyone else. Here's hoping Google finds this.

2012-12-18

Here's a one-liner for you today:

  $ sudo ngrep tcp and port 80 -W byline

This prints HTTP traffic to the console, including full body.

2012-12-04 Objective-C with a Makefile

Here's a little example on how to compile Objective-C on the commandline, with a makefile. All code can be downloaded here: test cars.zip or copy/pasted from below.

Create a file called Makefile:

 CC=clang
 LIBS=-framework Foundation
 all: test_cars
 test_cars: test_cars.m car.m
 	$(CC) $(LIBS) test_cars.m car.m -o test_cars
 clean:
 	rm -f test_cars *.o
 Now create a header file:
 #import <Cocoa/Cocoa.h>
 @interface Car : NSObject {
 
         float fillLevel;
 }
 -(float) gasLevel;
 -(void) addGas;
 @end

Plus the implementation:

 #import "Car.h"
 @implementation Car
 -(id) init
 {
     self = [super init];
     if (self) {
         fillLevel = 0.0;
     }
     return self;
 }
 -(void) addGas
 {
     NSLog(@"Added one gallon!");
     fillLevel += 1.0;
 }
 -(float) gasLevel
 {
     return fillLevel;
 }
 @end

And the file containing main:

 #import <Foundation/Foundation.h>
 #import "car.h"
 
 int main(int argc, const char * argv[])
 {
     @autoreleasepool {
         // insert code here...
         Car *c = [[Car alloc] init];
         NSLog(@"Level = %f", [c gasLevel]);
         [c addGas];
         NSLog(@"Level = %f", [c gasLevel]);
 
     }
     return 0;
 }

Type make in the directory containing the above files, and run the example with ./test_cars. Enjoy the commandline goodness.

The code in car.m and car.h is mostly by your friendly neighborhood Java/Grails developer.

2012-11-11 Starting with storyboard

I've never seriously worked with the storyboard before in Xcode, so here's a summary of what I've gathered so far.

  • The storyboard defines the flow of screens throughout your app, it's basically a collection of view controllers and their NIBs.
  • A storyboard is made up of scenes that are connected with segues (pronounced "SEG-way").
  • A scene here means: a view controller plus view.
  • A segue specifies how you go from one view controller to the next.

Container view controllers (such as a navigation controller, or a tab bar controller) have a special place in a storyboard, since these container view controllers define how the navigation takes place.

Thinking about the implementation details, I'd expect the storyboard to actually show a graphical representation of an XML file. In this file, the list of screens is saved, including the way how the screens are shown (with animations or what have you). Also present would be the type of container view controller.

In other words, in the MVC-pattern, the storyboard is the input for an over-arcing controller. A big data file, a specification, according to which all container view controllers are instantiated with the correct parameters. Instead of coding, you drag and drop this specification using Interface Builder.

You still have to put in some navigation code, though. Basically you code which segue to perform:

 [self performSegueWithIdentifier:@"OfferDetails" sender:self];

If I'd had to put it in one sentence: storyboard replaces the code for navigating between views.

2012-11-09 Full-text searching

In a previous project, I solved the full text search problem as follows.

Management overview: it's very easy to do full text searching but very hard to assign a weight to the results. MySQL supports full-text search but only in a degraded type of database (MyISAM instead of the full-featured InnoDB). The problem can be fixed by creating an "archive" table in this degraded MyISAM format, where you store a business ID, plus a summary of the data you're looking for (i.e. a concatenation of business name, address, reviews, locations etc). The MySQL full-text search facility then becomes available, and you get results as such:
(score, businessID, summary)
(0.99, 12, "Clear Cafe A beautiful restaurant that serves raw, vegan and seafood cuisine www.clearcafeubud.com Restaurants Jl. Hanoman, Ubud, Bali 80571, Indonesia 03618894437 Ubud Cheap and good food Bart I've been there with a 10+ people group and they were happy to accomodate apparently they also blah blah")

As you can see, the summary is just a concatenation of name, address, category, reviews, phone number, everything. There is lots of duplicate information in there, but that doesn't matter for the results.

Tech details: we were doing scientific measurements and these were stored in a bunch of InnoDB tables. You can mix table types InnoDB and MyISAM in a database schema. I created a separate table called 'fulltexts' in the database, with basically two fields: "measurement ID", and "summary". Any human-readable fields from any of the tables were concatenated into a summary. I created a database row-level trigger for a couple of tables, so that each insert or update would concatenate a buch of fields and do an insert into 'fulltexts' (or a replace, if the summary needed updating). Then, this table can be searched by the MySQL facility "full text search", and give you back a result with a relevancy. This is quite nifty and fast.

2012-08-12 Xcode 4.4 won't quit

I've had a problem with Xcode 4.3 and 4.4 where it will not quit after stopping the simulator. The problem is that Xcode thinks that tasks are running (even with the simulator not running). The only solution is to force-quit Xcode, which is a pretty major interruption in your workflow.

Here are some threads that appear to talk about the same problem:
Failure to stop executing task in Xcode 4.3
Why does Xcode 4.3.1 (4E1019) / 4.3.2 (4E2002) hang regularly with iOS simulator?
My 2 XCode 4.4 problems

I haven't found a workaround so far. The last thread on cocoabuilder.com indicates that you should add some extra logging to find the problem:

 defaults write com.apple.dt.Xcode IDEDebuggerLogToFile ~/IDEDebugger.log

Update: the problem seems to occur less, when I quit the task from Xcode. Normally I'd just punch Alt-Q to quit the simulator altogether; now I alt-Tab to Xcode first, then punch Alt-. (Alt-period) to stop the running task.

Update 2: I found a suggestion to clear the project from the organizer window: Xcode 4.3.2 process hangs.

Update 3: It still happens, even with Xcode 4.6... There's another suggestion that says to click the Simulator icon in the dock, which seems to work for now.
XCode 4.3.2, issue with running on simulator

This is my pet issue, and since this is the internet, every time Apple releases a new version of Xcode, I will loudly point to this bug and ask Why haven't they fixed this??!?!?!!111!! When they do fix it, I'll still keep using it as an example why Xcode is "broken".

2012-07-15 strace on Mac OS X

If you're looking for the Linux strace command on OS X, stop looking. OS X doesn't have this command, but what it does have, is dtruss.

I made the mistake of assuming dtruss was a straight replacement, so I ran it with the process name as its argument; i.e:

 $ sudo dtruss /Applications/SomeRandomApp/Contents/MacOS/SomeRandomApp
 dtrace: failed to execute /Applications/SomeRandomApp: file is set-id or
 unreadable [Note: the '-c' option requires a full pathname to the file]

The error message had me stumped, but after some reading up, dtruss is just a different command altogether. Rather you run it as follows:

 $ sudo dtruss -n SomeRandomApp

And in another terminal, or just via the GUI, run the command.

This came in handy when using the excellent JollysFastVNC which was failing to connect over an SSH tunnel without telling me the error. I found out the error as follows:

 $ sudo dtruss -n JollysFastVNC\ Home -t read
  6110/0x3af1f:  read(0x17, "Bad stdio forwarding specification '\\'\r\n\0", 0x1000)		 = 40 0

That gave me a clue that I made a typo in the SSH Options field in JollysFastVNC.

See also Brendan Gregg's blog.

2012-07-12 Deflux SQUID

Another update on the routines that I'm writing for the SAFARI project. I've already explained that we work with a sensor type called TES, and that to read out this sensor, we use a device called a SQUID. Our SQUID is basically a 3x3mm chip, Basically, the TESes and the SQUID are put together in an electronic circuit, which is then immersed in liquid helium.

Since at this temperature (at or below 4 Kelvin), the circuit is superconducting, the SQUID can trap flux because a superconducted current is flowing around in the SQUID.

Since the SQUID is basically a magnetometer, we need trapped flux just as much as we need a punch in the face. The flux can be removed by heating the SQUID a little but, but only just so it no longer superconducts. This is a very common thing for superconducting sensors, so our readout electronics have firmware which contains a "remove flux" procedure.

However, you can't just put the complete procedure in firmware, because we have only one version of electronics and we have multiple different setups in the labs. Each of these setups has a different cryostat with different temperatures (anything between 4 Kelvin and 50 milliKelvin). So the firmware deflux routine has a bunch of parameters which should be controlled through a software routine, which the user can then configure and kick off.

You might wonder why we don't just do it all in software. The reason is, that for the best result, the heating current must be stopped abruptly and the timing aspect is much more tightly controlled by using firmware.

2012-07-09 Mac OS X command line utilities

Recently I upgraded my macbook its SSD drive for a bigger version. Since I wanted to start with a fresh OS X copy, I decided not to use the Migration Assistant but rather just manually copy over the home folders (like Documents, Pictures etc).

I want to re-use the old, smaller SSD, but there might be some files which I forgot to copy. So what I was looking for, is a way to create a disk image like I'm used to when running Linux.

Below, the procedure is documented. It's probably wise to stick nice -n 20 before the commands dd and cat, so normal desktop usage is not bothered by the I/O, but for clarity, I've removed it.

First, connect your USB drive and type mount to show which drive got connected:

 $ mount
 /dev/disk0s2 on / (hfs, local, journaled)
 devfs on /dev (devfs, local, nobrowse)
 /dev/disk1s2 on /Volumes/storage (hfs, local, journaled)
 map -hosts on /net (autofs, nosuid, automounted, nobrowse)
 map auto_home on /home (autofs, automounted, nobrowse)
 /dev/disk2s2 on /Volumes/MacSSD (hfs, local, nodev, nosuid, journaled, noowners)

In this case, the last line shows that /dev/disk2 is the device we need, and partition 2 contains an HFS-formatted partition (or volume, in OS X speak). You can zoom in on this disk with the command diskutil list and show all partitions of all disks.

Since OS X automatically mounts volumes that a user attaches, we can now change to the directory where it's mounted, and zero the empty space:

 $ cd /Volumes/MacSSD
 $ cat /dev/zero > zero.file;rm zero.file

We'll now unmount the volume so we can make a copy of the whole partition (instead of the contents of the volume):

 $ cd
 $ hdiutil detach /dev/disk2s2

Now, we can make a literal copy of this partition:

 $ dd if=/dev/disk2s2 of=MacSSD.hfs bs=1m 

Over USB, this could take a loooong time, though. After that, compress the image with:

 $ gzip MacSSD.hfs

Now it's safe to move this file somewhere else. If you want, you can encrypt the file before doing so.

2012-07-02 Minimizing amplifier chain offset

For the SAFARI project, I'm refactoring some code, and one part of that is a routine to minimize the offset of the front-end electronics or FEE. For the place of the FEE in the setup, see also 2012-03-29 Extensions to Safari library.

The overall setup

Here is a picture of the lab setup.

lab setup with cryostat.jpg

From right to left: the PC with our lab software, the DEMUX board and some other measurement equipment in the rack, and the cyostat with on top the FEE board.

Zooming in on the cryostat; we're looking at a closed cryostat now. It's filled with helium because the scientists are doing a measurement. Before the test, the cryostat was opened:

opened safari cryostat.jpg

The sensor

The type of sensors used by the SAFARI instrument are TESes: Wikipedia article on transition edge sensors. As you can read with in this article, this type of sensor uses a SQUID in its readout electronics.

The TES basically consists of a tiny surface ("pixel") which is biased by a current source from the DEMUX board. When a photon hits the pixel, its resistance increases and the voltage drops. You detect this via a SQUID, whose output is amplified outside of the cryostat.

The TES its setpoint is 20 mOhm and if it's hit, the resistance increases to 100 mOhm. It's superconducting, so it's hard to get it out of its setpoint with heat, so flux is used.

The SQUID is controlled with a DC voltage, meaning we set and characterize it via a DC voltage. It's supplied by our DEMUX electronics board. Reading out happens with an AC voltage. It's supplied by our FEE electronics board, which also amplifies the output so it can be digitized by the DEMUX board. See also this schema:

SAFARIbuildingblocks-1.png

The signal

We currently use a single SQUID for all the TESes. The SQUID combines these in one signal, which the DEMUX board then splits up again so we can read out each individual pixel. To see what I mean, here is the sensor:

safari sensor.jpg

Description: at the top left, we have the TESes. On the bottom left, we have the LC filters (see below). On the right, we prominently see the black connectors that bring the signal out of the sensor. On the bottom right, we see the single SQUID. Note the connections in the top and bottom middle. If we increase the number of pixels, these connections will stay the same because the TES signals are multiplexed (put together). To get an idea of the physical scale, this thing has a width of 12-13 cm (I haven't measured it exactly, just an estimate).

There is one LC filter for each pixel. Basically, with the LC filter you pick out a certain frequency. Thus the LC filter controls which information the pixel gives us; it makes sure the spectrum we're measuring is nicely divided over the pixels. Wikipedia has an article on LC circuits which is hard to read for me, but do notice the applications header which I've linked to. First entry says that the most common application is tuning, which is exactly what's being done here.

As an aside: two or more SQUIDs

SRON bought the SQUID from elsewhere, namely Magnicon, and then bonded to the silicon substrate. As for the SQUID, there are other possibilities. Per pixel row, you could use one SQUID. Or you could still use the above setup, but with an extra SQUID, which we call the dual-SQUID configuration. This configuration has been designed, but for this to work, we need to characterize both SQUIDs and since they influence each other, we'd need to use a flux-locked loop to lock the first SQUID in its set point, then characterize the other. I might have to expand on this sometime later.

Looking ahead

What I've described above, is just the lab setup. With this setup, we can only detect power intensity, but the SAFARI instrument is a spectrometer. Thus, the instrument consists of an FPU (focal plane unit), which is a box containing mirrors, shutter, filter wheels, a cooler, an FTS and three FPAs (focal plane assemblies). These FPAs are basically little cubes with the above described sensor in there. The focal plane unit is cooled to 1.7 Kelvin, and the focal plane assemblies are cooled to 50 milli-Kelvin via an intermediate 300 milli-Kelvin stage. Outside of the focal plane unit, we also have two "warm" units. Basically these have the function of the current DEMUX and FEE boards, but our current boards are meant for a lab situation, so new electronics will be made.

So the sensor measures power intensity, and the FTS will give us intensity as a function of wavelength. In other words: the instrument will send us power plus mirror position (of the FTS), and these can be transformed mathematically to create a spectrum. Since SAFARI is an infrared instrument, we are looking in the 34 to 210 micrometer wavelength.

Current task

So far I've described the setup. Now on to my current task.

To accurately set the DC bias on the pre-amplifier chain that consists of a single SQUID, an amplifier, and an integrator (??), we need to measure the offset that's introduced by the FEE.

The basic schematic of the SQUID, the amplifier and the integrator look as follows:
schema single squid.png

On the left, we have the single SQUID. It's biased in a range between 22 and 44 uA. On the right, we have two functions. The top right shows a function of the FEE board, and the botton right (with the DC voltage part) shows a function on the DEMUX board; the DAC which sets a voltage, and the integrator that keeps the voltage over the setpoint of the SQUID. Below the SQUID is

This offset can be adjusted via the integrator-offset bias generator (noted as DAC in the above schema (??)). It's also adjusted via:

  • the LF_Gain, which has a range of 100..10k
  • the LF_Input Impedance
  • the LF Amplifier configuration
    (??)

Note: the ?? indicates I have to check this statement

Background

SPICA - SAFARI Link to the SRON homepage concerning the project

2012-05-19 Telfort ADSL modem Zyxel P-2812HNU-F1 and VoIP Planet

(English readers: this is an explanation on how to configure the modem of a Dutch ISP for VOIP usage).

Onlangs heb ik mijn Telfort ADSL abonnement geupgrade en kreeg een Zyxel modem, type P-2812HNU-F1. Vorig jaar heb ik reeds voor type Zyxel 2601HN-F1 instructies gegeven, hierbij een update.

Om via een SIP account bij VoIP Planet te bellen met een telefoontoestel dat je aansluit op het modem, volg de volgende instructies op.

Om het modem te configureren, log in als admin (standaard wachtwoord: 1234) op het modem via adres http://192.168.1.254/ en kies in het onderste menu genaamd VoIP voor de optie SIP.

zyxel sip menu.png

Het scherm dat tevoorschijn komt, heeft drie tabbladen. Het eerste tabblad is genaamd "SIP Service Provider". Laat het drop-down menu "Service Provider Selection" staan op "Provider-1" en vul deze als hieronder in. Klik daarna op Apply, en het drop-down menu toont nu netjes "VoipPlanet".

zyxel service provider.png

Klik daarna op het tweede tabblad, en klik op het pen-en-papier icoon in de Modify kolom, eerste rij. Er komt een popup-scherm waarin je slechts een viertal dingen hoeft te veranderen. Eerst moet je de "Active SIP Account" aanvinken, dan vul je de SIP Account Number in, en daarna Username en Password.

Waarbij je natuurlijk de vermeldde SIP Account Number en Username verandert in diegene die je van VoIP Planet hebt gekregen:

zyxel sip account.png

Als het goed is, hoef je niet naar beneden te scrollen. Klik op apply om op te slaan. Ga onderaan het scherm naar het menu System Monitor, en kies voor Log.

zyxel system monitor.png

Activeer de tweede tab, "Phone Log". Hier moet nu staan:

  SIP Registration: SIP:12345: Register Success

Voorbeeld:

zyxel sip success.png

Je kunt nu bellen met het aangesloten (analoge) telefoontoestel via VoIP Planet!

2012-03-29 Extensions to Safari library

I've received the specs for a modification to our software library for the SAFARI project, and after a two-month hiatus, I'm looking again at what's currently present.

What's existing, is basically a script that displays a menu with a bunch of testing options. It's called demuxtest, because it tests the electronics board which we call "the demux board".

SAFARIsetupsoftwareandelectronics.png

The Demux board plays the intermediary role here; all commands are routed through this board to the FEE (front-end electronics) board. If the FEE board isn't present, then it can generate a dummy response for testing purposes.

Zooming in on the electronics, it would look as follows:

SAFARIbuildingblocks-1.png

You could roughly see the demux board as the digital part, and the rest of this picture as the analog part.

Some visuals

This is our test setup:

demux fee test setup.jpg

The FEE board

This is how the FEE (front end electronics) board looks like:

fee board.jpg

The demux board

This is the demux board:

demux board.jpg

2012-03-20 graphics glitches on OS X Lion

Graphics glitches

For the past days, I've seen a number of glitches on OS X Lion (update 10.7.3) with my mid-2010 Macbook Pro, and apparently, I'm not the only one.

It seems to happen especially on external monitors:
https://discussions.apple.com/message/17524501#17524501

However, iMacs have a lot more trouble:
https://discussions.apple.com/thread/3201871

A possible solution for the graphics glitches is mentioned at the end of the thread:
https://discussions.apple.com/message/17804906#17804906

White screen

UPDATE: this issue seems fixed with the 10.7.4 update

I've encountered a bug where the screen would blank, reproducible as follows:

  • With a closed laptop, hook up an external monitor (mini-DisplayPort) and keyboard
  • Press a key to wake screen, then I start working
  • At the end of day, I unplug keyboard and laptop
  • When coming home, open lid and click "switch user" (I have security settings such that it locks the display when closing the lid)
  • Look at white screen, where the mouse cursor is visible. Sometimes, after up to a minute, the screen will redraw correctly and show the login screen. Sometimes it'll just stay this way, and a hard reboot is required.

Black screen

I've also encountered a bug where the laptop screen would just stay black, not reproducible. I took the following steps:

  • With a closed laptop, hook up an external monitor and keyboard
  • Press a key to wake screen, then disconnect screen with the mini-DisplayPort
  • Open laptop lid, stare at black screen

Remotely I could log into the laptop, though.

Another set of circumstances happens as follows:

  • With a closed laptop, hook up an external monitor and keyboard
  • Go away until display sleeps
  • Come back and discover laptop cannot be woken up, but is still accessible via SSH

The following lines are then visible in the kernel.log:

 --- last message repeated 1 time ---
 kernel[0]: NVDA(OpenGL): Channel timeout!                                                
 --- last message repeated 1 time ---
 kernel[0]: NVDA(OpenGL): Channel timeout!
 --- last message repeated 1 time ---
 kernel[0]: NVDA(OpenGL): Channel exception!  exception type = 0xd = GR: SW Notify Error
 kernel[0]: NVDA(OpenGL): Channel timeout!
 kernel[0]: NVDA(OpenGL): Channel exception! exception type = 0xd = GR: SW Notify Error
 kernel[0]: NVDA(DisplayBase): Channel timeout!

UPDATE: this is still occurring with 10.7.4 update. Workaround is to keep the lid open

2012-03-08 Launchctl on OS X

Comparison of SysV initialization and launchd

If you're an old-school Unix or Linux person, you're probably also used to the SysV system initialization routine. Nowadays, it's slowly being replaced by upstart and the likes, but I'm going to skip that for now. This is meant as a guide for ye olde Unix neckbeards.

Basically, SysV init means:

  • Daemons are controlled with scripts in /etc/init.d
  • These scripts take a couple of standard arguments, such as start/stop/restart/reload
  • There are three or four so-called runlevels, and the higher the runlevel, the more capable the system is (in other words, more daemons are started up)
  • The sequence of starting the daemons (i.e. calling the init.d scripts with "start") is determined by specially named symlinks in directories named for the runlevel, for instance for runlevel 3, the directory /etc/rc3.d contains a bunch of symlinks to start the daemons appropriate for runlevel 3
  • If you want to prevent a daemon from running, then you remove symlinks in the /etc/rcX.d directory. This is usually done through a command which manages this for you. Redhat uses chkconfig, Debian uses update-rc.d.
  • To control specific daemons, you call the appropriate script in /etc/init.d with the start or stop parameter

OS X does this differently, and superficially, much simpler.

When the system boots, the launchd process is responsible for starting all daemons. To sum up the characteristics of this method:

  • Daemons are controlled with the launchd command, there aren't any start/stop scripts
  • Description of how to start a daemon is specified in a configuration file in the folder /System/Library/LaunchDaemons, and these files have the binary plist format, editable via the defaults command
  • Launchd does not support dependencies, thus it's not possible to configure (for instance) starting the database daemon before the webserver.
  • You can prevent daemons from running by using the launchctl command
  • To control specific daemons, you use the launchctl command

There is a further distinction which I haven't talked about. OS X has the concept of user-specific daemons. These are called "launch agents" instead of daemons and they have their configuration files in /System/Library/LaunchAgents, and other locations specified in the launchd man page. Examples of these launch agents: the dock and software for your Wacom tablet.

launchctl examples

Listing present daemons

The stuff below is equivalent to listing the contents of the /etc/init.d directory on Linux.

To see what system daemons are present on the system;

 $ cd /System/Library/LaunchDaemons
 $ ls

Note that this does not tell you whether they've actually been configured to start, or not!

Overview of running daemons

The stuff below is equivalent to listing the contents of the /etc/rcX.d directories on Linux, or using the "chkconfig --list" command on RedHat.

Since launchd is more than just managing daemons, but also a replacement for init, it can show us some bookkeeping on previously run daemons. Use launchctl its 'list' parameter to list the currently running daemons, including the daemons that have already exited (i.e. configured for a one-time run).

To list all daemons:

 $ launchctl list

To show daemons, but filter out those that aren't currently running:

 $ launchctl list | grep -v "^-"

Controlling daemons

The stuff below is equivalent to using the scripts in /etc/init.d on Linux.

To temporarily stop a daemon, you use the unload parameter plus the config filename. For instance to stop the daemon that controls swapping memory to disk:

 $ sudo launchctl unload /System/Library/LaunchDaemons/com.apple.dynamic_pager.plist

If it says "nothing to unload", then the daemon wasn't configured to be started in the first place.

To temporarily start a daemon, use the load parameter:

 $ sudo launchctl unload /System/Library/LaunchDaemons/com.apple.dynamic_pager.plist

Adjusting the boot process

The stuff below is equivalent to using the chkconfig or update-rc.d commands in Linux.

To start or stop a daemon, and make sure it'll be started the next boot, use the -w flag. For example: to start the dynamic pager now, and after rebooting:

 $ sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.dynamic_pager.plist

If the daemon wasn't configured to start before, you might want to pass the -F flag, which forces starting.

Configuring a daemon

The stuff below is equivalent to editing a script in /etc/init.d, or a configuration file in /etc under linux.

To configure the start/stop flags of a daemon, you edit the plist file with the defaults command. To read the current configuration:

 $ defaults read /System/Library/LaunchDaemons/com.apple.dynamic_pager.plist

To change a config file, simply use the defaults 'write' parameter. As an example, to disable Bonjour advertising on the network:

 $ sudo defaults write /System/Library/LaunchDaemons/com.apple.mDNSResponder \
     ProgramArguments -array-add "-NoMulticastAdvertisements"

Note that whenever software is updated, these changes get written over! OS X does not have a package system to my knowledge, which will save any adjusted configuration files.

Notes

Some notes:

  • Launchd doesn't support dependencies, but it's possible to create special configuration files in /System/Library/LaunchDaemons which will start a daemon whenever a client tries accessing a particular network port.
  • Both launchd and launchctl have a man page, and they're okay (not super, though).
  • For more information on how to write the plist files, see also the man page of "launchd.plist"
  • Should you want to periodically run a script, check out the tips here: http://blog.mattbrock.co.uk/2010/02/25/moving-from-cron-to-launchd-on-mac-os-x-server/

2012-02-09 Writing ethernet packets on OS X and BSD

If you want to write raw ethernet frames, you can use so-called raw sockets. Unfortunately, there's a difference between raw sockets under Linux and raw sockets under BSD and derivatives such as Apple's OS X. Linux allows you to create custom ethernet frames with whatever content you want, but BSD assumes you will create IP (internet protocol) packets.

In my case, I wanted to write raw ethernet frames, as in: have complete control of the contents. The content didn't have anything to do with internet; at work we use ethernet to talk to custom electronics. The electronics doesn't contain enough processing power to have a full TCP/IP stack, when ethernet is perfectly fine.

Note: I've tested the code below on OS X, version 10.7 (Lion). I've checked the output using tcpdump on the other end (a Linux box). Most code is from Bastian Rieck's excellent piece "Using FreeBSD's BPF device with C/C++". The only real addition is the write_frames routine that chops up your data before sending it.

    #include <sys/types.h>
    #include <sys/uio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <string.h>
    #include <sys/ioctl.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <net/if.h>
    #include <net/ethernet.h>
    #include <net/bpf.h>
    // Fill in your source and destination MAC address
    unsigned char dest_mac[ETHER_ADDR_LEN]  = {0x00, 0x1b, 0x21, 0x53, 0x83, 0x4f};
    unsigned char src_mac[ETHER_ADDR_LEN] = {0xc8, 0xbc, 0xc8, 0x91, 0x79, 0x2d};
    // My struct for an ethernet frame. There are many like it, but this one is
    // mine.
    struct frame_t {
        struct ether_header header;
        unsigned char payload[ETHER_MAX_LEN - ETHER_HDR_LEN];
        ssize_t len;
        ssize_t payload_len;
    };
    // Some convenience constants
    const size_t ETHER_PAYLOAD_START = (2*ETHER_ADDR_LEN) + ETHER_TYPE_LEN;
    const size_t ETHER_PAYLOAD_LEN = ETHER_MAX_LEN - ETHER_HDR_LEN - ETHER_CRC_LEN;
    // Try to open the bpf device
    int open_dev(void)
    {
        char buf[ 11 ] = { 0 };
        int bpf = 0;
        int i = 0;
        for(i = 0; i < 99; i++ )
        {
            sprintf( buf, "/dev/bpf%i", i );
            bpf = open( buf, O_RDWR );
            if( bpf != -1 ) {
                printf("Opened device /dev/bpf%i\n", i);
                break;
            }
        }
        if(bpf == -1) {
            printf("Cannot open any /dev/bpf* device, exiting\n");
            exit(1);
        }
        return bpf;
    }
    // Associate bpf device with a physical ethernet interface
    void assoc_dev(int bpf, char* interface)
    {
        struct ifreq bound_if;
        strcpy(bound_if.ifr_name, interface);
        if(ioctl( bpf, BIOCSETIF, &bound_if ) > 0) {
            printf("Cannot bind bpf device to physical device %s, exiting\n", interface);
            exit(1);
        }
        printf("Bound bpf device to physical device %s\n", interface);
    }
    // Set some options on the bpf device, then get the length of the kernel buffer
    int get_buf_len(int bpf)
    {
        int buf_len = 1;
        // activate immediate mode (therefore, buf_len is initially set to "1")
        if( ioctl( bpf, BIOCIMMEDIATE, &buf_len ) == -1 ) {
            printf("Cannot set IMMEDIATE mode of bpf device\n");
            exit(1);
        }
        // request buffer length
        if( ioctl( bpf, BIOCGBLEN, &buf_len ) == -1 ) {
            printf("Cannot get bufferlength of bpf device\n");
            exit(1);
        }
        printf("Buffer length of bpf device: %d\n", buf_len);
        return buf_len;
    }
    // Read one or more frames
    void read_frames(int bpf, int buf_len)
    {
        int read_bytes = 0;
        struct frame_t *frame;
        struct bpf_hdr *bpf_buf = (struct bpf_hdr*) malloc(buf_len);
        struct bpf_hdr *bpf_packet;
        int run_loop = 1;
        int i = 0;
        printf("Start reading frames\n");
        while(run_loop) {
            memset(bpf_buf, 0, buf_len);
            if((read_bytes = read(bpf, bpf_buf, buf_len)) > 0) {
                printf("Read %d\n", i);
                i++;
                // read all packets that are included in bpf_buf. BPF_WORDALIGN is used
                // to proceed to the next BPF packet that is available in the buffer.
                char* ptr = (char*)bpf_buf;
                while(ptr < ((char*)(bpf_buf) + read_bytes)) {
                    bpf_packet = (struct bpf_hdr*)ptr;
                    frame = (struct frame_t*)((char*) bpf_packet + bpf_packet->bh_hdrlen);
                    frame->len = bpf_packet->bh_caplen;
                    frame->payload_len = frame->len - (2*ETHER_ADDR_LEN) - ETHER_TYPE_LEN;
                    // Do something with the frame
                    printf("Got packet, length of frame: %ld, length of data: %ld\n",
                        frame->len, frame->payload_len);
                    ptr += BPF_WORDALIGN(bpf_packet->bh_hdrlen + bpf_packet->bh_caplen);
                }
            } else {
                perror("Meh, couldn't read from bpf device");
                exit(1);
            }
        }
    }
    // Read a single frame
    void read_single_frame(int bpf, int buf_len)
    {
        int read_bytes = 0;
        int i;
        struct bpf_hdr* bpf_buf = malloc(buf_len);
        memset(bpf_buf, 0, buf_len);
        char *ptr;
        printf("Headerlength: %ld\n", sizeof(bpf_buf));
        read_bytes = read(bpf, bpf_buf, buf_len);
        if(read_bytes > 0) {
            printf("Got %d bytes\n", read_bytes);
        } else {
            printf("Got 0 bytes\n");
        }
        ptr = (char*)bpf_buf;
        for(i = 0; i < read_bytes; i++) {
            unsigned char byte = (unsigned char) *(ptr + i);
            printf("0x%02X ", byte);
        }
        printf("\n");
    }
    // Write a single ethernet frame with test data
    void write_single_frame(int bpf)
    {
        ssize_t data_length = 0x4F;
        struct frame_t frame;
        memcpy(frame.header.ether_dhost, dest_mac, ETHER_HDR_LEN);
        memcpy(frame.header.ether_shost, src_mac, ETHER_HDR_LEN);
        frame.header.ether_type = 0x00;
        frame.len = (2*ETHER_ADDR_LEN) + ETHER_TYPE_LEN + data_length;
        // Fill frame with ramp
        unsigned char j;
        for (j = 0; j < data_length; j++) {
            frame.payload[j] = j;
        }
        ssize_t bytes_sent;
        bytes_sent = write(bpf, &frame, frame.len);
        if(bytes_sent > 0) {
            printf("Bytes sent: %ld\n", bytes_sent);
        } else {
            perror("Whoops! Does the device actually have an IP address?");
            exit(1);
        }
    }
    // Divide data across ethernet frames
    void write_frames (int bpf, const unsigned char *databuf, size_t datalen)
    {
        size_t start = 0;
        struct frame_t *frame = malloc(ETHER_MAX_LEN);
        size_t bytes_to_send;
        ssize_t bytes_sent;
        memcpy(frame->header.ether_dhost, dest_mac, ETHER_HDR_LEN);
        memcpy(frame->header.ether_shost, src_mac, ETHER_HDR_LEN);
        frame->header.ether_type = 0x0000;
        do {
            // Clear frame
            bzero((void*)(frame+ETHER_PAYLOAD_START), ETHER_PAYLOAD_LEN);
            // Calculate remainder
            if((datalen - start) < ETHER_PAYLOAD_LEN) {
                bytes_to_send = datalen - start;
            } else {
                bytes_to_send = ETHER_PAYLOAD_LEN;
            }
            // Fill frame payload
            printf("Copying payload from %lu, length %lu\n", start, bytes_to_send);
            memcpy(frame->payload, (void*)(databuf + start), bytes_to_send);
            frame->len = ETHER_HDR_LEN + bytes_to_send;
            // Note we don't add the four-byte CRC, the OS does this for us.
            // Neither do we fill packets with zeroes when the frame length is
            // below the minimum Ethernet frame length, the OS will do the
            // padding.
            printf("Total frame length: %lu of maximum ethernet frame length %d\n",
                frame->len, ETHER_MAX_LEN - ETHER_CRC_LEN);
            bytes_sent = write(bpf, frame, frame->len);
            // Check results
            if(bytes_sent < 0 ) {
                perror("Error, perhaps device doesn't have IP address assigned?");
                exit(1);
            } else if(bytes_sent != frame->len) {
                printf("Error, only sent %ld bytes of %lu\n", bytes_sent, bytes_to_send);
            } else {
                printf("Sending frame OK\n");
            }
            start += bytes_to_send;
        } while (start < datalen);
        free(frame);
    }
    // Create a simple ramp so we can check the splitting of data across frames on
    // the other side (using tcpdump or somesuch)
    unsigned char* make_testdata(int len)
    {
        unsigned char *testdata = (unsigned char*)malloc(len);
        int i;
        unsigned char j = 0;
        for(i = 0; i < len; i++) {
            testdata[i] = j;
            j++;
            if(j < sizeof(char)) {
                j = 0;
            }
        }
        return testdata;
    }
    int main(void)
    {
        char* interface = "en0";
        unsigned char* testdata;
        size_t testdata_len = 4530;
        int bpf;
        int buf_len;
        bpf = open_dev();
        assoc_dev(bpf, interface);
        buf_len = get_buf_len(bpf);
        //read_single_frame(bpf, buf_len);
        //read_frames(bpf, buf_len);
        testdata = make_testdata(testdata_len);
        write_frames(bpf, testdata, testdata_len);
        exit(0);
    }

2012-01-26 TransIP VPS

Since last year, TransIP offers virtual private servers (VPSes). I have been playing with the M version for the past week. This particular VPS has 512 MB of RAM, 10 GB of harddisk space and one CPU core, and I ran UnixBench on it. Here are the results:

 System Benchmarks Index Values               BASELINE       RESULT    INDEX
 Dhrystone 2 using register variables         116700.0   24917048.3   2135.1 
 Double-Precision Whetstone                       55.0       3413.4    620.6
 Execl Throughput                                 43.0       4520.4   1051.3
 File Copy 1024 bufsize 2000 maxblocks          3960.0     882288.4   2228.0
 File Copy 256 bufsize 500 maxblocks            1655.0     260223.5   1572.3
 File Copy 4096 bufsize 8000 maxblocks          5800.0    1378435.4   2376.6
 Pipe Throughput                               12440.0    2369954.3   1905.1 
 Pipe-based Context Switching                   4000.0     387771.4    969.4
 Process Creation                                126.0      14882.1   1181.1
 Shell Scripts (1 concurrent)                     42.4       6816.5   1607.7
 Shell Scripts (8 concurrent)                      6.0        898.3   1497.2
 System Call Overhead                          15000.0    4303337.7   2868.9
                                                                    ========
 System Benchmarks Index Score                                        1537.7

Edit 29/03/2012: their offer has gotten better, with more disk space.

2012-01-23 Logrotate error for MySQL on Debian 6 Squeeze

When installing a new machine with Debian 6 (Squeeze), I also imported a database from a CentOS 5 box. The next day, the following error appeared in my inbox:

 /etc/cron.daily/logrotate:
 error: error running shared postrotate script for '/var/log/mysql.log
      /var/log/mysql/mysql.log /var/log/mysql/mysql-slow.log '
 run-parts: /etc/cron.daily/logrotate exited with return code 1

Turns out that Debian needs a user called 'debian-sys-maint' and that this user needs a number of rights on the database. The username and password are configured in /etc/defaults/debian.cnf so write these down, then create the user with the following commands:

 $ mysql -u root -p
 mysql> GRANT SHUTDOWN ON *.* TO 'debian-sys-maint'@'localhost';
 mysql> GRANT RELOAD ON *.* TO 'debian-sys-maint'@'localhost';
 mysql> GRANT SELECT ON 'mysql'.'user' TO 'debian-sys-maint'@'localhost';

and then set the correct password for the debian user:

 mysql> SET PASSWORD FOR 'debian-sys-maint'@'localhost' = PASSWORD('secret');

Test whether logrotate won't give any problems:

 $ sudo logrotate -f /etc/logrotate.d/mysql-server

2011-12-12 Protocol stuff

We have an interesting situation here at work, concerning our internally developed binary protocol.

There are two libraries to parse the protocol. These have different ways to calculate the length of a block of data. And now I want to know which way is the correct. Of course, that's debatable, with a history of hardware that uses the protocol for different purposes.

In the current firmware/software combination, the firmware generates a large number of packets that each fit into the Ethernet maximum packet length (i.e. 1500 bytes). Considering the packet length is 1500 bytes, and our data consists of 2-byte samples, we get 730 samples in a packet. The rest is overhead, namely: 6 bytes primary header, 9 bytes secondary header and 24 bytes data header, plus an end-of-packet byte containing the value 0xA5.

A number of these packets are received by an intermediate piece of software, called the Ethernet Daemon, and then concatenated into a larger packet, where the Data Header/Data part are repeated.

CCSDSpacketconcatenation.png

The discussion piece currently, is how to calculate the length of the block of data. You need to know this in order to find the next Data Header. The following fields in the data header can help:

  • samples64; the number of samples per 64 bit; must be a power of 2
  • sample_count: the number of samples
  • data_bits: number of bits that's used in the data block
  • encoding: the encoding of the data block; two complement, unsigned integer, double or zero-terminated string

Basically samples64 says something about how much samples are contained in 64 bits (eight bytes), and this can be represented graphically in the way below:

CCSDSdata bits.png

The above fields are pretty much explained now, except that the data_bits explanation is rather terse. The thing is, we software engineers round off everything to one byte. But in the real world, that's nonsense. For example, measuring a voltage will result in, say, 12 bits. For a 12-bits sample, we set the data_bits field to 12. Rounding off to bytes, this means that each sample will occupy two bytes. Thus, the samples64 field will be set to "4", meaning: four samples will fit in 64 bits.

The developer of our Python library said that the samples64 field implies, that the length of the Data field is always divisible by 8. Or in other words, that the Data field is created using 8-byte pieces. After discussion, it turns out that's not what the firmware does, in our projects.

His calculation of the length of the Data field was:

  Data field length (in bytes) = 8 * ( (sample_count + samples64 - 1) // samples64 )

(The double slash means: throw away the remainder)

The firmware would send a packet with a number of Data Header/Data fields. The first Data Header says contains 31390 samples, each 16-bits. The second Data Header contains 8760 samples. The above calculation would yield for the first Data Header:

  Data field length (in bytes) = 8 * ( (31390 + 4 - 1) // 4 )
  Data field length (in bytes) = 8 * 7848
  Data field length (in bytes) = 62784

And for the second Data Header:

  Data field length (in bytes) = 8 * ( (8760 + 4 - 1) // 4 )
  Data field length (in bytes) = 8 * 2190
  Data field length (in bytes) = 17520

The Perl library does things differently; its way of calculating the length of the Data field is:

  Data field length (in bytes) = sample_count * (data_bits / 8)

Thus for the first and second Data Headers, this looks as follows:

  Data field length (in bytes) = 31390 * (16 / 8)
  Data field length (in bytes) = 62780
  Data field length (in bytes) = 8760 * (16 / 8)
  Data field length (in bytes) = 17520

For this to work, the data_bits has to be rounded off to the nearest power of two. We don't have firmware that does otherwise, but in theory it's possible. This implies that the following combinations should be expected:

samples64 field data_bits field
1Between 64 and 33
2Between 32 and 17
4Between 16 and 9
8Between 8 and 5
16Either 4 or 3
322
641

We assume here, that the firmware packs the data as efficient as possible, of course. But that's not a guarantee. So the way to calculate the byte length of the Data field, should not include data_bits. It should be as simple as sample count times sample size (in bytes).

The sample size basically comes from the samples64 field:

samples/64 field sample size in bytes
18
24
42
81
160.5
320.25
640.125

Of course, now we're pushing the boundaries of other libraries and hardware. Computer and networking equipment dictates that the minimum transfer unit is the byte. Thus when the samples64 field is higher than 8, software must start ignoring the remainder of the Data field.

  Data field length (in bytes) = sample_count * sample_size_in_bytes
  Data field length (in bytes) = sample_count * ( 8 / samples64 )

Because we want to make sure that we're getting whole bytes, the result should be rounded up.

2011-10-27 Compact Google Calendar header

UPDATE

I have created an official extension, available in the Chrome Web Store:

Compact Calendar extension

Original post

The recent changes to Google Calendar are looking nice, but unfortunately the header and navigation take up far too much space when using my 13" MacBook. For a compact Google Calendar header in your Chrome browser, take the following steps:

  • Install the Live CSS Editor extension for Chrome
  • Go to the options, set them all to "Yes" and click Save
  • Go to your Google Calendar
  • Click the new CSS button that the extension provides
  • Add the following code
 #vr-nav { display:none; }
 #onegoogbar { display:none;}
 #vr-header { display:none; }

Done!

2011-10-11 OS X and Vim

Vim comes with OS X by default, but it doesn't come with X11 support. You can always install MacVim of course, but some of us prefer to use vim in the terminal. Unfortunately, because of the lack of X11 support, copying/pasting doesn't work correctly in OS X.

As an example, copy a paragraph of text from Safari, then paste it into your vim instance in the terminal. Chances are, if options such as AutoIndent are on, the paragraph will be rearranged.

On Linux, we have the + and * buffers for that. That way, a simple "+p command will paste the most recently copied text into the terminal version of vim.

To enable this on OS X, I added a hint to my Vim page.

2011-10-05 From iPhone to Android

This is part 8 of my series where I document going from an iPhone to Android 2.3, running on the Samsung Galaxy Gio.

Syncing, it turns out, is a royal pain in the ass with the combination Android and OS X. There's a whole bunch of sub-optimal and incomplete solutions out there. And that's assuming that your mail, calendar, contacts and notes/tasks are synced via your Google account.

Product Pros Cons
doubleTwistFree version available. Syncs music, video's and photos, wireless in the paid version Does not transcode music and resize photos to appropriate size for device. Does not sync (only download) photos.
iTuneMyWalkmanOpen source. Syncs music via iTunes, via a specially-named playlist. iTunes does transcoding if you care for that. Moves pictures to your harddisk, but doesn't sync them.Doesn't sync anything else than music (photos are copied). Thinks thumbnails are also pictures. Doesn't transfer cover art. Transferring takes a long time, a couple of Gb of music causes a twenty-minute "collecting data from iTunes" message.
PicasaSyncs photos wirelessly Forces you to use Picasa web albums

As far as I know, there is no one-stop solution on OS X, unless you use Samsung Kies on OS X, which only supports a limited number of Samsung phones.

2011-09-21 From iPhone to Android

This is part 7 of my series where I document going from an iPhone to Android 2.3, running on the Samsung Galaxy Gio.

From version 2.2, Android offers the HotSpot functionality. There's another way, namely using the phone as a 3G modem via USB. This is called tethering, and OS X supports this out-of-the-box, just follow the instructions on TodayOnTech.

Another thing I bumped into, is the lack of audiobook support. doubleTwist doesn't really sync this properly. So, you copy them manually, which is not really a big deal for me. However the default media player will list these as music, which it most definitely isn't. The Android blog has a solution for this, creating a zero-length .nomedia file in your SD-card its /Audiobooks directory. Clumsy, in my opinion. The mediaplayer could look at the ID3-tags of the MP3 files, and see that they're marked as 'spoken word' or 'audio book'.

Another missing thing of the stock music player is the lack of control from the lock screen. From a locked iPhone, you can doubleclick the home button and the player buttons will pop up if music is playing. There are a number of Android apps that simulate this, for instance bTunes, mixZing and Lithiumplayer.

Perhaps a solution is to use iTuneMyWalkman, if doubleTwist continues to bug me, then I'll try this solution.

Rooting

On the forums, there are powerusers who manage to fill up their phone with apps. Apparently, the Gio isn't meant for those people. If you're one of those, you want to be rooting your Samsung Gio. That way, you can delete some of Samsung's stock apps. It's a process similar to jailbreaking your iPhone, but since the Android phone manufacturers aren't as tight-assed, it's not really as invasive and/or complicated as the typical iPhone jailbreaking. See also the xda-developers forum for instructions on jailbreaking the Samsung Galaxy Gio.

2011-09-19 From iPhone to Android

This is part 6 of my series where I document going from an iPhone to Android 2.3, running on the Samsung Galaxy Gio.


Android and iOS both have home screens, but they really have a different purpose. In iOS, the home screen contains all apps. In Android, your apps are stored in the app drawer (usually the icon in the lower right corner). The Android home screen is much more flexible. It can contain a number of items, of which the most important are: widgets and shortcuts. Widgets are little apps that run on your home screen, showing the date/time, the weather or what have you. A shortcut is just a way of starting up an app via the home screen.

Thus on Android, you could easily have an empty home screen, if you don't mind starting apps with two taps (first go to the app drawer, then tap the app itself).

At first, the Android home screen is a bit confusing since there are so many possibilities compared to iOS. But there are many cool things here, such as the display of upcoming events in your calendar, right on the home screen:
CalWidget.

2011-09-15 From iPhone to Android

This is part 5 of my series where I document going from an iPhone to Android 2.3, running on the Samsung Galaxy Gio.

I have some problems with the stock music player. First, it'll show all my audiobooks right in between my music. This makes shuffle play worthless, and clutters the list of albums.

Another thing is skipping/pausing. Sometimes it happens that you want to skip a currently playing song when you're on the home screen or in some other app. The iPhone offers a convenient popup that offers basic player controls when you double-click the home button. I haven't found the equivalent under Android. It's certainly not built in, at least, if you don't count Android multitasking: hold down the home button and a popup appears with all running programs. You can now tap the music player and control it.

2011-09-13 From iPhone to Android

This is part 4 of my series where I document going from an iPhone to Android 2.3, running on the Samsung Galaxy Gio.

Another thing I found out; making screenshots is not something that's universal on all Android phones. It totally depends on the specific phone maker and model. My Gio supports it; holding the back button while pressing the home button will do the trick. For lots of other Android phones, there's no such thing.

Captive bugs

A particularly interesting thing about Android is, that there's an alternative in the Android Market for lots of standard apps. For texting and the calendar app, there are replacements.

That's necessary sometimes, because my Samsung Gio has the Captive "sauce" over the stock Android. There's something weird with the calendar app, where if you add an event, it's added by default to the local calendar. In other words, if you set up the phone to sync to your Google calendar, and you add an event, it doesn't show up on Google. How weird is that? Turns out this is an age-old issue, so after creating the event, you'll have to go back and change the calendar.

Accessories

A couple of days ago, I got a silicone case for the Gio, but it's not really to my liking so I ordered a hard case on eBay via the Totomagirl seller. This was surprisingly difficult to find. When iPhone cases are all over the place, some Android phones like the Gio clearly aren't that attractive to makers of accessories.

As opposed to the iPhone, it's really easy to swap out the battery of the Samsung Gio. So I also ordered a battery on DealExtreme (the Galaxy Ace and the Gio use the same batteries). The iPhone with its external batteries (or expensive add-on packs) is less than ideal in this regard.

2011-09-08 From iPhone to Android

This is part 3 of my series where I document going from an iPhone to Android 2.3, running on the Samsung Galaxy Gio.

Previous time, I charged the phone with a cheap charger in my car and at home, which resulted in touchpad unresponsiveness, only to be fixed with a reboot. I suspect the chargers and started using only the Samsung-supplied charger and my PC for juice. We'll see.

I've also started using doubleTwist (yeah, lower "d") for syncing Music from my MacBook to my Samsung Gio. It works fine, but I'm missing some iTunes features. A really useful feature for iPhones and iPods with less than 16 Gb is the option "Convert higher bitrate songs to 128 kbps AAC". doubleTwist currently doesn't support downconverting (also called transcoding) music to a lower bitrate.

As for apps, I haven't really missed anything between my iPhone and my Samsung Gio. All the biggies are here; Facebook, Kindle, Skype, an RSS reader which syncs with Google Reader, a number of SimpleNote clients; all there. There's a couple of extra apps that aren't available on iPhone such as the official Google Reader App.

What's nice as well is that Android apps seem to be able to control the phone, more so than on the iPhone platform. For instance, there are apps that can control the screen brightness with a very wide range. Useful stuff.

The Android Market for apps seems to attract developers who aren't in it for the money. A decent SSH client (tool for IT system administrators) for example is ConnectBot, open source and completely free. The equivalent in the iOS AppStore is TouchTerm, which costs $4. Other app markets for Android seem to be popping up as well; for instance there's the Amazon app store (only available for US residents, unfortunately).

2011-09-06 From iPhone to Android

This is part 2 of my series where I document going from an iPhone to Android 2.3, running on the Samsung Galaxy Gio.

Damnit. When I wake up this morning, something is wrong with the touchscreen. It seems to register touches, but the phone thinks you touched the screen somewhere else. I can't even turn off the phone, because although the power button is a real button at the side of the phone, it asks for confirmation on the screen. After a number of tries, I get it to power off.

That's not good, Samsung. The second day, I have to restart the Gio.

When I cycle to work, I start the media player and realize that there is no album art when you manually sync music to your phone. Which you are forced to do, because the OS X version of Kies doesn't support the Gio. Lifehacker tells me to take a look at DoubleTwist, which I may do later.

One of the weird things about Android is, that you have the home screen as well as the application screen. The home screen contains selected shortcuts to apps, if I can say so, while the application screen contains all apps. Why not just limit it to one screen? There must be some reason, but I haven't found it yet.

The Gio is a real nice, light phone. My iPhone 3G weighs 133 g (4.7 oz), and the Samsung Gio only weighs 102 g (3.6 oz). It's also 1.5mm thinner. Since I remembered dropping my iPhone in the first week after purchase, I ordered a cheap silicone casing on eBay store Cellapod.

Just before dinner, the phone warned me to recharge it, although I hadn't heavily used it. That's okay though, my iPhone 3G is very power hungry as well.

I did a little testdrive to see how the navigation is holding up (quite nicely) and charged the phone using my cheap USB car charger that I used for my iPhone as well. When I came home, the phone wouldn't react to screen touches, and when clicking the home button, a screenshot would be made.

Because the unresponsiveness this morning as well as this evening happened right after charging, I've come to the conclusion that this phone can't handle cheap chargers. I'm going to do some more testing, though.

2011-09-05 From iPhone to Android

Today, my iPhone 3G lost its reception in the middle of the city, and that wasn't the first time. Since this was a good time as any to experiment with Android, I figured I'd see what Google's ecosystem is doing, and I'm going to relate my experiences from my point of view: staunch GMail user, a heavy smartphone user, and one who is used to the Apple way of doing things. That means: a MacBook, iTunes and an iPhone.

Since I was limited on budget but still wanted the equivalent in specifications of an iPhone 3G, I went for the Samsung Galaxy Gio S5660:

http://upload.wikimedia.org/wikipedia/commons/thumb/c/c8/Samsung_Galaxy_Gio.JPG/450px-Samsung_Galaxy_Gio.JPG

Wikipedia has all the specs of this phone, but the gist is: an Android phone with a reasonable CPU, a nice 320×480 pixel display, a 3 megapixel cam and enough onboard memory as not to cause any trouble.

The Gio actually has a pretty bad history, as Koen Delvaux diligently blogs. Crashes related to WiFi usage, battery draining, et cetera. But since Samsung rolled out the new Android 2.3 version (nicknamed Gingerbread), these bugs are supposedly fixed.

The phone was advertised as having Android version 2.3, but when I asked the sales guy, it turns out it's still at 2.2 and you have to update it yourself.

Coming home, I discovered that updating the Gio is done through Samsung Kies, a program which runs on both OS X and Windows. Except the OS X version only supports four or five of Samsung's phones. Luckily, I have Parallels running on my Mac, so I can run Kies on a virtualized Windows. For the sake of Google, I repeat: if you have a Mac, you need Parallels in order to run Samsung Kies, because the version for OS X does not support the Galaxy Gio. Samsung Kies has a number of documented problems, for which MobileCowboys thoroughly provides workarounds. But perhaps I was lucky, because installing Kies and updating the phone to Android 2.3 worked fine.

Of course, this will mean a bit of a hassle in the future. Putting music on my iPhone is basically connecting the iPhone, starting iTunes on my Mac, then hit sync. Now I'll have to copy music manually to the phone.

After updating, I configured the phone with my GMail account and installed a bunch of apps (Kindle, Facebook, Skype, etc) that I already used on my iPhone. The configuration of GMail is flawless, even though I use a Google Apps account (I have had my own domain for years now, and mail is handled by GMail).

The iPod application of the iPhone is nice, and supports a sleep timer. I often fall asleep while listening to audiobooks. You need a separate app for this, and I was advised to take a look at MortPlayer. I haven't tried it yet.

2011-08-30 Creating specific password lists with John the Ripper

For security purposes, you sometimes want a dictionary with possible passwords. Perhaps you want to assess password strength in an in-house built application, or you want to test the security of the WPA wireless network.

There are plenty of dictionaries around to use to warn (or exclude) users from creating an account with such a password, or for the latter purpose, to brute force the WPA key.

After the standard dictionaries, a good next step is to create a password list for specific circumstances. For example; you are building an app for a company and want to make sure that easy passwords are excluded. You then need to build a list of passwords that any cracker would try.

There are good commercial products that can do this kind of stuff automatically, but for the poor man, it's easy enough to just get the stuff from the company website, or the Wikipedia article on them. With standard Unix/Linux tools, you're good to go.

I assume we create a directory in your home directory called "crack". First, use wget to get the source of words that shouldn't be a password:

 $ mkdir ~/crack
 $ cd ~/crack
 $ wget "http://nl.wikipedia.org/wiki/Rotterdam" -O wordlist.html

Then use sed to strip out the HTML:

 $ cat wordlist.html | sed -e :a -e 's/<[^>]*>//g;/</N;//ba' > wordlist.txt

Then edit the list with vi, and manually remove some JavaScript cruft at the bottom if necessary, then clean it up a bit.

First put each word on a separate line. The ^M indicates the Enter key. Create it by pressing CTRL+V, then pressing enter.

 :%s/ */^M/g

Remove extra whitespace:

 :%s/ *//g

Remove empty lines:

 :g/^$/d

Done. Now we're going to expand the wordlist with all sorts of word play that people might think of, like using years, or numbers, or using leet speak. John the Ripper by default has a nice set of mangling rules, but it's very limited.

Matt Weir has greatly enhanced the default set of mangling rules, and that's the one we'll be using.

Make sure you have a recent version of John the Ripper, because Matt's rules need it. Your Linux distribution will probably have an older version, so download and compile version 1.7.8 or something later. I'm assuming you'll put John in your home directory in opt, then add it to your path:

 $ mkdir ~/opt
 $ cd ~/opt
 $ wget http://www.openwall.com/john/g/john-1.7.8.tar.gz
 $ tar xfz john-1.7.8.tar.gz
 $ cd john-1.7.8/src
 $ make

(make now lists a number of targets, choose the best for your architecture and re-run make)

 $ make linux-x86-64

When finished, the executable and configuration file reside in $HOME/opt/john-1.7.8/run. Download Matt Weir's configuration file to this directory, but back up the original configuration first:

 $ cd ~/opt/john-1.7.8/run
 $ mv john.conf john.conf.bak
 $ wget http://sites.google.com/site/reusablesec/Home/john-the-ripper-files/john-the-ripper-sample-configs-1/john.conf?attredirects=0&d=1 -O john.conf

Since John the Ripper is used by us in WordList mode, we need to edit the configuration file so it uses the correct rules. Edit john.conf, and search for the line

 [List.rules:Wordlist]

(around line 335) and disable it by adding a few characters, for example

 [List.rules:WordlistXYZ]

Then find the following line:

 [List.rules:Modified_Single]

(around line 21), and change it to:

 [List.rules:Wordlist]

Now add John the Ripper to your path, by adding the following line to your ~/.bashrc:

 export PATH=$HOME/opt/john-1.7.8/run

Logout, and login again. Typing 'john' should print the version. Now mangle your newly tested word list. To make sure the new mangling rules are used, create a file called test.txt with only one line, for example 'rotterdam'. Then run john. It should look something like this:

 $ cd ~/crack
 $ vi test.txt
 $ john -w=test.txt --stdout --rules
 rotterdam
 rotter
 Rotterdam
 rotterdamrotterdam 
 ROTTERDAM
 rotte
 rotterdam1
 rotterdam2
 rotterdam3
 rotterdam4

(skipping a whole lot of lines)

 ROTtERDAm
 ROTTerdAm
 ROTTerDAm
 ROTTeRdAm
 ROTTeRDAm
 ROTTErdAm
 ROTTErDAm
 ROTTERdAm
 ROTTERDAm
 RoTTeRDaM
 Tpyyrtfs,
 Eirrwesan
 words: 4745  time: 0:00:00:00 100%  w/s: 39541  current: Eirrwesan
 $

As you can see, Matt's rules generate all sorts of permutations which you can then use in your software or brute forcing efforts. From one word, 4745 mutations have been created! If you see less (for example between 20 and 40), then the default rules have been used and there is something wrong with the configuration file. Perhaps john couldn't find it?

If this went okay, then re-run john on your custom wordlist:

 $ john -w=wordlist.txt --stdout --rules > wordlist_mangled.txt

Voilà, this resulting word list can now be used in your password strength assessment, or brute forcing efforts.

2011-08-18 Google Chrome on Debian Lenny

The latest packages of Google Chrome won't install anymore on Debian 5.0, nickname Lenny. This is the old stable release of Debian.

For the time being, I'm still using Lenny, so for everybody else, here is a link to the most recent release that still installed on Lenny:

Google Chrome

Note that this is somewhat of a security risk!

2011-06-20 Running yum on low end VPSes

Yum is a dog to run on older systems with small amounts of memory, or when you run a VPS (virtual private server) that does not have much memory. When running a yum update, you get errors such as memory alloc (8 bytes) returned NULL. Recommendations are:

  • Running yum clean all
  • Disabling plugins by using the --noplugins parameter

But ofthen that's not enough. A simple yum update can find a huge number of packages to update and nothing will help you getting the required amount of free memory. You can work around this problem by updating them one-by-one.

First generate a list of packages-to-be-updated, then dump them in a file.

 $ yum -q check-update | cut -f1 -d" " > list

Now use a loop that runs yum for each package listed in the file:

 $ for i in $(cat list); do sudo yum -y update "$i"; done

2011-06-17 Encrypting and decrypting files part 2

Some time ago, I gave a duo of one-liners that encrypts/decrypts files.

This wasn't good enough for normal usage, so here's a better version that takes care of directories as well by running tar on them.

  #!/bin/sh
  source="$1"
  if [ -d "$source" ]; then
      newfilename="$source".tar.encrypted
  else
      newfilename="$source".encrypted
  fi
  if [ -e "$newfilename" ]; then
      echo "ERROR: A filename with the name $newfilename already exists"
      exit 1
  fi
  if [ -d "$source" ]; then
      # Source is a directory, first tar it
      if [ -e "$source".tar ]; then
          echo "ERROR: A filename with the name $source.tar already exists"
          exit 1
      fi
      tar cf "$source".tar "$source"
      openssl des3 -salt -in "$source".tar -out "$newfilename"
      rm -f "$source".tar
  else
      openssl des3 -salt -in "$source" -out "$newfilename"
  fi

And its evil twin, decrypt:

 #!/bin/sh
  origfilename=$(basename "$1" .encrypted)
  if [ "$origfilename" == "$1" ]; then
      echo "ERROR: Encrypted files must have the .encrypted extension"
      exit 1
  fi
  if [ -e "$origfilename" ]; then
      echo "ERROR: A filename with the name $origfilename already exists"
      exit 1
  fi
  openssl des3 -d -salt -in "$1" -out "$origfilename"
  # Check if the output is tarred
  tarfilename="$origfilename"
  origfilename=$(basename "$origfilename" .tar)
  if [ "$origfilename" != "$tarfilename" ]; then
      # It was tarred
      if [ -e "$origfilename" ]; then
          echo "ERROR: A filename with the name $origfilename already exists"
          exit 1
      fi
      tar xf "$tarfilename"
      rm "$tarfilename"
  fi

Save them in your path somewhere and make them executable.

2011-05-19 App Store on OS X a disappointment so far

So far, the OS X AppStore couldn't be called 'wildly popular' since its inception on January this year. Regularly, I checked my installed apps for availability in the App Store, because it allows for such easy updating. Lo and behold, only fairly trivial apps are there, the following list is not available in the App Store:

  • Google software (Chrome, Sketchup)
  • Mozilla software (Firefox, Thunderbird)
  • Adobe software (Flash, Flash Builder CS5, Photoshop etc)
  • Microsoft software (Office, Messenger, Silverlight etc.)
  • OpenOffice
  • Microsoft Messenger
  • Microsoft Silverlight
  • Seashore (painting program)
  • Parallels
  • VLC
  • Skype
  • Calibre (an eBook converter)
  • XBench (a benchmark for OS X)
  • Vuze
  • KisMAC

Now I agree that stuff like a bittorrent client (Vuze) and a network sniffing tool (KisMAC) would probably be refused in the App Store. But all in all, the OS X App Store could be called a disappointment so far.

Note that the Opera browser (which contains a bittorrent client) is in the App Store.

2011-04-21 Not enough CPU time

The datarate of the Demux board of the Safari project is quite high for a desktop PC to work with, even considering the fact that the raw data is being thoroughly filtered before it reaches the PC running the Generic EGSE (the central part of the software, which allows the user to control electronics and plot output).

We've made some tweaks in the past to allow more breathing space, such as good optical GBit network adapters (we dumped cheap DLinks and bought some expensive Intel ones). A separate PC was installed to run a daemon, which concatenates the large amount of small ethernet packets into a couple of big CCSDS packets. We tweaked the kernel TCP buffers and the driver settings on both PCs.

The running problem with the EGSE was unresponsiveness. When retrieving packets and plotting them, the whole software stopped reacting to user input. The cause was that the plotting (and its computations) took a lot of time because computations run in the same thread as the plotting, and although the GUI gets time to accept user interaction at regular interval but this interval is too long. A solution is to allow the GUI to accept user interactions more often. We are testing this solution.

(What didn't help, was that several bugs popped up in the meantime. For instance, it's possible to create two plots for one packet type. What would happen, is that packets would get divided between plots, or packets would interrupt the plotting right after the first plot was drawn, but before the second plot was started. A colleague also found and fixed these.)

We brainstormed with the users, and one had experience with this type of situation. After talking, we had a proposal for a situation where the daemon would maintain a very large buffer in memory or on disk, and the EGSE would then lazily retrieve the buffer. Since the daemon runs on a separate PC, memory contention wouldn't be a problem.

safaridaemoncaching.png

We thought about this solution, but although it's a solution for other (older) programming models, we use the Qt library. The big idea of Qt is that it generates signals when an event happens, and you just write code (a "slot") that handles the signal. The Signal/Slot system of Qt provides an elegant and robust solution to manage a buffer which is filled in with packet coming from a daemon and is read lazily by the EGSE. We're testing this solution.

More...