Carstens blog

Friday, June 6, 2014

Disassembling Magicshine MJ-876/878 dive light

Here is how to disassemble the Magicshine MJ-876/878 dive light. This is just a copy of this post trying to make it more readable.

All credit goes to EskildJ

To remove the battery


Get a 15mm nut. Drill a bit into the threading using a 10mm drill. This Tool will open the battery compartment:










So you can see the nice 6*18650 pack, and if you hoped that the charging connector was Water-tight, it isn't.
To get to the switching ring, you don't need to dismount the battery.


To remove the front


You can't simply unscrew the lamp at the battery indicator. You need to remove the reflector first.

Remove the glass holder ring:



You need to make a Tool to unscrew this. I did like this, and it worked fine:



After removing the glass, you see the glass o-ring. You are now at the reflector. unscrew it to get it out.






You can now un-screw the front part of the lamp, threading is in front of the battery indicator.



Notice the two green O-rings. Remove the two green O-rings, and you can slide forward the battery indicator ring:



Then remove the switch ring, beware of the spring/ball:





Notice that the LED window is only single or-ring secured - I guess it is OK, as the Water will compress the plastic opposed to the alu. But it needs to be clean!

Now, the magnet is visible. It is a round disc, 5mm diameter and around 1mm thick.



It is very visible why it is weak. It is corroded away. This could also explain why some people complain about sand under the ring. The "sand" could be disintegrated magnet.

Disassembling the Electronics

I didn't disassemble the Electronics this time, to avoid messing with the heat sink paste under the LED. But it is straight-forward, unscrewing the two-hole ring around the LED.

Wednesday, October 17, 2012

Trello Bugzilla Integration

Here is a GreaseMonkey script that provides simplistic integration of Bugzilla into Trello.com.


So far it supports:
  • Decorating cards that have 'Bug \d+' in their card title with a badge that is a hyper link to Bugzilla.

  • Auto-completion of new card titles starting with 'Bug \d+' 

The script also works in Chrome with the Tampermonkey extension installed.

You can get the code on GitHub


// ==UserScript==
// @name Trello Bugzilla Integration
// @namespace http://www.navicon.dk/
// @version 0.1
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js
// @description Looks for card titles with 'Bug \d+' and adds badge/links to bugzilla. Also autocompletes new card titles that starts with 'Bug \d+' from bugzilla. Autocomplete is actived when pressing spacebar after 'Bug \d+'.
// @match https://trello.com/board/*
// @copyright 2012+, Carsten Madsen
// ==/UserScript==

// grey bugz icon
var bugzillaImg = '';

// edit me to point somewhere else
var bugzillaLink = 'http://bugzilla.mozilla.com/show_bug.cgi?id=';

var addBugzillaBadge = function() {
    $(".list-card-title").each(function(i,val){
    if ($(this).html().match(/Bug \d+/)) {
        var regExpMatch = $(this).html().match(/Bug (\d+)/);
        if ($(this).parent().find(".bugz").length < 1){
        $(this).parent().children('.badges').append(''+bugzillaImg+'');}
    }
    });
};

// intercept spacebar press when creating new cards and look in
// bugzilla to do possible autocomplete

unsafeWindow.$("body").delegate(".js-card-title", "keypress", function(e){
    var code = (e.keyCode ? e.keyCode : e.which);
    if(code == 32) { //Space keycode
    var text = $(this).val();
    var regExpMatch = text.match(/Bug (\d+)/);
    var textarea = $(this);
    if (regExpMatch) {
        // see http://stackoverflow.com/questions/11007605/gm-xmlhttprequest-why-is-it-never-firing-the-onload-in-firefox
        setTimeout(function() {GM_xmlhttpRequest({
        method: "GET",
        url: bugzillaLink+regExpMatch[1],
        onload: function(response) {
            var jq = $(response.responseText);
            textarea.val(jq.find(".bz_alias_short_desc_container b").text().replace(String.fromCharCode(160)," ")+ " - " + jq.find("#short_desc_nonedit_display").text());
        }
        })}, 0);
    }   
    }
});

setInterval(addBugzillaBadge, 5000);

Wednesday, August 3, 2011

CouchBoard - Porting a ROR/JQuery application to CouchDB/JQuery


Looking for a replacement for our small company Scrummy taskboard I stumbled over CognifidesLabs Taskboard Ruby On Rails (ROR) application. It looked useful but it seemed stale and being a complete ROR imbecile and a JavaScript novice I initially gave up as the entry threshold just seemed to big.




But after fooling around with CouchDB in another context I realized that maybe I could scrap the entire ROR backend as the data exchange format was JSON and just port the http requests to the ROR backend to CouchDB JavaScript.

At first this seemed as a daunting task as most tutorials etc. for doing CouchDB application development uses couchapp so even a simple task as figuring out how to port the login logic was hard. Eventually I found couchdb-login-jquery and from there on the porting job was as easy a calling the CouchDB JavaScript CRUD functions.

The ROR back end is approximately 1700 lines of Ruby code this was exchanged for an additional 200 lines of JavaScript. For somebody used to setting up Eclipse, Maven, Tomcat, Spring, Spring Security, PostgreSQL just to get started it seems unreal to just use a text editor, reupholster and Firefox/Firebug for the entire developmet process.

On top of this all that is needed to host the application is a free account at cloudant where you get a scalable, fault-tolerant, distributed database to host your applications in.

In summary porting the ROR backend to pure CouchDB/JavaScript was surprisingly easy. The most diffuclt part was acctual to understand the CouchDB security model and then learn that the cloudant (BigCouch) model was incompatible.

In case you need a small and simple to manage scrum/kanban board you can get the code from here and you can test the application here

Friday, October 15, 2010

Preliminary GRIB rendering for OpenMap

I have managed to render GRIB weather data in a OpenMap layer. It should support GRIB data from a numbers of free sources like NOAA.

Looks pretty cool with the isobars, wind arrows and a wind heat map.

The images below are showing a storm front (Xynthia) hitting Western Europe.

















Comments are welcome!

Wednesday, August 18, 2010

Geonames location searcher for OpenMap

Here is an integration of openmap with geonames that will add a combobox that can be used to search geonames.org to the toolbar by adding the following to your openmap.properties file:

openmap.components=... geonamessearcher
geonamessearcher.class=GeoNamesSearcher

Here is the code

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.plaf.basic.BasicComboBoxEditor;

import net.miginfocom.swing.MigLayout;

import org.apache.log4j.Logger;
import org.geonames.Toponym;
import org.geonames.ToponymSearchCriteria;
import org.geonames.ToponymSearchResult;
import org.geonames.WebService;

import com.bbn.openmap.event.CenterListener;
import com.bbn.openmap.event.CenterSupport;
import com.bbn.openmap.gui.OMToolComponent;

public class GeoNamesSearcher extends OMToolComponent {

private static final Logger log = Logger.getLogger(GeoNamesSearcher.class
.getName());

private JComboBox searchBox;

private CenterSupport centerDelegate;

private DefaultComboBoxModel searchBoxModel = new DefaultComboBoxModel();

public GeoNamesSearcher() {
centerDelegate = new CenterSupport(this);
setLayout(new MigLayout());
searchBox = new JComboBox(searchBoxModel);
searchBox.setEditable(true);
searchBox.setMaximumRowCount(25);
searchBox
.setToolTipText("Enter part of location to search for (and center to).");
searchBox.setRenderer(new DefaultListCellRenderer() {
@Override
public JComponent getListCellRendererComponent(JList list,
Object value, int index, boolean isSelected,
boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index,
isSelected, cellHasFocus);
if (value instanceof Toponym) {
Toponym toponym = (Toponym) value;
setText(toponym.getName() + " " + toponym.getCountryCode());
}
return this;
}
});
searchBox.setEditor(new BasicComboBoxEditor() {
@Override
public void setItem(Object anObject) {
if (anObject != null) {
if (anObject instanceof Toponym) {
Toponym toponym = (Toponym) anObject;
editor.setText(toponym.getName() + " "
+ toponym.getCountryCode());
// oldValue = anObject;
} else {
super.setItem(anObject);
}
} else {
editor.setText("");
}
}
});


searchBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
JComboBox cb = (JComboBox) evt.getSource();

Object selectedItem = cb.getSelectedItem();

if ("comboBoxEdited".equals(evt.getActionCommand())) {
// User has typed in a string; only possible with an
// editable combobox
try {
searchBoxModel.removeAllElements();
ToponymSearchCriteria searchCriteria = new ToponymSearchCriteria();
// searchCriteria.setCountryCode("DK");
searchCriteria.setQ((String) selectedItem);
ToponymSearchResult searchResult = WebService
.search(searchCriteria);
for (Toponym toponym : searchResult.getToponyms()) {
searchBoxModel.addElement(toponym);
}
if (searchResult.getTotalResultsCount() == 1) {
Toponym t = searchResult.getToponyms().get(0);
centerDelegate.fireCenter(t.getLatitude(), t
.getLongitude());
} else if (searchResult.getTotalResultsCount() > 1) {
searchBox.showPopup();
}
} catch (Exception e) {
log.error("", e);
}

} else if ("comboBoxChanged".equals(evt.getActionCommand())) {
// User has selected an item; it may be the same item
if (cb.getSelectedItem() instanceof Toponym) {
Toponym newItem = (Toponym) cb.getSelectedItem();
centerDelegate.fireCenter(newItem.getLatitude(),
newItem.getLongitude());
}
}

}
});
add(searchBox);
}

public synchronized void addCenterListener(CenterListener listener) {
centerDelegate.add(listener);
}

/**
* Remove a CenterListener
*
* @param listener
* CenterListener
*/
public synchronized void removeCenterListener(CenterListener listener) {
centerDelegate.remove(listener);
}

@Override
public void findAndInit(Object obj) {
if (obj instanceof CenterListener) {
addCenterListener((CenterListener) obj);
}
}

@Override
public void findAndUndo(Object obj) {
if (obj instanceof CenterListener) {
removeCenterListener((CenterListener) obj);
}
}
}

Wednesday, September 23, 2009

Using JavaFX Classes Directly From Java

Sitting on the Swing side of the fence the grass on the JavaFX side certainly sometimes seems a lot greener.


Knowing that the latest and greatest scene graph code is available somewhere in the pile of JavaFX jars it is tempting to try and get access to it from the Swing side without being "bothered" with the JavaFX language.

Other people like Matt Hicks have had the same thoughts (see I Hate JavaFX; I Love JavaFX!) so I started out with trying Matt's port of the JavaFX Clock and after some fiddling I got it up and running with JavaFX 1.2. Try it by pushing the launch button below.



The code follows (add all the JavaFX SDK jars to your classpath before trying this):

/**
* @author Matt Hicks (matt@matthicks.com)
*/
public class Clock {
private float radius = 77;
private float centerX = 144;
private float centerY = 144;
private Calendar calendar = Calendar.getInstance();
private int hours;
private int minutes;
private int seconds;

private FloatVariable hoursVariable;
private FloatVariable minutesVariable;
private FloatVariable secondsVariable;

public Clock() throws IOException {
nextTick();

// Build JavaFX clock
Group group = new Group();
{
ImageView imageView = new ImageView();
Image image = new Image();
// should be image.set$url() but does not work ?
image.loc$platformImage().set(
ImageIO.read(getClass().getClassLoader().getResource(
"clock_background.png")));
imageView.set$image(image);
group.loc$content().insert(imageView);

Group face = new Group();
{
Translate translate = new Translate();
translate.set$x(centerX);
translate.set$y(centerY);
face.loc$transforms.insert(translate);

// Every third hour
for (int i = 3; i <= 12; i += 3) {
Text text = new Text();
translate = new Translate();
translate.set$x(-5.0f);
translate.set$y(5.0f);
text.loc$transforms.insert(translate);
text.set$font(Font.font("Arial", 16));
text.set$x(radius * ((i + 0) % 2 * (2 - i / 3)));
text.set$y(radius * ((i + 1) % 2 * (3 - i / 3)));
text.set$content(String.valueOf(i));
face.loc$content.insert(text);
}

// Black circle for the rest of the hours
for (int i = 1; i < 12; i++) {
if (i % 3 == 0) {
continue; // Don't show a circle on every third hour
}

Circle circle = new Circle();
Rotate rotate = new Rotate();
rotate.set$angle(30.0f * i);
circle.loc$transforms.insert(rotate);
circle.set$centerX(radius);
circle.set$radius(3.0f);
circle.set$fill(Color.$BLACK);
face.loc$content.insert(circle);
}

// Center circles
Circle circle = new Circle();
circle.set$radius(5.0f);
circle.set$fill(Color.$DARKRED);

face.loc$content.insert(circle);
circle = new Circle();
circle.set$radius(3.0f);
circle.set$fill(Color.$RED);
face.loc$content.insert(circle);

// Second hand
Line line = new Line();
{
Rotate rotate = new Rotate();
BindingExpression exp = new AbstractBindingExpression() {
@Override
public void compute() {
pushValue(seconds * 6f);
}
};
secondsVariable = FloatVariable.make(exp);
rotate.loc$angle().bind(false, secondsVariable);

line.loc$transforms.insert(rotate);

line.set$endY(-radius - 3.0f);
line.set$strokeWidth(2.0f);
line.set$stroke(Color.$RED);
}
face.loc$content.insert(line);

// Hour hand
Path path = new Path();
{
Rotate rotate = new Rotate();
BindingExpression exp = new AbstractBindingExpression() {
@Override
public void compute() {
pushValue((float) (hours + minutes / 60) * 30 - 90);
}
};

hoursVariable = FloatVariable.make(exp);
rotate.loc$angle().bind(false, hoursVariable);
path.loc$transforms.insert(rotate);

path.set$fill(Color.$BLACK);

MoveTo e1 = new MoveTo();
e1.set$x(4.0f);
e1.set$y(4.0f);
path.loc$elements.insert(e1);
ArcTo e2 = new ArcTo();
e2.set$x(4.0f);
e2.set$y(-4.0f);
e2.set$radiusX(1.0f);
e2.set$radiusY(1.0f);
path.loc$elements.insert(e2);
LineTo e3 = new LineTo();
e3.set$x(radius - 15.0f);
e3.set$y(0.0f);
path.loc$elements.insert(e3);
}
face.loc$content.insert(path);

// Minute hand
path = new Path();
{
Rotate rotate = new Rotate();
BindingExpression exp = new AbstractBindingExpression() {
public void compute() {
pushValue((float) minutes * 6 - 90);
}
};
minutesVariable = FloatVariable.make(exp);
rotate.loc$angle().bind(false, minutesVariable);

path.loc$transforms.insert(rotate);
path.set$fill(Color.$BLACK);

MoveTo e1 = new MoveTo();
e1.set$x(4.0f);
e1.set$y(4.0f);
path.loc$elements.insert(e1);
ArcTo e2 = new ArcTo();
e2.set$x(4.0f);
e2.set$y(-4.0f);
e2.set$radiusX(1.0f);
e2.set$radiusY(1.0f);
path.loc$elements.insert(e2);
LineTo e3 = new LineTo();
e3.set$x(radius);
e3.set$y(0.0f);
path.loc$elements.insert(e3);
}
face.loc$content.insert(path);

group.loc$content.insert(face);
}
}

Timeline timeline = new Timeline();
timeline.set$repeatCount(Timeline.$INDEFINITE);

KeyFrame kf = new KeyFrame();
kf.set$time(Duration.valueOf(1000.0f));
kf.set$canSkip(true);
kf.set$action(new Function0<Void>() {
public Void invoke() {
nextTick();
return null;
}
});
timeline.loc$keyFrames.insert(kf);
// this is somewhat hairy JFxtras does it like this I think
Scene scene = new Scene();
scene.loc$content().insert(group);
JPanel panel = new JPanel(new BorderLayout());
TKScene fxNode = scene.get$javafx$scene$Scene$impl_peer();
panel.add(((SwingScene) fxNode).scenePanel, BorderLayout.CENTER);
JFrame frame = new JFrame("JavaFX Clock Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.setSize(295, 325);
frame.setVisible(true);
timeline.play();
}

public void nextTick() {
calendar.setTimeInMillis(System.currentTimeMillis());
seconds = calendar.get(Calendar.SECOND);
minutes = calendar.get(Calendar.MINUTE);
hours = calendar.get(Calendar.HOUR_OF_DAY);
// trigger bindings to re calc and move hands
if (secondsVariable != null) {
secondsVariable.invalidate();
minutesVariable.invalidate();
hoursVariable.invalidate();
}
}

public static void main(String[] args) throws Exception {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
try {
new Clock();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}

For comparison the JavaFX version follows here:

public class Clock extends CustomNode {

public var radius: Number = 77;
public var centerX: Number = 144;
public var centerY: Number = 144;

public var hours:Number;
public var minutes:Number;
public var seconds:Number;

public function nextTick () {
var now = new Date();
seconds = now.getSeconds();
minutes = now.getMinutes();
hours = now.getHours();
}

public override function create() : Node {
return Group {
content: [
ImageView {
image: Image {
url: "{__DIR__}clock_background.png"
}
},
Group {
transforms: Translate {
x: centerX,
y: centerY
}
content: [
// code to display the numbers for every third hour
for (i in [3, 6, 9, 12])
Text {
transforms: Translate {
x: -5,
y: 5
}
font: Font {
size: 16
}
x: radius * (( i + 0 ) mod 2 * ( 2 - i / 3))
y: radius * (( i + 1 ) mod 2 * ( 3 - i / 3))
content: "{i}"
}, //Text


//code to display a black circle for the rest of the hours on the clock
for (i in [1..12])
if (i mod 3 != 0 ) then Circle {
transforms: Rotate {
angle: 30 * i
}
centerX: radius
radius: 3
fill: Color.BLACK
} //for
else [ ],

// code for the clock's first center circle
Circle {
radius: 5
fill: Color.DARKRED
}, //Circle
//code for the smaller center circle
Circle {
radius: 3
fill: Color.RED
}, //Circle
//code for the seconds hand
Line {
transforms: Rotate {
angle: bind seconds * 6
}
endY: -radius - 3
strokeWidth: 2
stroke: Color.RED
}, //Line
//code for the hour hand
Path {
transforms: Rotate {
angle: bind (hours + minutes / 60) * 30 - 90
}
fill: Color.BLACK
elements: [
MoveTo {
x: 4,
y: 4},
ArcTo {
x: 4
y: -4
radiusX: 1
radiusY: 1},
LineTo{
x: radius - 15
y: 0},
] //elements
}, // Path
// code for the minutes hand
Path {
transforms: Rotate {
angle: bind minutes * 6 - 90
}
fill: Color.BLACK
elements: [
MoveTo {
x: 4,
y: 4},
ArcTo {
x: 4
y: -4
radiusX: 1
radiusY: 1},
LineTo{
x: radius
y: 0},
] // elements
} // Path
] //content
}
]
};
}

init {
var timeline = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: [
KeyFrame {
time: 1s
canSkip: true
action: function() {
nextTick();
}
}
]
}
timeline.play();
}
}

Web start depolyment is rather easy as it is possible to take advantage of the JavaFX extension and just add:



to your jnlp file and then do the usual jnlp stuff.

Saturday, September 12, 2009

Country Flag Decorations On Your Swing Components

Here is how to get country flag icon decorations on your JTable cells, JList cells, JCombobox'es and JTextFields.




First you need to grab the Flag Icons from the famfamfam site and package them into a jar file and add it to your classpath. The JTextField flag decoration is done via the BuddySupport in the xswingx project.

Then we need to set up a mapping of country names to famfamfam icon names like this:

private final String[] countries = { "AFGHANISTAN", "AF", "Ă…LAND ISLANDS",...}
Map<String, String> countryMap = new HashMap<String, String>();
for (int i = 0; i < countries.length;) {
countryMap.put(countries[i], countries[i + 1]);
i += 2;
}
countryFlagDecorator = new CountryFlagDecorator(countryMap);
// lazy do CountryFlagDecorator.getInstance() and skip the above mapping setup

Now we can decorate e.g., a JCombobox like this:

JComboBox flagCombo = new JComboBox(new String[] { "DENMARK", "SWEDEN",
"NORWAY" });
countryFlagDecorator.addCountryFlagDecorations(flagCombo);

A JTable cell like this:

JTable jt = new JTable(data, fields);
TableColumn col = jt.getColumnModel().getColumn(2);
col.setCellRenderer(new CountryFlagTableCellRendere(
countryFlagDecorator));
col.setCellEditor(new CountryFlagCellEditor(countryFlagDecorator,
new String[] { "DENMARK", "SWEDEN", "NORWAY" }));

A Jlist (with a small twist)

JList flagList = new JList(new String[] { "DK", "SE", "NO", "US" });
flagList.setCellRenderer(new CountryFlagListCellRendere(
countryFlagDecorator, new CountryNameConverter() {
@Override
public String convertCountryName(String countryName) {
if ("DK".equals(countryName)) {
return "DENMARK";
} else if ("SE".equals(countryName)) {
return "SWEDEN";
} else if ("NO".equals(countryName)) {
return "NORWAY";
} else if ("US".equals(countryName)) {
return "UNITED STATES";
}
return null;
}
}));

And a JTextField like this:

JTextField flagField = new JTextField("");
PromptSupport.setPrompt("Write name of a country, e.g., DENMARK", flagField);
countryFlagDecorator.addCountryFlag(flagField, null);

Download the source

Live Traffic Map