Subscribe via RSS
29Jul/200

Burning 16-bit EPROMs with a Willem Programmer

This wasn't fun! I wanted to get Kickstart 1.3 on my Amiga 500 and so I purchased some M27C400 EPROMs from eBay with the thought that I could host two ROMs on them and make it switchable. The ROMs arrived and, to my stupidity, didn't fit in a standard Willem EPROM Programmer! Of course, a quick google prior to purchasing anything would've made this very apparent. Fortunately, there's a fix! You can buy a DIP42 16-bit EPROM Adapter for the Willem, and so I did.

DSC00241

Thanks to COVID, it took an extra-specially long time to arrive and, once it did, got thrown into the box'o'Amiga'junk as I'd put the whole lot on the backburner whilst working on the Ataris of late. This weekend I'd finally managed to wrangle some time together and sat down with my trusty XP laptop to program these chips.... It wasn't easy!

Input Voltages

I jumped head-first into the programming and failed a LOT with errors such as 0x00 and 0x11. Any search online will tell you that, when getting lots of 0x00 and 0x11 errors, it mainly indicates that you aren't supplying the correct programming voltage for your target chip. Check out the PDF Datasheet for the chip you want to burn, it'll specify a programming voltage that it requires to successfully understand and store the bits that you're about to throw at it.

For the M27C400, we need to provide 12.5v +/- 0.25v. The Willem has a blue trimmable capacitor (or is that a resistor) on the top-right of the board that you can adjust to get this voltage. I always recommend to use an external power supply with these programmers and so I have a plug-pack which happily provides 12v @ 2amps. To make sure we'll have a chance of programming this chip, switch to the Test H/W tab of the software and tick VPP. This is pin 1 of the E32 socket. If this has worked, then you'll have a red LED illuminated on the board. Now, check the voltage between pin 16 (GND) and pin 1 (VPP) of the socket.

DSC00252

Mine was reporting a value lower than 12v to start with and hence I had a few issues burning. With a multimeter in place, I tuned the trimmer on the top-right and got the value to 12.5v. Actually, it was probably 12.6... but 12.4 would also work as you just need to be in the range of 12.25 - 12.75.

Another test burn saw a new error... no more 0x00 and 0x11, it was now further into the chip, usually around 0x100?

Willem Software vs. Programmer Settings

Next up, we need to make sure that you can correctly control ALL address lines with your Willem Programmer. After fixing the initial errors above, I started getting new errors at random intervals into the burning process. The errors all seemed to indicate that there was an issue writing data after address 0x100. I'd only been using empty chips, so it couldn't be because they already had data on them. You can also check if the chips are empty prior to burning and I totally recommend doing this. Either way, after this new error was received, I chose to then read the chips and view the data:

amiga-repeat

What you're seeing above is the data read back from the chip. I wrote the rom (loaded from file) to the chip, got the error and then chose to read the data from the chip back into the application. 75% of the data on that screen is correct, but you'll notice that the data repeats itself from 0x100! It turns out that this was all due to the fact that my Willem was only controlling the first 8 address lines and leaving the rest low. I recommend you rig up an LED test harness as per below and control the address pins via the Test H/W tab on the Willem Software.

pcb-settings

After swapping chips around, checking continuity between the 4051s and reseating everything else, I came to the conclusion that there's incorrect information in the Standard Willem Programming Guide. If you scroll to page 5, you'll find information on the version of the software to use. It suggests that, based on jumper settings on the board, you can select if the board emulates a PCB3B or PCB3.5. If you've chosen PCB3B, then you'll need to use software version 0.97ja, as per my post from a long time ago, when I was failing with the same issue! Alternatively, if you've chosen PCB3.5, you need to use 0.98d6 or higher.

DSC00242

I looked at my board and checked out the jumpers that set the board emulation. Mine were actually individually set to 1-2 and 2-3 instead of both 1-2 or both 2-3. Hah... how did this thing ever work?! With these jumpers now set to 2-3 (aka PCB3B), I went back to version 0.97ja of the software and tried to get the full suite of address lines to light up via the Test H/W tab. No dice! If you browse to page 6 of the above PDF, you'll find that the jumpers should be set to 1-2 for PCB35 and 2-3 for PCB3B. Just for fun, I switched the jumpers to 1-2 and tested out version 0.98d6 of the burning software... still no dice! No permutation of checkboxes on the Test H/W tab would get a high signal on any of the address lines higher than A7! Up until this point in time, I'd only programmed 8-bit EPROMs, so I'd never noticed this.

I went ahead and played musical-chips. There's a chain of CD4015BE ICs that shift out the address lines, 3 of them with 1 each controlling 8 bits. It seemed plausible that there might have been a faulty connection between the first and second, but no amount of tinkering brought the address pins to life. I took a break as the frustration (or was that sadness) levels were elevating and sat down to a bit of TV. With a fresh mindset, I came back to the device, forgetting which settings were applied. Opening up version 0.97ja and testing address lines now worked!? Wait, the jumpers are set for PCB35... what the hell, it's backwards!? I opened 0.98d6 and it didn't work, so I set the jumpers back to 2-3 (for PCB3B) and it now 0.98d6 worked!? Urgh... so much time lost. So, TLDR; The PCB3B/PCB35 jumper settings are backwards on my Willem Programmer. Set them to 1-2 for PCB3B and 2-3 for PCB35!

Using the adapter

First up, there's two jumpers on the adapter to configure. The right-most controls the VPP/A21 line and needs to be set to 1-2 for M27C400 or M27C800 EPROMs. The other, in the middle of the board, I left at 1-2 as I don't know what it does.

DSC00244

Next up, there's a ribbon cable used to obtain more address pins from the main programming board. For the M27C400, the base E32 socket of the Willem actually produces enough address lines, so this ribbon cable isn't actually needed. Check out the diagram below to work out how the adapter is constructed. I've coloured the lines that are used on the M27C400 and 800, where you can see that the 400 doesn't use any of the wires from the header on the right.

dip42-adapter

If you're going to program an M27C800 or larger, you'll need to connect this. Simply make sure that pin A23 (bottom-most) lines up with A23 on the Willem board.

DSC00253

Finally, when inserting ANY chip into to the socket, make sure that the pins are all straight and that none will foul. For any of the chips, always make sure that the bottom pins are at the bottom end of the socket. The EPROMs are designed so that the Address lines extend 'north', so the silhouette on the board is actually for an M27C800. An M27C400 will be one pin lower than the silhouette, aligned at the bottom of the socket.

Conclusion

All the errors I made above would've been avoided if I'd known what I was doing from the start. Usually the old RTFM instruction makes sense, but when the jumper settings work in reverse to the manual, it's a little frustrating. On another note, I'd just trusted that this piece of hardware worked, as I've managed to burn other chips in the past. Assuming that cheap hardware just-works(tm) is a flaw in itself. Always make sure you know the hardware you're working with back-to-front, which was totally possible this time around thanks to open-source schematics being available!

7Apr/200

Mixing And Matching KVM Cables

Thanks to these new working-from-home shennanigans, I had to dig through the to-sell-at-trash-and-treasure box and find a KVM Switch to hook up all my machines. The desk used to only have one PC, but now additionally houses my work laptop and a large development server. I've got two monitors, and I want one of those to switch over to the development PC when required. I also want my keyboard and mouse to switch between all devices. To do this, I'll use the VGA/USB KVM to channel input and video to the monitor.

DSC00016

The best KVM in the box'o'stuff was a Belkin SOHO 4-port VGA+USB+Audio switch. Fortunately it had two of the KVM cables, so I could switch in my Laptop and development machine... but... I had to then unplug the USB to switch the input to the main PC. This wasn't optimal, so I went digging in the box again for another cable. The Belkin cables switched both Audio in+out, VGA and USB. From the outside, it looked like the audio channels were external to the VGA cable, unlike the USB which does seem to run through the same wires.

Back into the box, but unfortunately there were no other Belkin-branded cables. What I did find was another KVM with USB switching: Level One 2-Port VGA+USB KVM Switch.

DSC00033

DSC00013

This one also switched audio, but via USB instead of standard audio jacks. I grabbed a cable from it and was about to just plug everything together, but then I halted. USB has four wires, with two of those being the 5v rails. If there's any wiring differences between the switches then I could well send voltages in haphazard directions and cook equipment!

USB over VGA cable pinout

Wanting to hook this all together, I reached for my multimeter and started mapping out both the Level One and Belkin KVM cables. Thankfully, they have used very similar wiring for their cables! Thanks to monitors now talking digitally to hosts when describing their capabilities, there are spare pins on the cables to use for other purposes. There's a lot of extra ground pins also, but Belkin still preferred to keep those split whilst Level One happily merged them altogether.

DSC00019

Pin VGA Level One Cable Belkin Cable
1 R
2 G
3 B
4 ID2 / RES USB D- (White) USB D+ (Green)
5 GND USB D+ (Green) USB D- (White)
6 RGND USB GND + GND USB GND + RGND
7 GGND USB GND + GND GND
8 BGND USB GND + GND GND
9 KEY / 5v USB 5v + 5v USB 5v + 5v
10 SGND USB GND + GND GND
11 ID0 / RES
12 ID1 / SDA
13 HSYNC
14 VSYNC
15 ID3 / SCL

From above, you can see that the voltage (thanks to VGA actually having a +5v pin as standard!) and GND are close-enough, but the USB data pins are backwards. You assholes.. here I was thinking there might be a standard. What to do here? Well, I simply hacked apart a USB extension cable and crossed over the D+ and D-. Note that there is absolutely no reason for using this cable in any other scenario!

DSC00034

After this, one last hurdle.. the Level One KVM has a male plug at either end, whereas the Belkin cables have female on the KVM side.

DSC00028 DSC00023 DSC00032

And the bloody thing just worked! Happy to not have to spend money on extra proprietary cables that I actually couldn't find anywhere.

3Apr/200

Sony HDR-AS100V Action Cam – Further Hacking

After a LOT of mucking around, trying to get a stream URL from the Action Cam, I did further googling on the defunct UStream service that the camera could, supposedly, stream live to. It wasn't long before I came across this mammoth thread on Github describing a few users' attempts to hack apart the APK that does the steaming and to fake a server with the intent to intercept the feed.

They use the Sony-PMCA-RE Project code to tweak settings on the camera and then built up an entire webserver to fake the Ustream service. None of this was beyond me, so I started by trying to work out if I could configure my camera to point at a server... (note that I've fudged any secrets in the console logs below...)

C:\Users\Steven\Downloads>pmca-console-v0.17-win.exe info -d libusb
Using drivers libusb-MSC, libusb-MTP
Looking for Sony devices

Querying mass storage device
Sony Camcorder is a camera in mass storage mode

Model:              HDR-AS100V
Product code:       0002426601
Serial number:      03035253
Firmware version:   2.00
GPS Data:           2020-04-02 00:00:00 - 2020-05-02 00:00:00
C:\Downloads>pmca-console-v0.17-win.exe stream -d libusb
Using drivers libusb-MSC, libusb-MTP
Looking for Sony devices

Querying mass storage device
Sony Camcorder is a camera in mass storage mode

twitterEnabled:     0
twitterConsumerKey: o9fJ2342342423433qg
twitterConsumerSecret: TTL4324234234234324324llM6EQdivTXesA
twitterAccessToken1:
twitterAccessTokenSecret:
twitterMessage:     Live Streaming from Action Cam by Sony
facebookEnabled:    0
facebookAccessToken:
facebookMessage:    Live Streaming from Action Cam by Sony
service:            0
enabled:            1
macId:
macSecret:
macIssueTime:       0000000000000000
unknown:            1
channels:           [0]
shortURL:
videoFormat:        1
supportedFormats:   [1, 3]
enableRecordMode:   1
videoTitle:         Recorded with Action Cam by Sony
videoDescription:   Shot 100% with Sony's Action Cam #SonyActionCam #ProveYourself
videoTag:
C:\Downloads>pmca-console-v0.17-win.exe updatershell -d libusb
Using drivers libusb-MSC, libusb-MTP
Looking for Sony devices

Querying mass storage device
Sony Camcorder is a camera in mass storage mode

Getting device info
Using firmware for model HDR-AS100V

Initializing firmware update
Traceback (most recent call last):
  File "C:\projects\sony-pmca-re\pmca-console.py", line 96, in <module>
  File "C:\projects\sony-pmca-re\pmca-console.py", line 82, in main
  File "C:\projects\sony-pmca-re\pmca\commands\usb.py", line 360, in updaterShellCommand
  File "C:\projects\sony-pmca-re\pmca\commands\usb.py", line 373, in firmwareUpdateCommandInternal
  File "C:\projects\sony-pmca-re\pmca\usb\sony.py", line 527, in checkGuard
  File "C:\projects\sony-pmca-re\pmca\usb\sony.py", line 501, in _sendWriteCommands
Exception: Firmware update error: Low battery
[9052] Failed to execute script pmca-console

Right... low battery... I charged it up and then tried again:

C:\Users\Steven\Downloads>pmca-console-v0.17-win.exe updatershell -d libusb
Using drivers libusb-MSC, libusb-MTP
Looking for Sony devices

Querying mass storage device
Sony Camcorder is a camera in mass storage mode

Getting device info
Using firmware for model HDR-AS100V

Initializing firmware update
Switching to updater mode

Waiting for camera to switch...
Please follow the instructions on the camera screen.

DSC04100

Cool, at this point the camera showed 'EXEC' on its LCD display. This kept flashing... and then the console app error'd out:

Operation timed out. Please run this command again when your camera has connected.

I pressed the record button on the back of the camera to try and force the 'EXEC' and it seemed to reboot. From here, it then wouldn't even come back to life. The battery charge light just stayed a fixed red, which I now think was an error indicator. After popping the battery out, I could get it back to the 'EXEC' screen via the console appm but it occurred to me that I actually had no idea what updater-shell even did. Instead I tried the stream fuction to push a UStream configuration file to the device as per the instructions here at Novex's project where he attempts to create his own UStream APK. Here's the config I used...

[
    [
        "twitterEnabled",
        0
    ],
    [
        "twitterConsumerKey",
        ""
    ],
    [
        "twitterConsumerSecret",
        ""
    ],
    [
        "twitterAccessToken1",
        ""
    ],
    [
        "twitterAccessTokenSecret",
        ""
    ],
    [
        "twitterMessage",
        "Live Streaming from Action Cam by Sony"
    ],
    [
        "facebookEnabled",
        0
    ],
    [
        "facebookAccessToken",
        ""
    ],
    [
        "facebookMessage",
        "Live Streaming from Action Cam by Sony"
    ],
    [
        "service",
        0
    ],
    [
        "enabled",
        1
    ],
    [
        "macId",
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    ],
    [
        "macSecret",
        "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
    ],
    [
        "macIssueTime",
        "dbeaaaaa00000000"
    ],
    [
        "unknown",
        1
    ],
    [
        "channels",
        [
            12345678
        ]
    ],
    [
        "shortURL",
        "http://www.ustream.tv/channel/asdf"
    ],
    [
        "videoFormat",
        3
    ],
    [
        "supportedFormats",
        [
            1,
            3
        ]
    ],
    [
        "enableRecordMode",
        0
    ],
    [
        "videoTitle",
        "Recorded with Action Cam by Sony"
    ],
    [
        "videoDescription",
        "Shot 100% with Sony's Action Cam #SonyActionCam #ProveYourself"
    ],
    [
        "videoTag",
        ""
    ]
]

And then I smushed it into the camera using the console application...

C:\Downloads>pmca-console-v0.17-win.exe stream -w ustream.json -d libusb
Using drivers libusb-MSC, libusb-MTP
Looking for Sony devices

Querying mass storage device
Sony Camcorder is a camera in mass storage mode

Urrghh... what mode does it need to be in? Wait, is that even an error? Let's try use the buttons on the camera to switch it into Live mode...

DSC04105

Preparing?

DSC04103

No AP? Maybe it has no idea how to connect to a 5Ghz access point? Let's set it to my 2.4G secondary network, which nothing else uses, so I can isolate and watch any traffic. This took a little bit of hacking as the Sony Network Settings Utility needs the driver to be reverted back to USB Mass Storage. Jump in to Device Manager and force a driver 'update' back to the standard driver.

reset-to-usb-mass-storage

Now you can go into Network Settings and set your AP once more.

net-set-tool-1

wifi-connection incomplete-settings incomplete-settings-2

Note that it'll complain at the end that the settings are incomplete. Fortunately it still writes the AP configuration to the camera. After this, you'll have to switch back to the lib-usb driver and then re-upload the streaming settings.

remap-usb-driver-1 remap-usb-driver-2 remap-usb-driver-3

From here? Test! Unplug the camera and hit the Live mode again...

DSC04111

Auth Error? HDR-AS100V Official documentation indicates that this means it's failed to authenticate with UStream... makes sense to me! Can we hack my local network to make it auth to a fake server?

Network Setup for a Fake UStream Server

Since I want the raspi to be doing all the heavy lifting, I'm going to follow the instructions here at Luca Lanziani's project to set up a listener and a dns faker.

pi@raspberrypi:~ $ wget https://github.com/LucaLanziani/sony-camera-stream-receiver/archive/master.zip
--2020-04-03 16:01:51--  https://github.com/LucaLanziani/sony-camera-stream-receiver/archive/master.zip
Resolving github.com (github.com)... 13.237.44.5
Connecting to github.com (github.com)|13.237.44.5|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://codeload.github.com/LucaLanziani/sony-camera-stream-receiver/zip/master [following]
--2020-04-03 16:01:51--  https://codeload.github.com/LucaLanziani/sony-camera-stream-receiver/zip/master
Resolving codeload.github.com (codeload.github.com)... 52.63.100.255
Connecting to codeload.github.com (codeload.github.com)|52.63.100.255|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3790 (3.7K) [application/zip]
Saving to: ‘master.zip’

master.zip                                  100%[===========================>]   3.70K  --.-KB/s    in 0s      

2020-04-03 16:01:51 (7.39 MB/s) - ‘master.zip’ saved [3790/3790]

It downloaded easy enough... let's see what's inside!

pi@raspberrypi:~ $ mv master.zip sony-camera-receiver.zip
pi@raspberrypi:~ $ unzip sony-camera-receiver.zip 
Archive:  sony-camera-receiver.zip
dd4b1170f9cdeaabc2cb68eccff6b7f63214abce
   creating: sony-camera-stream-receiver-master/
 extracting: sony-camera-stream-receiver-master/.gitignore  
  inflating: sony-camera-stream-receiver-master/README.md  
  inflating: sony-camera-stream-receiver-master/dns_server.js  
  inflating: sony-camera-stream-receiver-master/index.js  
  inflating: sony-camera-stream-receiver-master/package.json  
  inflating: sony-camera-stream-receiver-master/rtmp_server.js  
  inflating: sony-camera-stream-receiver-master/web_server.js  
pi@raspberrypi:~ $ cd sony-camera-stream-receiver-master/
pi@raspberrypi:~/sony-camera-stream-receiver-master $ ls
dns_server.js  index.js  package.json  README.md  rtmp_server.js  web_server.js

Cool, everything we need. Following the instructions, we first need to download all the supporting node libraries.

pi@raspberrypi:~/sony-camera-stream-receiver-master $ npm i
npm WARN npm npm does not support Node.js v10.15.2
npm WARN npm You should probably upgrade to a newer version of node as we
npm WARN npm can't make any promises that npm will work with this version.
npm WARN npm Supported releases of Node.js are the latest release of 4, 6, 7, 8, 9.
npm WARN npm You can find the latest version at https://nodejs.org/
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN sony-cameras-stream-receiver@1.0.0 No repository field.

added 76 packages from 64 contributors in 17.452s

Cool, done. Now it's time to see if it'll actually run?

pi@raspberrypi:~/sony-camera-stream-receiver-master $ sudo node ./index.js --web.channel_number=12345678 --dns.external_ip=192.168.1.50
03/04/2020 16:09:24 16471 [INFO] Node Media Server v1.4.7
  dnsproxy:info we are up and listening at 192.168.1.50 on 53 +0ms
03/04/2020 16:09:24 16471 [INFO] Node Media Rtmp Server started on port: 1935
Server listening at http://0.0.0.0:80/
03/04/2020 16:09:26 16471 [INFO] There is a new version 2.1.6 that can be updated

Okey dokey.. it's up and running. The best thing we can do now is slap the new DNS server in our config and see what happens. After configuring my router and letting it reboot, nothing was resolving via .50. It seemed that .1 was still the DNS Server? I tried renewing my DHCP and flushing DNS, but nothing worked. Intead, I just hacked my local ethernet interface to point to .50 for DNS... woah! It works!

dhcp-1 dhcp-2 dhcp-3

But I can't hack the camera in the same way... so I need to work out why DHCP isn't giving me .50. I then realised I'd been reconfiguring my repeater and not my actual router!? I logged into .1 and set the DHCP configuration to have a Primary DNS of .50 and, well, shit just worked.

The camera still threw the Auth Error, so I totally disabled the DHCP on the repeater, because that really shouldn't be there, and rebooted the camera. Hitting the REC button on Live mode on the Action Cam then saw action on the raspi!

03/04/2020 16:58:47 16471 [INFO] [rtmp connect] id=SRMCPZ41 ip=::ffff:192.168.1.134 app=12345678 args={"app":"12345678","flashVer":"LNX 9,0,124,0","swfUrl":"http://www.ustream.tv/mobile.swf","tcUrl":"rtmp://api.ustream.tv/12345678","capabilities":1,"audioCodecs":1143,"videoCodecs":252,"videoFunction":1,"pageUrl":"http://www.ustream.tv","objectEncoding":0,"fpad":false}
03/04/2020 16:58:47 16471 [INFO] [rtmp publish] New stream. id=SRMCPZ41 streamPath=/12345678/broadcaster/live7665 streamId=1
03/04/2020 16:58:48 16471 [INFO] [rtmp publish] Handle video. id=SRMCPZ41 streamPath=/12345678/broadcaster/live7665 frame_type=1 codec_id=7 codec_name=H264 0x0
03/04/2020 16:58:48 16471 [INFO] [rtmp publish] Handle audio. id=SRMCPZ41 streamPath=/12345678/broadcaster/live7665 sound_format=10 sound_type=2 sound_size=1 sound_rate=3 codec_name=AAC 48000 2ch

03/04/2020 16:59:40 16471 [INFO] [rtmp connect] id=7YV3W0L2 ip=::ffff:192.168.1.121 app=12345678/broadcaster args={"app":"12345678/broadcaster","flashVer":"LNX 9,0,124,2","tcUrl":"rtmp://192.168.1.50:1935/12345678/broadcaster","fpad":false,"capabilities":15,"audioCodecs":4071,"videoCodecs":252,"videoFunction":1}
03/04/2020 16:59:40 16471 [INFO] [rtmp play] Join stream. id=7YV3W0L2 streamPath=/12345678/broadcaster/live7665  streamId=1

And then pointing VLC at rtmp://192.168.1.50/12345678/broadcaster/live7665 got me....

traiiiinnnnssss

Traaaaaiinnnsssss... Now to find the perfect angle. Note that you get an 'ONAIR' message on the LCD when it's ... on air.

DSC00001

Sure, not the best mounting... I'll work on that. I've also got a waterproof case for it that I'll need to hack a hole into to get the USB power through.

Productionising

Having the raspi be the DNS server is a little scary. But I suppose this is where secondary DNS servers come in? If we put 8.8.8.8 for the secondary, does it get used when the primary is down? Turns out it doesn't work that way, so I'll need to keep the raspi up and running 100% of the time!

C:\Users\Steven>ipconfig /all
Ethernet adapter Ethernet:
   Connection-specific DNS Suffix  . :
   Description . . . . . . . . . . . : Intel(R) Ethernet Connection (2) I219-V
   Physical Address. . . . . . . . . : 30-5A-3A-81-E1-1B
   DHCP Enabled. . . . . . . . . . . : Yes
   Autoconfiguration Enabled . . . . : Yes
   IPv4 Address. . . . . . . . . . . : 192.168.1.121(Preferred)
   Subnet Mask . . . . . . . . . . . : 255.255.255.0
   Default Gateway . . . . . . . . . : 192.168.1.1
   DHCP Server . . . . . . . . . . . : 192.168.1.1
   DNS Servers . . . . . . . . . . . : 192.168.1.50
                                       8.8.8.8
   NetBIOS over Tcpip. . . . . . . . : Enabled
C:\Users\Steven>ipconfig /flushdns
Windows IP Configuration
Successfully flushed the DNS Resolver Cache.
C:\Users\Steven>nslookup google.com
DNS request timed out.
    timeout was 2 seconds.
Server:  UnKnown
Address:  192.168.1.50

DNS request timed out.
    timeout was 2 seconds.
DNS request timed out.
    timeout was 2 seconds.
DNS request timed out.
    timeout was 2 seconds.
DNS request timed out.
    timeout was 2 seconds.
*** Request to UnKnown timed-out

For some reason I really enjoy the spelling of 'UnKnown'. Regardless, the secondary has not kicked in! With this news, I suppose it'd be best to learn how to keep that service up and running 24/7 on the pi. Instead, I have decided to not even go ahead with this camera! It's wide-view fish-eye angle just isn't wide enough to get the trains close enough and the cityscape in the shot... I therefore don't see any advantage over my current camera.

So! A learning experience! For those who want to use a Sony Action Cam HDR-AS100V as a livestreaming camera, it's plausible!

3Apr/200

Streaming from a Sony HDR-AS100V Action Cam

This is a cool camera. I picked it up from the Hard Off in Kagoshima whilst I was visiting the southernmost JR Railway station in Japan. I then used it to take a miriad of videos, especially from the balcony at the apartment in Shin Osaka.

DSC04151

DSC04152 DSC04153 DSC04156

Whilst it works great recording by itself, you can also use the Play Mobile (now Imaging Edge Mobile) application from Sony to control it remotely. The application uses the phones Wifi to connect to the camera, which acts as a wifi access point. From there, it shows a live view of what the camera can see whilst you're trying to line up the next shot. You can even control the camera remotely with the buttons on screen, being able to start and stop recording very easily... especially without bumping the camera!

Screenshot 20200331-143248

I was hoping Sony would release the same software for the PC and allow me to connect wirelessly to the camera. This would then let me sniff the traffic and get the video stream live link, but alas, it's only available for mobile devices. Wait!, you say, there is a version of Play Memories for windows! Sure, go ahead and download it... install it even! It'll connect to your camera, but there's no ability to see the live stream from the lens... I tried... trust me....

camera-options actioncam-import streaming-tool

With the USB camera connected, you'll get the screens above. They let you update firmware and manage settings, but not much else. Of course, there's also the file transfer functionality to let you download the images and videos you've taken. Interestingly, the last image shows the second pane of the Network Settings tab which allows you to configure the camera to upload to UStream automatically. Turns out that this was a 'thing' back in the day and not really practical anymore: IBM bought UStream and the service is no longer free... nor could I get it to work, even with a trial account.

Can we create a network to inspect the Wifi?

So, I had a plan. I have a Vonets VAR11N-300 that allows an ethernet cabled device to access an access point over Wifi. With this, I could use an ethernet port on my laptop to connect to the camera. The Wifi adapter in the laptop could then create an ad-hoc network that my phone could connect to. If I bridged the ethernet to the Wifi, connected my phone ad-hoc to the wifi network created by the laptop, then I could possibly pass the traffic through the laptop and use wireshark to listen in?

s-l1600

As I was plugging this all together, traffic started flowing without client application requesting anything! I'd not yet bridged the adapters, but I'd installed Wireshark and had the VAR11N-300 connected to the camera and, all of a sudden, I'd noticed jitters on the traffic graph for the ethernet interface on the front page of Wireshark. The ethernet port seems to have received an IP from the camera since it was acting as a terminating host and not an ethernet bridge! For everyones information: the IP range is 196.168.0.x, with IPs dished out from 1 and the camera being 192.168.122.1. With the little graph jumping around, I decided to listen in and have a look!

ws-1 ws-2 upnp-xml

Ok so, from left to right above... the Wireshark main screen with my miriad of interfaces. You may notice that I have Wifi-2 selected there and there's traffic flowing on it... At this point, realising that I didn't need the mobile application to trigger data flow from the camera, I'd slapped a USB Wifi dongle into my main desktop so that I could work in comfort. The second screen is the main window of Wireshark once you're sniffing. You'll see a huge list of traffic up the top. Wireshark, without filters, will just present everything it can possibly see. Each row is a packet sent over the Wifi and you may need to combine multiple rows to see an entire picture, depending on what you're looking for. Usually, if this was my main network interface, I'd use a filter at the top with either ip.dest == 192.x.x.x or ip.src == 192.x.x.x to limit the displayed data, but fortunately this interface was only talking to the camera.

The second shot there has a very interesting packet displayed. It turns out that the camera is broadcasting UPnP information, telling anyone listening on the immediate network how to find it! No wonder, once you've connected your phone to the camera, that it can find it. It doesn't even have to look as the camera just yells it out every few seconds. Let's check out the data we're receiving...

<?xml version="1.0" encoding="utf-8"?>
<root xmlns="urn:schemas-upnp-org:device-1-0" xmlns:dlna="urn:schemas-dlna-org:device-1-0" xmlns:av="urn:schemas-sony-com:av">
	<specVersion>
		<major>1</major>
		<minor>0</minor>
	</specVersion>
	<device>
		<dlna:X_DLNADOC xmlns:dlna="urn:schemas-dlna-org:device-1-0">DMS-1.50</dlna:X_DLNADOC>
		<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>
		<friendlyName>HDR-AS100V</friendlyName>
		<manufacturer>Sony Corporation</manufacturer>
		<manufacturerURL>http://www.sony.com/</manufacturerURL>
		<modelDescription>SonyDigitalMediaServer</modelDescription>
		<modelName>SonyImagingDevice</modelName>
		<modelURL>http://www.sony.net/</modelURL>
		<UDN>uuid:00000000-0005-0010-8000-fcc2de658d5e</UDN>
		<serviceList>
			<service>
				<serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType>
				<serviceId>urn:upnp-org:serviceId:ContentDirectory</serviceId>
				<SCPDURL>/CdsDesc.xml</SCPDURL>
				<controlURL>/upnp/control/ContentDirectory</controlURL>
				<eventSubURL>/upnp/event/ContentDirectory</eventSubURL>
			</service>
			<service>
				<serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType>
				<serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId>
				<SCPDURL>/CmsDesc.xml</SCPDURL>
				<controlURL>/upnp/control/ConnectionManager</controlURL>
				<eventSubURL>/upnp/event/ConnectionManager</eventSubURL>
			</service>
			<service>
				<serviceType>urn:schemas-sony-com:service:ScalarWebAPI:1</serviceType>
				<serviceId>urn:schemas-sony-com:serviceId:ScalarWebAPI</serviceId>
				<SCPDURL>/ScalarWebApiDesc.xml</SCPDURL>
				<controlURL/>
				<eventSubURL/>
			</service>
		</serviceList>
		<iconList>
			<icon>
				<mimetype>image/jpeg</mimetype>
				<width>48</width>
				<height>48</height>
				<depth>24</depth>
				<url>/DLNA_camera_48.jpg</url>
			</icon>
			<icon>
				<mimetype>image/jpeg</mimetype>
				<width>120</width>
				<height>120</height>
				<depth>24</depth>
				<url>/DLNA_camera_120.jpg</url>
			</icon>
			<icon>
				<mimetype>image/png</mimetype>
				<width>48</width>
				<height>48</height>
				<depth>24</depth>
				<url>/DLNA_camera_48.png</url>
			</icon>
			<icon>
				<mimetype>image/png</mimetype>
				<width>120</width>
				<height>120</height>
				<depth>24</depth>
				<url>/DLNA_camera_120.png</url>
			</icon>
		</iconList>
		<av:X_ScalarWebAPI_DeviceInfo xmlns:av="urn:schemas-sony-com:av">
			<av:X_ScalarWebAPI_Version>1.0</av:X_ScalarWebAPI_Version>
			<av:X_ScalarWebAPI_ServiceList>
				<av:X_ScalarWebAPI_Service>
					<av:X_ScalarWebAPI_ServiceType>guide</av:X_ScalarWebAPI_ServiceType>
					<av:X_ScalarWebAPI_ActionList_URL>http://192.168.122.1:10000/sony</av:X_ScalarWebAPI_ActionList_URL>
					<av:X_ScalarWebAPI_AccessType />
				</av:X_ScalarWebAPI_Service>
				<av:X_ScalarWebAPI_Service>
					<av:X_ScalarWebAPI_ServiceType>accessControl</av:X_ScalarWebAPI_ServiceType>
					<av:X_ScalarWebAPI_ActionList_URL>http://192.168.122.1:10000/sony</av:X_ScalarWebAPI_ActionList_URL>
					<av:X_ScalarWebAPI_AccessType />
				</av:X_ScalarWebAPI_Service>
				<av:X_ScalarWebAPI_Service>
					<av:X_ScalarWebAPI_ServiceType>camera</av:X_ScalarWebAPI_ServiceType>
					<av:X_ScalarWebAPI_ActionList_URL>http://192.168.122.1:10000/sony</av:X_ScalarWebAPI_ActionList_URL>
					<av:X_ScalarWebAPI_AccessType />
				</av:X_ScalarWebAPI_Service>
			</av:X_ScalarWebAPI_ServiceList>
			<av:X_ScalarWebAPI_ImagingDevice>
				<av:X_ScalarWebAPI_LiveView_URL>http://192.168.122.1:60152/liveview.JPG?%211234%21http%2dget%3a%2a%3aimage%2fjpeg%3a%2a%21%21%21%21%21</av:X_ScalarWebAPI_LiveView_URL>
				<av:X_ScalarWebAPI_DefaultFunction>RemoteShooting</av:X_ScalarWebAPI_DefaultFunction>
			</av:X_ScalarWebAPI_ImagingDevice>
		</av:X_ScalarWebAPI_DeviceInfo>
	</device>
</root>

Oooowwweee... Check that link at the bottom!? X_ScalarWebAPI_LiveView_URL? Really? Can it be this easy? I slapped it into a browser and no... it's not this easy... the browser just doesn't respond as it's constantly being fed a stream of data and doesn't know what to do with it! Turns out this stream is a motion-jpeg or some frame-by-frame binary flow that isn't a standard video stream. So, what to do? Google the hell out of those keywords and see who has already come to our rescue!

First hit was a post on stackoverflow asking 'How to get Sony ScalarWebAPI method list'. From the responses, one person suggested to send the camera JSON via a post so that you can see what functions you can call. One such function is getMethodTypes and can be called via the following post body:

{"method": "getMethodTypes", "params": [""], "id": 1, "version": "1.0"}

posty

Using Postman, I got the following result...

{
    "id": 1,
    "results": [
        [
            "actTakePicture",
            [],
            [
                "string*"
            ],
            "1.0"
        ],
        [
            "getApplicationInfo",
            [],
            [
                "string",
                "string"
            ],
            "1.0"
        ],
        [
            "getAvailableApiList",
            [],
            [
                "string*"
            ],
            "1.0"
        ],
        [
            "getAvailableCameraFunction",
            [],
            [
                "string",
                "string*"
            ],
            "1.0"
        ],
        [
            "getAvailableMovieQuality",
            [],
            [
                "string",
                "string*"
            ],
            "1.0"
        ],
        [
            "getAvailableShootMode",
            [],
            [
                "string",
                "string*"
            ],
            "1.0"
        ],
        [
            "getAvailableSteadyMode",
            [],
            [
                "string",
                "string*"
            ],
            "1.0"
        ],
        [
            "getCameraFunction",
            [],
            [
                "string"
            ],
            "1.0"
        ],
        [
            "getEvent",
            [
                "bool"
            ],
            [
                "{\"type\":\"string\", \"names\":\"string*\"}",
                "{\"type\":\"string\", \"cameraStatus\":\"string\"}",
                "{\"type\":\"string\", \"zoomPosition\":\"int\", \"zoomNumberBox\":\"int\", \"zoomIndexCurrentBox\":\"int\", \"zoomPositionCurrentBox\":\"int\"}",
                "{\"type\":\"string\", \"liveviewStatus\":\"bool\"}",
                "{\"type\":\"string\", \"liveviewOrientation\":\"string\"}",
                "{\"type\":\"string\", \"takePictureUrl\":\"string*\"}*",
                "{\"type\":\"string\", \"continuousError\":\"string\", \"isContinued\":\"bool\"}*",
                "{\"type\":\"string\", \"triggeredError\":\"string*\"}",
                "{\"type\":\"string\", \"sceneRecognition\":\"string\", \"steadyRecognition\":\"string\", \"motionRecognition\":\"string\"}",
                "{\"type\":\"string\", \"formatResult\":\"string\"}",
                "{\"type\":\"string\", \"storageID\":\"string\", \"recordTarget\":\"bool\", \"numberOfRecordableImages\":\"int\", \"recordableTime\":\"int\", \"storageDescription\":\"string\"}*",
                "{\"type\":\"string\", \"currentBeepMode\":\"string\", \"beepModeCandidates\":\"string*\"}",
                "{\"type\":\"string\", \"currentCameraFunction\":\"string\", \"cameraFunctionCandidates\":\"string*\"}",
                "{\"type\":\"string\", \"currentMovieQuality\":\"string\", \"movieQualityCandidates\":\"string*\"}",
                "{\"type\":\"string\", \"checkAvailability\":\"bool\", \"currentAspect\":\"string\", \"currentSize\":\"string\"}",
                "{\"type\":\"string\", \"cameraFunctionResult\":\"string\"}",
                "{\"type\":\"string\", \"currentSteadyMode\":\"string\", \"steadyModeCandidates\":\"string*\"}",
                "{\"type\":\"string\", \"currentViewAngle\":\"int\", \"viewAngleCandidates\":\"int*\"}",
                "{\"type\":\"string\", \"currentExposureMode\":\"string\", \"exposureModeCandidates\":\"string*\"}",
                "{\"type\":\"string\", \"currentPostviewImageSize\":\"string\", \"postviewImageSizeCandidates\":\"string*\"}",
                "{\"type\":\"string\", \"currentSelfTimer\":\"int\", \"selfTimerCandidates\":\"int*\"}",
                "{\"type\":\"string\", \"currentShootMode\":\"string\", \"shootModeCandidates\":\"string*\"}",
                "{\"type\":\"string\", \"currentAELock\":\"bool\", \"aeLockCandidates\":\"bool*\"}",
                "{\"type\":\"string\", \"checkAvailability\":\"bool\", \"currentBracketShootMode\":\"string\", \"currentBracketShootModeOption\":\"string\"}",
                "{\"type\":\"string\", \"checkAvailability\":\"bool\", \"currentCreativeStyle\":\"string\", \"currentCreativeStyleContrast\":\"int\", \"currentCreativeStyleSaturation\":\"int\", \"currentCreativeStyleSharpness\":\"int\"}",
                "{\"type\":\"string\", \"currentExposureCompensation\":\"int\", \"maxExposureCompensation\":\"int\", \"minExposureCompensation\":\"int\", \"stepIndexOfExposureCompensation\":\"int\"}",
                "{\"type\":\"string\", \"currentFlashMode\":\"string\", \"flashModeCandidates\":\"string*\"}",
                "{\"type\":\"string\", \"currentFNumber\":\"string\", \"fNumberCandidates\":\"string*\"}",
                "{\"type\":\"string\", \"currentFocusMode\":\"string\", \"focusModeCandidates\":\"string*\"}",
                "{\"type\":\"string\", \"currentIsoSpeedRate\":\"string\", \"isoSpeedRateCandidates\":\"string*\"}",
                "{\"type\":\"string\", \"checkAvailability\":\"bool\", \"currentPictureEffect\":\"string\", \"currentPictureEffectOption\":\"string\"}",
                "{\"type\":\"string\", \"isShifted\":\"bool\"}",
                "{\"type\":\"string\", \"currentShutterSpeed\":\"string\", \"shutterSpeedCandidates\":\"string*\"}",
                "{\"type\":\"string\", \"checkAvailability\":\"bool\", \"currentWhiteBalanceMode\":\"string\", \"currentColorTemperature\":\"int\"}",
                "{\"type\":\"string\", \"currentSet\":\"bool\", \"currentTouchCoordinates\":\"double*\"}"
            ],
            "1.0"
        ],
        [
            "getMethodTypes",
            [
                "string"
            ],
            [
                "string",
                "string*",
                "string*",
                "string"
            ],
            "1.0"
        ],
        [
            "getMovieQuality",
            [],
            [
                "string"
            ],
            "1.0"
        ],
        [
            "getShootMode",
            [],
            [
                "string"
            ],
            "1.0"
        ],
        [
            "getSteadyMode",
            [],
            [
                "string"
            ],
            "1.0"
        ],
        [
            "getStorageInformation",
            [],
            [
                "{\"storageID\":\"string\", \"recordTarget\":\"bool\", \"numberOfRecordableImages\":\"int\", \"recordableTime\":\"int\", \"storageDescription\":\"string\"}*"
            ],
            "1.0"
        ],
        [
            "getSupportedCameraFunction",
            [],
            [
                "string*"
            ],
            "1.0"
        ],
        [
            "getSupportedMovieQuality",
            [],
            [
                "string*"
            ],
            "1.0"
        ],
        [
            "getSupportedShootMode",
            [],
            [
                "string*"
            ],
            "1.0"
        ],
        [
            "getSupportedSteadyMode",
            [],
            [
                "string*"
            ],
            "1.0"
        ],
        [
            "getVersions",
            [],
            [
                "string*"
            ],
            "1.0"
        ],
        [
            "setCameraFunction",
            [
                "string"
            ],
            [
                "int"
            ],
            "1.0"
        ],
        [
            "setMovieQuality",
            [
                "string"
            ],
            [
                "int"
            ],
            "1.0"
        ],
        [
            "setShootMode",
            [
                "string"
            ],
            [
                "int"
            ],
            "1.0"
        ],
        [
            "setSteadyMode",
            [
                "string"
            ],
            [
                "int"
            ],
            "1.0"
        ],
        [
            "startIntervalStillRec",
            [],
            [
                "int"
            ],
            "1.0"
        ],
        [
            "startLiveview",
            [],
            [
                "string"
            ],
            "1.0"
        ],
        [
            "startMovieRec",
            [],
            [
                "int"
            ],
            "1.0"
        ],
        [
            "stopIntervalStillRec",
            [],
            [
                "int"
            ],
            "1.0"
        ],
        [
            "stopLiveview",
            [],
            [
                "int"
            ],
            "1.0"
        ],
        [
            "stopMovieRec",
            [],
            [
                "string"
            ],
            "1.0"
        ]
    ]
}

So, there's a startLiveview command down the bottom there... maybe I need to call this prior to trying to actually display the URL in a browser? It easily be called via Postman with the body:

{"method": "startLiveview","params" : [],"id" : 1,"version" : "1.0"}

You'll get the following result...

{
    "id": 1,
    "result": [
        "http://192.168.122.1:60152/liveview.JPG?%211234%21http%2dget%3a%2a%3aimage%2fjpeg%3a%2a%21%21%21%21%21"
    ]
}

We knew this link already from the XML above, but does the video now work? I slapped the URL into the browser but didn't have much luck. So... a little more digging and I found the following: Tony Jan's work on decode the Sony stream written in Ruby, kazyx's kz-remote-api written in C# and twenzel's version called SonyCameraRemoteControl also in C#.Finally, with the term 'liveview' added to my search, I hit cryptofuture's liveView project which contained binaries to run really easy on Windows.

livecam-py camera-shot-1 camera-shot-3

Horrible image quality... but we have it! A livestream from the HDR-AS100V! I wrapped it up at that point, since you can see it was past midnight.

Getting this out to the world!

Nice, we have a connection. Next step? Pipe this to Youtube. I was going to use a Raspberry Pi, so Ruby will an easy choice and therefore Tony's library was tested. Gem is installed by default...

pi@raspberrypi:~ $ sudo gem install sonycam
Fetching: thor-1.0.1.gem (100%)
Successfully installed thor-1.0.1
Fetching: sonycam-1.3.2.gem (100%)
Successfully installed sonycam-1.3.2
Parsing documentation for thor-1.0.1
Installing ri documentation for thor-1.0.1
Parsing documentation for sonycam-1.3.2
Installing ri documentation for sonycam-1.3.2
Done installing documentation for thor, sonycam after 3 seconds
2 gems installed

Just for fun, after hooking up the wireless... try to take a picture?

pi@raspberrypi:~ $ sonycam api actTakePicture
Can not find /home/pi/.sonycam, start scanning...
Found location: http://192.168.122.1:64321/DmsRmtDesc.xml
Device description file saved to /home/pi/.sonycam
Traceback (most recent call last):
        8: from /usr/local/bin/sonycam:23:in `<main>'
        7: from /usr/local/bin/sonycam:23:in `load'
        6: from /var/lib/gems/2.5.0/gems/sonycam-1.3.2/bin/sonycam:3:in `<top (required)>'
        5: from /var/lib/gems/2.5.0/gems/thor-1.0.1/lib/thor/base.rb:485:in `start'
        4: from /var/lib/gems/2.5.0/gems/thor-1.0.1/lib/thor.rb:392:in `dispatch'
        3: from /var/lib/gems/2.5.0/gems/thor-1.0.1/lib/thor/invocation.rb:127:in `invoke_command'
        2: from /var/lib/gems/2.5.0/gems/thor-1.0.1/lib/thor/command.rb:27:in `run'
        1: from /var/lib/gems/2.5.0/gems/sonycam-1.3.2/lib/sonycam/cli.rb:49:in `api'
/var/lib/gems/2.5.0/gems/sonycam-1.3.2/lib/sonycam/api.rb:17:in `request': Sonycam::Error::IllegalRequest

I suppose, this is an Action Cam and not an SLR? Let's try the livestream to MP4.

sonycam liveview | ffmpeg -f image2pipe -c mjpeg -i pipe:0 -codec copy liveview.mp4

Trying to open the video file whilst the recording was happening didn't work... but it opened successfully once it was stopped.

The final option was to use ffserver, but it turns out that it's ancient technology that's been deprecated in FFmpeg. I quickly looked at the age of the repository and noticed it was last updated 6 years ago!

In the end, it turns out that the liveview is only ever outputted at 635x300-something. It's not HD! Nooooo... alllll... thissss... timmmeeee... speennntt.... It seems that there's another call for startLiveviewWithSize, but my camera doesn't support this. Oh right, only the full-frame cameras do!? And look at that link, they've called Liveview "viewfinder"! Of course it wont be full resolution.

HDMI Output

What goes out, must come in? Sure, this thing has a mini/micro/something HDMI port in its ass... but how does that help me? YouTube doesn't have a HDMI input plug! Well, eBay does have crappy HDMI to USB converters.. so..

s-l500

I bought that thing... and got this input via VLC. The actual software they wanted to use needed Direct X Movie/Video WMV software installed and Windows 10 didn't want to even care.

HDMIin

Image quality was OK, but no colour? I didn't bother digging further as I really didn't want HDMI cables running all over the shop.

Let's just use the old camera

Oh well.. it's back to the old camera until I find another method of getting this camera to stream. Here it is, right now, streaming live to the world from my balcony.

DSC00002

Hahahaa... do you like my handywork? (Go over here to Youtube if the embed below is broken.)

I'll update if I manage to find a better method. There's hints of people hacking apart the APK (yes, these cameras run Android!) on the unit that streams to UStream.
Why can't we just create one that provides an RTSP endpoint, like my camera above?

17Oct/190

Red LED Shop Display

Found this thing at the flea markets on the weekend. It's a standard LED marquee/shop display with two cables hanging out one side. One looks like a power cable, the other data with an RJ45 phone plug on the end. Manufactured by Data Signs Australia, there's no mention of this relic on their site... seems they only care about road signs now!

DSC01945

As you can see, it's what's on the inside that counts. At the end there seems to be a controller board with an Atmel microcontroller. This is then connected to five display modules, all on a standard bus. The build of the headers is nice as it all connects seamlessly together to give one full display. It looks like the wiring of the phone jack goes straight into a MAX232... so we can talk serial to this thing. I'll do that later though, I'm more interested in the display modules.

DSC01957

DSC01959 DSC01960 DSC01967

DSC01969

DSC01965 DSC01966 DSC01971

Each module contains six 5x7 LED matrices. These are powered by TIP125 transistors and data is driven by MC14015BCP shift registers connected in series. The data bus on the side has 2 lines at either end for VCC and GND. The pins in the middle are then Clock, Data, Reset (for the shift registers) and then enable lines for the 7 matrix rows.

DSC01940

Hacking started in earnest to work out the pinout...

Lighting it up

For any LED matrix, the basic idea is to send data really quickly to them and 'emulate' that all LEDs are on at one time. Whenever you try to take photos of LED lights, be that traffic lights, train destination boards, etc... you'll find that, unless your shutter speed is slow, you don't get the entire set of 'lit' LEDs visible at once. Here's a good example:

DSC08481

Not the clearest example... blame the snow in early November... but you can see that the LEDs in the destination board aren't of a consistent illumination. The microcontroller is still busy rendering the screen and the frame is 75% drawn.

Microcontrollers 'print' the LED signal across the matrices to get them to light up. They do this so quickly, that to the naked eye, the LEDs are always lit. We're going to have to do this here as well. I've decided to use that huge IO board I investigated recently to drive this thing.

Based on the datasheet for the MC14015BCP shift registers, as the clock signal is driven high, whatever is on the data line will be fed into the first parallel data out line. These shift registers contain two sets of 4-bit outputs. Following the wiring on the card, the final bit of the first 4-bit output is chained to the data input of the second register. The final bit of that is then chained to the next shift register IC's data in. The clocks are all tied together, meaning that any data fed in to the clock/data lines on the bus at the side of the board will eventually trickle down all the way through the board, and then to any boards attached further. Quite the shift-register-centipede!

Writing some code...

Thanks to the previous work with this IO card, the interfacing would be quite simple. Clock, Data and Reset would be pins 1,2,3 on the first block-of-eight at the base IO address. I then put the 7 row-enable pins on the second block-of-eight. This meant less bit-twiddling. QBasic allows a nice mechanism to record data that can then be read into arrays. The data happens to be the pin value of the data pin, so here "2" will set the second pin HIGH when sent to the port, meaning that the data line will be high for the shift registers and therefore illuminating the LED in that column. I then just need to enable the first pin briefly to send through that number. Of course, I'd send a 0 (or LOW) to pin 2 to send a 0 through the shift registers, thereby turning that column's LED off.

The above needs to be done for each row. i.e. I have to send out 30 values (length of one row on one module) then 'flicker' the enable row. Then I need to send the next 30 values and 'flicker' the second enable row. The speed at which the row enable pin is 'flickered' is also an issue... if it's too slow, then you just get one line of LEDs and it's very jittery... but if it's too fast the LEDs start to dim, as they're starved of power to actually illuminate!

OUTDATA# = &H410
OUTLINES# = &H411
OUT &H413, 0
OUT &H413, 0
CLOCKBIT = 1
DATA 30, 8
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,2,2,2,0,2,2,2,0,2,2,2,0,2,2,2,0,2,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,2,0,0,2,0,0,0,2,0,0,0,0,2,0,0,2,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,2,0,0,2,2,2,0,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,2,0,0,2,0,0,0,0,0,2,0,0,2,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,2,0,0,2,2,2,0,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
READ DATALENGTH
READ DATAHEIGHT
DIM DATAPIXELARRAY((DATALENGTH - 1), (DATAHEIGHT - 1)) AS INTEGER
FOR Y = 0 TO (DATAHEIGHT - 1)
	FOR X = 0 TO (DATALENGTH - 1)
		READ DATAPIXELARRAY(X, Y)
	NEXT X
NEXT Y
DO
	FOR ROWS = 0 TO (DATAHEIGHT - 1)
		FOR DOT = 0 TO (DATALENGTH - 1)
			BIT = DATAPIXELARRAY(DOT, ROWS)
			OUT OUTDATA#, BIT
			OUT OUTDATA#, (BIT + CLOCKBIT)
			OUT OUTDATA#, 0
		NEXT DOT
		OUT OUTLINES#, 255 - (2 ^ ROWS)
		FOR i = 0 TO 10
		NEXT i
		OUT OUTLINES#, 255
	NEXT ROWS
LOOP UNTIL INKEY$ = "q"

DSC01951

The only real trickery in the code above is the two zeroes sent out to 0x413 at the start... it seemed to be required to get the IO card to treat the ports as output and pull them to ground by default. Otherwise, data is defined and loaded into the array. A non-stop loop (unless you press "q") is then started where we load a single row of dots and then trigger the row enable pin. Each row-enable pin needs to be brought low to activate the LEDs, hence the 255 - (2 ^ rownumber). Finally, that crappy little for-loop is there to provide a mini delay so that the LEDs get a chance to light up.

Let's make a marquee

Right, a display of static pixels is fine, but what if you want one of those really ugly perpetually-scrolling marquees that are seen in every $2 (100-yen) shop? Should be pretty simple, just throw in an offset and increment it at every loop. That'll work, but then once it's off the screen it'll start back at '0' which happens to be fully-on-screen. That jump is a little jarring, so instead you need to actually shift it width-of-data off the start. For now, we'll just wrap it without padding in the middle.

OUTDATA# = &H410
OUTLINES# = &H411
OUT &H413, 0
OUT &H413, 0
CLOCKBIT = 1

DATA 30, 8
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,2,2,2,0,2,2,2,0,2,2,2,0,2,2,2,0,2,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,2,0,0,2,0,0,0,2,0,0,0,0,2,0,0,2,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,2,0,0,2,2,2,0,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,2,0,0,2,0,0,0,0,0,2,0,0,2,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,2,0,0,2,2,2,0,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

READ DATALENGTH
READ DATAHEIGHT
DIM DATAPIXELARRAY((DATALENGTH - 1), (DATAHEIGHT - 1)) AS INTEGER
FOR Y = 0 TO (DATAHEIGHT - 1)
   FOR X = 0 TO (DATALENGTH - 1)
      READ DATAPIXELARRAY(X, Y)
   NEXT X
NEXT Y

DIM SCROLLOFFSET AS INTEGER
DIM LOOPCOUNTER AS INTEGER

SCROLLOFFSET = 0
LOOPCOUNTER = 0

DO
	FOR ROWS = 0 TO (DATAHEIGHT - 1)
		FOR DOT = SCROLLOFFSET TO (DATALENGTH - 1)
			BIT = DATAPIXELARRAY(DOT, ROWS)
			OUT OUTDATA#, BIT
			OUT OUTDATA#, (BIT + CLOCKBIT)
			OUT OUTDATA#, 0
		NEXT DOT
		FOR DOT = 0 TO (SCROLLOFFSET - 1)
			BIT = DATAPIXELARRAY(DOT, ROWS)
			OUT OUTDATA#, BIT
			OUT OUTDATA#, (BIT + CLOCKBIT)
			OUT OUTDATA#, 0
		NEXT DOT
		OUT OUTLINES#, 255 - (2 ^ ROWS)
		FOR i = 0 TO 10
		NEXT i
		OUT OUTLINES#, 255
	NEXT ROWS
	LOOPCOUNTER = LOOPCOUNTER + 1
	IF LOOPCOUNTER > 10 THEN
		LOOPCOUNTER = 0
		SCROLLOFFSET = SCROLLOFFSET + 1
		IF SCROLLOFFSET > DATALENGTH THEN
			SCROLLOFFSET = 0
		END IF
	END IF
LOOP UNTIL INKEY$ = "q"

TEST!

I'm sure there's some MOD function I can use to prevent the need for two loops, but it works! Of course, it only works as long as the data is the length of the screen! What happens when I hook up the other modules?...

Oh... that's no good... it's offsetting by 1 row per module? What's the go there? Let's edit the code to fill the entire row.

OUTDATA# = &H410
OUTLINES# = &H411
OUT &H413, 0
OUT &H413, 0
CLOCKBIT = 1

DIM ACTUALDISPLAYWIDTH AS INTEGER
ACTUALDISPLAYWIDTH = 60

DATA 30, 8
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,2,2,2,0,2,2,2,0,2,2,2,0,2,2,2,0,2,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,2,0,0,2,0,0,0,2,0,0,0,0,2,0,0,2,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,2,0,0,2,2,2,0,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,2,0,0,2,0,0,0,0,0,2,0,0,2,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,2,0,0,2,2,2,0,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

READ DATALENGTH
READ DATAHEIGHT
DIM DATAPIXELARRAY((DATALENGTH - 1), (DATAHEIGHT - 1)) AS INTEGER
FOR Y = 0 TO (DATAHEIGHT - 1)
   FOR X = 0 TO (DATALENGTH - 1)
      READ DATAPIXELARRAY(X, Y)
   NEXT X
NEXT Y

DIM SCROLLOFFSET AS INTEGER
DIM LOOPCOUNTER AS INTEGER

SCROLLOFFSET = 0
LOOPCOUNTER = 0

DO
	FOR ROWS = 0 TO (DATAHEIGHT - 1)
		FOR DOT = SCROLLOFFSET TO (ACTUALDISPLAYWIDTH - 1)
			IF (DOT >= DATALENGTH) THEN
				BIT = 0
			ELSE
				BIT = DATAPIXELARRAY(DOT, ROWS)
			END IF
			OUT OUTDATA#, BIT
			OUT OUTDATA#, (BIT + CLOCKBIT)
			OUT OUTDATA#, 0
		NEXT DOT
		FOR DOT = 0 TO (SCROLLOFFSET - 1)
			IF (DOT >= DATALENGTH) THEN
				BIT = 0
			ELSE
				BIT = DATAPIXELARRAY(DOT, ROWS)
			END IF
			OUT OUTDATA#, BIT
			OUT OUTDATA#, (BIT + CLOCKBIT)
			OUT OUTDATA#, 0
		NEXT DOT
		OUT OUTLINES#, 255 - (2 ^ ROWS)
		FOR i = 0 TO 10
		NEXT i
		OUT OUTLINES#, 255
	NEXT ROWS
	LOOPCOUNTER = LOOPCOUNTER + 1
	IF LOOPCOUNTER > 5 THEN
		LOOPCOUNTER = 0
		SCROLLOFFSET = SCROLLOFFSET + 1
		IF SCROLLOFFSET >= ACTUALDISPLAYWIDTH THEN
			SCROLLOFFSET = 0
		END IF
	END IF
LOOP UNTIL INKEY$ = "q"

So now we have a ACTUALDISPLAYWIDTH variable that tells us how much space we actually need to fill. Using the SCROLLOFFSET, we then start printing out the data array and, if we're past it then just zeroes. Once we've gotten to our full width, we then go back to the array and attack it from the start.

And if we increase to three panels and up the width to 90?

Gosh, it's getting slow loading that full row each time... maybe time to switch to an Arduino? Before we do that... here's a typewriter...

OUTDATA# = &H410
OUTLINES# = &H411
OUT &H413, 0
OUT &H413, 0
CLOCKBIT = 1

DIM ACTUALDISPLAYWIDTH AS INTEGER
ACTUALDISPLAYWIDTH = 90

DATA 104, 8
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,2,2,2,0,2,2,0,0,2,2,2,0,2,2,0,0,2,2,2,0,2,2,2,0,2,2,2,0,2,0,2,0,2, 2,2,0,0,2,2,0,2,0,2,0,2,0,0,0,2,0,2,0,2,2,0,0,0,2,0,0,2,2,2,0,2,2,2,0,2, 2,2,0,2,2,2,0,2,2,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,2,2
DATA 0,2,0,2,0,2,0,2,0,2,0,0,0,2,0,2,0,2,0,0,0,2,0,0,0,2,0,0,0,2,0,2,0,0, 2,0,0,0,0,2,0,2,0,2,0,2,0,0,0,2,2,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2, 0,2,0,2,0,0,0,0,2,0,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,0,0,2
DATA 0,2,2,2,0,2,2,0,0,2,0,0,0,2,0,2,0,2,2,2,0,2,2,2,0,2,0,2,0,2,2,2,0,0, 2,0,0,0,0,2,0,2,2,0,0,2,0,0,0,2,0,2,0,2,0,2,0,2,0,2,0,2,2,2,0,2,0,2,0,2, 2,2,0,2,2,2,0,0,2,0,0,2,0,2,0,2,0,2,0,2,2,2,0,0,2,0,0,0,2,0,0,0,2,0
DATA 0,2,0,2,0,2,0,2,0,2,0,0,0,2,0,2,0,2,0,0,0,2,0,0,0,2,0,2,0,2,0,2,0,0, 2,0,0,2,0,2,0,2,0,2,0,2,0,0,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,0,0,2,2,2,0,2, 2,0,0,0,0,2,0,0,2,0,0,2,0,2,0,2,0,2,0,2,2,2,0,2,0,2,0,0,2,0,0,2,0,0
DATA 0,2,0,2,0,2,2,0,0,2,2,2,0,2,2,0,0,2,2,2,0,2,0,0,0,2,2,2,0,2,0,2,0,2, 2,2,0,0,2,0,0,2,0,2,0,2,2,2,0,2,0,2,0,2,0,2,0,0,2,0,0,2,0,0,0,2,2,2,0,2, 0,2,0,2,2,2,0,0,2,0,0,2,2,2,0,0,2,0,0,2,0,2,0,2,0,2,0,0,2,0,0,2,2,2
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

READ DATALENGTH
READ DATAHEIGHT
DIM DATAFONTARRAY((DATALENGTH - 1), (DATAHEIGHT - 1)) AS INTEGER
FOR y = 0 TO (DATAHEIGHT - 1)
   FOR X = 0 TO (DATALENGTH - 1)
      READ DATAFONTARRAY(X, y)
   NEXT X
NEXT y

DIM ACTUALPIXELBUFFER(ACTUALDISPLAYWIDTH - 1, DATAHEIGHT - 1) AS INTEGER

DIM SCROLLOFFSET AS INTEGER
DIM LOOPCOUNTER AS INTEGER

SCROLLOFFSET = 0
LOOPCOUNTER = 0

DO
	FOR ROWS = 0 TO (DATAHEIGHT - 1)
		FOR DOT = 0 TO (ACTUALDISPLAYWIDTH - 1)
			BIT = ACTUALPIXELBUFFER(DOT, ROWS)
			OUT OUTDATA#, BIT
			OUT OUTDATA#, (BIT + CLOCKBIT)
			OUT OUTDATA#, 0
		NEXT DOT
		OUT OUTLINES#, 255 - (2 ^ ROWS)
		FOR i = 0 TO 10
		NEXT i
		OUT OUTLINES#, 255
	NEXT ROWS
	LOOPCOUNTER = LOOPCOUNTER + 1
	pressedchar$ = INKEY$
	IF (LEN(pressedchar$) = 1) THEN
		lastchar = ASC(pressedchar$)
		lastchar = lastchar - 97
		IF (lastchar >= 0) THEN
			FOR c = 0 TO 3
				FOR y = 0 TO 6
					ACTUALPIXELBUFFER(c + offset, y) = DATAFONTARRAY((lastchar * 4) + c, y)
				NEXT y
			NEXT c
			offset = offset + 4
			IF ((offset + 4) >= ACTUALDISPLAYWIDTH) THEN
				offset = 0
			END IF

		END IF
	ELSE
		REM lastchar = ASC(RIGHT$(pressedchar$, 1))
	END IF
	PRINT lastchar
	IF LOOPCOUNTER > 200 THEN
		LOOPCOUNTER = 0
		SCROLLOFFSET = SCROLLOFFSET + 1
	END IF
LOOP WHILE INKEY$ <> "."

Hah... that worked better than expected...

24Aug/190

Copying Dolphin Save Games Cross-Platform

I recently removed my Dell Precision T3500... It was beautiful, but it started presenting very weird temperature/time related blue-screens and other gremlin-esque issues. I removed it and replaced it with a Raspberry Pi 4. That piece of fruit couldn't successfully even load my train radar (Chromium on Raspbian can't load OpenLayers Maps), so I went hunting for another piece of fruit in a local store and found a 2014 Mac Mini.

DSC 0131

I hate this... It's fkn old and has soldered-in 8gb RAM... but it Just Works(tm). So, with this new device, I loaded up Dolphin to see if I could continue my Pikmin 2 quest. My GameCube is long dead, so I use emulation to get the game disc, sitting on my shelf, performing in full glory on my 4K TV. Anyway... previous install was Windows 10... new is Mac OS Mohave. Installing Dolphin was easy enough... finding where it saved games wasn't.

Turns out that MacOS puts them in /Users/stevenh/Library/Application Support/Dolphin. To find this folder, ask Finder to show the Library Folder by choosing the option in View Options.

Screen Shot 2019-08-24 at 6.27.39 pm Screen Shot 2019-08-24 at 6.27.07 pm Screen Shot 2019-08-24 at 6.27.27 pm

Now that we've got the Library folder open... browse through to Application Support/Dolphin. Have a look... but this is the target folder... we need contents! As I'd mentioned, I'd come from Windows... so I mounted the disk and took a wild guess: My Documents! Of course, it's not called that anymore... it's Documents and it's in your home folder. In here, you'll find a very similar layout! If you're copying a WII game, then check the WII folder for saves/memorycards. If you're copying a GameCube game, then look in the GC folder...

Screen Shot 2019-08-24 at 6.33.41 pm

GOT IT! Move the relevant card saves to the relevant folder in your new Macintosh home directory and ...

Screen Shot 2019-08-24 at 6.36.24 pm

NAILED IT.

Screen Shot 2019-08-24 at 6.35.26 pm

Meanwhile, this Macintosh Mini 2014 Edition is supposedly meant to be terrible... but it plays perfectly. The warning messages disappear after a while.

2Aug/190

MS Access: Error 6 – Overflow

A quick break between posts of the recent trip to Japan: I was distracted at work by a user who was experiencing a bug in an Access Database. Now, my team has never built access databases, but we're still the first-point-of-call for technical support on internally developed applications; even if not developed by us, nor in this decade!

We'd also just recently migrated a SQL server from one Windows VM to another, so there was a high chance that a user had been orphaned in the process... either way, I dug in.

First step: Reproduce locally!

I had the user screen-share over Skype so I could see what was going on. There was a very quick path to reproduction, so I took a copy of the database and brought it over to my machine. In no time I had the following:

Error 6 Overflow

Time to find the code?

Unlocking an Access DB

To get into the backend code, you need to unlock all the menus. Hit the File menu and then choose Privacy Options.

2019-08-02 10 57 04-Window

From here... make sure the following items are checked. Allow Special Keys is required for breakpoints to trigger in the VBA script!

options

Close the database and open it again.

Hacking the code

You'll now have the navigation pain on the left-hand side. Somewhere down the bottom you should have one or more modules containing the code throwing errors.

2019-08-02 11 17 03-

Double-clicking Module1 presenting me with the following... of course, like any good organisation, we had the passwords well-documented!

2019-08-02 11 02 45-Window

Once in... breakpoints were set and code was tested. The breakpoints didn't initially trigger, as I hadn't set the Allow Special Keys option. The name really doesn't make sense, but it's required if you want to debug! Once going though, I found that the code wouldn't hit another breakpoint inside the following function...

Public Sub RetrieveTestNamesFromSpecs(iBulkID As Integer)
        Dim RCountDB As DAO.Database
        Set RCountDB = CurrentDb
        ...
        ...
End Sub

Nothing really special! But the Overflow error was happening before the function started. A breakpoint on the 'Set' line would never get hit. I looked at the line before the function call and didn't see anything incriminating. I then checked the value of the variable being passed in as iBulkdID. It was 32790. That's a pretty ominous number for anyone who understands bytes... or variable sizes. It turns out that an Access Database Integer only supports the value range of -32768 to 32768. Our ID had surpassed this and was therefore not 'fitting in' to the variable.

Changing this to a Long fixed the issue! I handed the DB back to the user and dusted my hands.

More Casting!

Before long I had the DB back on my desk as there was a new Overflow Error. I quickly dug into the code and found that the error was happening when executing the following SQL.

        strSQL = "SELECT * FROM [TblName] where [CName] = " & Me.Variable & " Order By CInt(CName) Asc"
        Set rs = RCountDB.OpenRecordset(strSQL)

Anyone playing at home will see the error straight-away, but I wasn't used to Access SQL syntax. Long-story-short, that CInt is trying to cast the value as an Integer, and we already know that it doesn't fit! A quick conversion to CLng fixed this error as well!

I then scoured the rest of the code for crappy Integer references...

22Jan/190

PCI 64-Bit Backwards Compatibility

Picked up this awesome external SCSI HDD enclosure last week but was perplexed by the cable. There's an ultra-tiny plug on one end and a standard-sized external 68-pin plug on the other. So far I've only seen this plug on internal cables and didn't know it was built in an external format also.

DSC06411

I was already in trouble. All of my standard ISA or PCI cards are either Centronics, DB25 or ultra-tiny plug and the only card I have with a full-width 68-pin external socket is a 64-bit PCI card!

DSC06413 DSC06418 DSC06420

The last motherboard I had with 64-bit PCI slots was the Apple G4 Graphite and that's long gone. I realised I could plug the cable into the internal side of the card... but that wasn't optimal!

DSC06423

A quick google then lead me to understand that 64-bit cards are backwards compatible with 32-bit slots! Supposedly there's an 'enable' pin on the extra 64-bit edge connector section where, if not brought down, the cards are expected to work in 32-bit mode!

So I lined it up...

DSC06425

And plugged it in...

DSC06426

And the world was a happier place! The drives came online (with extended IDs 14 and 15) and their contents happily displayed. They turned out to be a RAID pair of NT Server NTFS drives with home directories from an old Radio/TV company. Very random stuff in here!? Is data archeology a thing!?

13Nov/180

Repairing a 3rd Gen iPod Dock

I've had this little beast for a very long time and thought my amplifier was playing up when the left channel started cutting in and out. A quick amount of cable-jiggling proved otherwise: the jack on the back of the iPod Dock was frail.

DSC05284

DSC05278 DSC05279 DSC05283

No amount of stickytape would keep the cable in contact with the socket, so I endeavoured to open the unit up. Turns out there's large tabs on the inside 'side' edges and smaller tabs along the longer edges.

DSC05282

DSC05268 DSC05271 DSC05277

DSC05274

The cracked solder joints were immediately obvious and a quick touch-up with the soldering iron brought the conductivity back from a crackling zero to 'awesome'. I really love easy fixes!

2Nov/180

Oculus Rift DevKit 2

I always managed to be surprised at things that turn up at flea markets. I was stumbling through Laverton Market on the weekend and came across this... for AUD$5.00. Is this retro? It could well be... it's nearly vintage nonetheless!

DSC05215

DSC05214 DSC05216 DSC05217

DSC05219

Turns out it's the Oculus Rift Development Kit 2. More infomration here. The headset has a cable running off it with USB and HDMI. There's also a USB and audio-style port, hidden under a cover on top. It's also got a random date on the side and a differing price tag of AUD$2.00? Did I get ripped off?

Testing it

Seemed to be simple enough... Just for fun I plugged it into my laptop. It straight away showed up as a portrait screen and I could move application windows onto the new secondary desktop! The LCD looked a little sketchy, but I assumed it was nothing that couldn't be cleaned off!

With the basic shake-out complete, I downloaded the Oculus Rift software and installed it. Whilst reading the download notes, I realised it hinted at another piece of hardware: a sensor. My kit didn't come with this! It seems that the sensor (looks like a webcam) tracks the IR output of the headset to determine your physical position. Supposedly you can use the headset without it and you'll just get rotational giroscope-based movement.

Once the software was installed, I plugged the unit back in and ... well ... nothing but errors. The Oculus Software reported that I had a USB connection, but no HDMI connection. No amount of cable-tweaking-or-swapping managed to get it to work. Long-story-short, my laptop wasn't even capable of supporting the unit and would never actually be able to output HDMI in the format the headset wanted. It would actually be really nice if the software provided this tiny hint, instead of just telling me that the HDMI connection was unavailable, but it actually turns out I skipped the entire first step... there's more on that a few sections below.

Digging deeper, it turns out that the Oculus software installs a driver for the headset and prevents it from being used as a second monitor. The point that I saw a desktop when I first plugged it in, was just because the Oculus display drivers were not installed. Of course, I had no idea that this was the case and thought I'd actually killed the unit! I proceeded to tear it apart ... was it a loose cable or other broken component?

What Does It Look Like On The Inside?

I had read another review where the author had described the whole unit as a 'kitbash'. I chuckled at that, as it's a common term in model railroading where you take an off the shelf product (or multiple) and blend them together to make something new and unique. Well... that's actually what they've done here... to the point where the LCD is actually a Samsung Galaxy Note 3 screen, complete with phone face-panel and touchscreen digitizer!

DSC05218 DSC05220 DSC05223

DSC05225

DSC05226 DSC05239 DSC05256

The best part? The LCD in my unit is crappy... it seems to have two areas of pixels that are totally burnt out. I went ahead and ordered one on eBay as I had assumed that this was the reason that the unit had stopped working.

Compatibility

Of course, it struck me later on, after re-assembling the unit and stowing it away to wait for the new screen, that it could just be my laptop!? I downloaded the Oculus Compatibility Check Tool and was quickly told that my laptop hardware was useless. Muhahaha... what a waste of time... What to do? I have a desktop with a real NVIDIA card and realised I should've used it to begin with. I downloaded the Oculus Legacy 0.8.8 Runtime and gave it a whirl... success! I used the older version as there were conflicting reports online that this older DK2 unit only worked with older software.

2018-10-30 19 09 01-Oculus Configuration Utility

With the test application, I managed to happily sit at my desk and swing my head around. The movement was actually pretty damn flawless and the image quality very impressive! I then tried to load up games like GzDoom VR and Quake II VR but none managed to initialise the device... I assumed they needed the newer version of the software.

DSC05241 DSC05244 DSC05245

With my head at the right angle, I could really see how bad the LCD was also!? It seemed to be burnt at the same place on both screens!? Maybe some really bright light in a game cooked the crystals? (Excuse the dust... but look for the pink dot)

DSC05249

Throwing caution to the wind, I downloaded and installed the standard (latest) Oculus software on my desktop. Low-and-behold it (took forever to download and install) found both the USB and HDMI connections!

2018-11-01 18 50 57-Window 2018-11-01 19 06 59-Window 2018-11-01 20 25 24-Window

During setup, it whinged that it couldn't find anything, but skipping that worked. In the actual app I then got a warning that the DK2 was no longer supported. Dismissing that, I was finally presented with an error saying that it couldn't find the sensor.

DSC05263

Urgh... back to square zero...

Trying to play games without the sensor

This just doesn't work... I got a message box saying that the sensor can't be found and was presented with an OK button. Clicking this button was impossible though! Firstly, I needed a 'touch' device that the Oculus supported so that I could actually interact with the 3D world. At this point I was worried about putting in more money to something that I could never get working. I didn't know if clicking OK will actually let me into the game!?

Meanwhile, can't I just buy a replacement sensor? Turns out it's a big, fat no! Googling is pretty funny on this topic. It seems that during production, the sensor and headset are paired via serial number. The hardware has been open-sourced (here's the actual files), but the firmware for the sensor is not available! The quote from them is: The sensor also utilizes microcontrollers which require firmware which was not redistributable. Grrrrrrr!

Before I start an attempt to hack together a sensor (here's great inspiration!)... I wonder if I can (cheaply) get that button pressed to see if the games will continue without a sensor?

Controllers

To press that magic OK button, I knew I needed a working controller. I considered borrowing an XBOX 360 controller from a friend, but then found an article using an Xbox 360 Controller Emulator. It seems that others have had luck! (Original French article here)

2018-11-01 20 43 30-Window

I tried my hardest to fake the controller, but had no success... I couldn't get the DLL in the right place to stop the main Oculus app from whinging that nothing was connected. Of course, the best thing at this point was to get an actually-supported controller. I didn't bother trying to use my Android phone can emulate one. Instead, I picked up a cabled controller from a nearby second-hand store.

DSC05267

Finally, Oculus recognised it. Of course... it didn't let me press that magical OK button... I was stuck without a sensor!

Reverting back to 0.8.8

I was out and about taking the MR-2 for a spin and the thought came to me: I'd had the demo working fine on 0.8.8... there were no camera/sensor warnings! Maybe I should downgrade and find compatible software? I uninstalled the latest software and re-installed 0.8.8. I then browsed over to this list of compatible software. Oh look, Duke Nukem 3D!

DSC05266

Hahaha... SHAKE IT BABY. It just worked perfectly... with controller and all! I then went back to the GZ3Doom Releases Page and scrolled down, looking for that magical SDK value of 0.8.8. It also worked!

DSC05255

Ok, nice... I have things to do with this now.

We get to wait with Totoro!?

Whaaaaaat... Someone has built a 3D scene of the bus stop!? But the links are all dead... can anyone send me one that works?! Full review of it here, but still no valid links. The review indicates that Fire Panda are the author. But there's nothing on their actual site. Ohhhh: Due to a request by Studio Ghibli, these demos will sadly no longer be available. I hope that everyone who got a chance to play them enjoyed them and I hope you enjoy future projects I am able to bring to you. Thanks again for all your support! Boo... I'm only a few years late!

Turns out there's a whole world of user-authored content at SketchFab. Here's the train from Spirited Away. That Sea Railway was always a cool idea.

OpenHMD

This all seems promising, but there's no compiled binaries for Windows? I don't have a Linux machine to muck around with that has a good enough video card... hmmm... that's a little bit too much effort to go to. Actually, my main machine has started dropping the ethernet connection and blue-screening... maybe it's time for a rebuild!?

Build your own sensor?

There's a great set of documents at doc-ok.org on Hacking the Oculus Rift DK2 (Part 2, Part 3, Part 4) which describes how to read the data from a real DK2 sensor. This is all well and good, if you have the sensor... in my situation, this code will come in handy once I manage to fake a sensor. Philipp Zabel's ouvrt project also looks interesting as it is able to extra ROMs from DK2 sensors. This may only be the USB configuration ROM, but it'll totally help with the faking.

Where to start? The Open-sourced project of the DK2 has a hardware schematic of the sensor which shows that it's based on an eSP570. Turns out these are pretty hard to find... but Alibaba seems to have some available.

I thought the camera lens would sense the timing flashes that the headset produces, but it seems that this is done by an IR sensor built-in. There's then a micro-controller to parse this. You can see the sensor in the tear-down here. It seems to be something similar to this dis-continuted sensor. Urgh...

But I'm getting carried away... this is all totally-probably worth a probably-totally separate blog post...