Blog Index | Next >> |
---|
Last week I more or less finished the hardware, time to work on the software. In this post I will explain the bits and pieces, the complete software source including the XDK project can be cloned from GitHub (https://github.com/AgriVision/nixie_display ).
Also mcb1 brought up the question how to know which function is displayed on the six digits when displaying all the different information like time, date, weather etc. To be honest I didn't think a lot about this when writing the application, but this is definitely an issue. Nowadays we are very much used to nice displays with alphanumeric characters and even pictures on our equipment. In the past this was different. Look for instance at the Wang calculator in the reply of dougw on one of my previous posts. My mission is to keep the counter as original as possible and certainly I don't want to add LEDs or displays to the exterior in order to display function information. Luckily the counter is equipped with a button, as described in the previous blog post. Using this button I found one solution to the problem described above. Read on for information about my solution.
Controlling the IO expanders
In blog post [Upcycle It] Nixie Display #8 - Controlling the nixie tubes I explained how the PCF8574APCF8574A IO expanders are controlled using Johnny-Five. In the final setup there are three such controllers, each with there own I2C address, set by the jumpers on the board. They are defined as follows:
var virtual1 = new five.Board.Virtual( new five.Expander({ controller: "PCF8574A", address: 0x3A }) ); var virtual2 = new five.Board.Virtual( new five.Expander({ controller: "PCF8574A", address: 0x39 }) ); var virtual3 = new five.Board.Virtual( new five.Expander({ controller: "PCF8574A", address: 0x38 }) );
Each controller consists of 8 outputs (inputs doesn't make sense in this application). The easiest single output pin in Johnny-Five is a led:
var extender1 = new five.Leds( Array.from({ length: 8 }, function (_, i) { return new five.Led({ pin: i, board: virtual1 }); }) ); var extender2 = new five.Leds( Array.from({ length: 8 }, function (_, i) { return new five.Led({ pin: i, board: virtual2 }); }) ); var extender3 = new five.Leds( Array.from({ length: 8 }, function (_, i) { return new five.Led({ pin: i, board: virtual3 }); }) );
Each IO extender is connected to two nixie tubes. The function nixie_disp(number, extender) will display the two digit number: number on the extender: extender. The function first converts the number to BCD and subsequently toggles the individual bits on or off.
function nixie_disp(number, extender) { var n1 = number % 10; var n2 = (number - n1) / 10; var bcd_number = 16 * n2 + n1; for (var i = 0; i < 8; i++) { var bit = (bcd_number & (1 << i)) != 0; //console.log(i,number,bcd_number,bit); if (bit) { extender[i].on(); } else { extender[i].off(); } } }
Now with this nixie_disp function it is easy to put useful numbers on the display, such as the current time:
function showTime() { var date = new Date(); var hour = date.getHours(); var sec = date.getSeconds(); var min = date.getMinutes(); nixie_disp(sec, extender1); nixie_disp(min, extender2); nixie_disp(hour, extender3); }
or date:
function showDate() { var date = new Date(); var year = date.getYear() - 100; var month = date.getMonth() + 1; var day = date.getDate(); nixie_disp(day, extender1); nixie_disp(month, extender2); nixie_disp(year, extender3); }
In order to keep the display up to date, finally we need a function which displays the information at a regular interval, in this case 500 ms:
function showAll() setInterval(function () { //showTime(); showDate(); }, 500); } showAll();
As you see we can programatically decide what to display by uncommenting the specific function call. Of course that is not what we want and as described in my previous post, luckily the counter is equipped with a button. My intention is to use this button to select the different display functions.
Reading the button
In my previous post I explained how I prepared the button input, but it wasn't wired by then. In order to be complete here are the pictures of the wired button:
To read the button status again Johnny-Five is used and as can be seen in the API documentation there are three button functions, that is 'hold', 'press' and 'release' (JavaScript Robotics: Button API (Johnny-Five)). This is a real nice feature, as we can diferentiate between different actions by holding or pressing the button. I decided to use it this way:
- Each function has its own number, which can be displayed or changed with the button:
- 0 - show time
- 1 - show date
- 2 - show temperature
- etc.
- When the button briefly is pressed, the function number will be displayed for one second.
- When the button is held for more than one second, the current function will be increased by one or set to 0 when the highest function is reached. This way we rotate over the different functions.
- After the button is released, the function number will be displayed for one second, and subsequently the new function data will be displayed.
Here is how the button is defined using the Johnny-Five button class:
var button = new five.Button({ pin: 0, // is GPIO182 on Intel Edison mini breakout board invert: true, holdtime: 1000 });
The holdtime is the time in milliseconds that the button must be held until emitting a "hold" event, one second in this case.
Three functions are defined to handle the button actions.
button.on("hold", function () { display_function += 1; if (display_function > MAXFUNCTION) { display_function = 0; } showDisplayFunction(display_function); switch_function = true; }); button.on("press", function () { switch_function = true; }); button.on("release", function () { setTimeout(function () { switch_function = false; }, 1000); });
switch_function is a global variable which is true when something is happening with the button, and turns false one second after the button is released. When the button is held, the global variable display_fuction is rotated over the available number of functions (MAXFUNCTION).
Then a main loop is running at an interval of 500 ms. It displays either the functions, which currently are only the time and date, or the number corresponding with the function:
function showAll() { setInterval(function () { if (switch_function) { showDisplayFunction(display_function); } else { switch (display_function) { case 0: showTime(); break; case 1: showDate(); break; } } }, 500); } showAll();
The function number is displayed on the right most two digits:
function showDisplayFunction(num) { nixie_disp(num, extender1); nixie_disp(0, extender2); nixie_disp(0, extender3); }
Weather information
Now time and date works, the next thing in the plan was to display weather information. This informations retrieved from the OpenWeatherMap service on the internet. There is a nice and simple to use API for Node.js available: https://www.npmjs.com/package/openweather-apis. It can be installed with npm:
root@edison_nixie:~# npm install openweather-apis
openweather-apis@3.3.2 node_modules/openweather-apis
root@edison_nixie:~#
Before using OpenWeatherMap you need to make an account in order to get a key which is needed to obtain weather data. (https://home.openweathermap.org/users/sign_up)
Next we can incorporate it into the code:
var weather = require('openweather-apis'); weather.setLang('en'); weather.setCity('yourcity'); weather.setUnits('metric'); weather.setAPPID('key obtained from openweathermap');
To obtain weather info we run the weather.getSmartJSON at an interval of one minute.
// get all the weather info as JSON file returned from openweathermap at an interval of 60 seconds function getWeather() { weather.getSmartJSON(function (err, JSON) { JSON_Weather = JSON; console.log(JSON_Weather); }); }; getWeather(); setInterval(getWeather, 60000); // get new weather data each minute
The JSON_Weather now holds the following weather information as JSON:
{ temp : 25, humidity : 88, pressure : 101325, description : 'sun', rain: 4, weathercode : 200 }
From which I'm using the temp, humidity, pressure and rain. If you want, you can extract much more information from OpenWeatherMap, but for now those are the most informative to me.
The ShowAll loop is expanded with the extra information:
// main loop function showAll() { if (switch_function) { showNumber(display_function); } else { switch (display_function) { case 0: showTime(); break; case 1: showDate(); break; case 2: if (JSON_Weather != null) { showNumber(Math.round(JSON_Weather.temp)); } else { showNumber(0); } break; case 3: if (JSON_Weather != null) { showNumber(JSON_Weather.humidity); } else { showNumber(0); } break; case 4: if (JSON_Weather != null) { showNumber(JSON_Weather.pressure); } else { showNumber(0); } break; case 5: if (JSON_Weather != null) { showNumber(JSON_Weather.rain); } else { showNumber(0); } break; } } } showAll(); setInterval(showAll, 500);
The reason for the if (JSON_Weather != null) { is that Node.js is an asynchronous programming model, which means that the software can reach that statement before the JSON_Weather is filled with information. There is also the new function showNumber(value) which displays six digits on the nixie display.
Displaying a six digit number
On the nixie display the first IO expander is connected to digit 1 and 2, the second to 3 and 4 and the third to 5 and 6. The function showNumber distributes the digits over the three expanders as follows:
function showNumber(number) { var n1n2 = number % 10000 var n1 = n1n2 % 100; var n2 = (n1n2 - n1) / 100; var n3 = (number - n1n2) / 10000 nixie_disp(n1, extender1); nixie_disp(n2, extender2); nixie_disp(n3, extender3); }
Settings file
Instead of hard coding user related information in the main.js program I decided to put the OpenWeatherMap Language, City, Units and APPID in a separate settings file. Also the function which will be displayed when the display is switched on, is defined in this file. The file format is JSON, which is obvious for Node.js/javascript:
// read settings from settings.json file // rename settings_template.json and fill with proper values var fs = require("fs"); console.log(__dirname); var settings = fs.readFileSync(__dirname + "/settings.json"); var jsonSettings = JSON.parse(settings);
Weather settings and startup display function can now be set with:
weather.setLang(jsonSettings.Lang); weather.setCity(jsonSettings.City); weather.setUnits(jsonSettings.Units); weather.setAPPID(jsonSettings.APPID); // Globals var display_function = jsonSettings.startupDisplay;
Here are the contents of the settings_template.json file. For your own use fill in the appropriate information and rename the file to settings.json.
{ "Lang": "en", "City": "YourCity", "Units": "metric", "APPID": "YourKey", "startupDisplay": 0 }
Side effect of this approach is that I don't need to put sensitive information, such as the OpenWeatherMap APPID on GitHub.
settings.json is put in .gitignore, and settings_template.json is provided as an example.
Function table
The table below shows the available functions with their units and the number format.
Number | Function | Format | |
---|---|---|---|
0 | Time | hhmmss | |
1 | Date | YYMMDD | |
2 | Temperature | °C | 0000CC |
3 | Humidity | % | 0000HH |
4 | Pressure | hPa | 00PPPP |
5 | Rain Volume last 3H | RRRRRR |
Demo video
This video shows all functions in sequence by holding and releasing the button.
As you can see it works like a charm. First and second goal reached .
Thats it for now, two more weeks left for adding some extra functions and wrap up.
Stay tuned!
Top Comments