Note: I don’t do this any more. Third party APIs are a world of hurt – you save some time up front and end up chasing someone else’s upgrade path on their agenda, supporting their ads. There’s nothing fundamentally wrong with building cloud services and dependencies into your work, as long as you don’t want it to be up there for more than six weeks in my experience.
In their increasing commercialisation as Pachube went away from the original hacker ethos to Cosm, and now Xively, Xively have totally borked the documentation, at least for freebie cheapskates.
The most obvious thing you want to do with an IoT device is to chart the time series. With Pachube and Cosm that was easy. It’s also pretty easy using Xively, but what isn’t easy is finding out how to do it!
This is what I’m after. I have a Geiger counter, and a PIC ticks up a counter each time it detects an event. After a minute is up, it reads the counter, sends it via LLAP over a serial RF network to a Raspberry Pi that uploads the count to Cosm Xively and resets the counter, and it all starts over again.
Note: I’ve now moved to running RRDtool on the Pi because I found Xively just a little bit too intermittent, plus there’s the whole getting control of my own data thing. I found it more reliable to store the data locally, generate the graph and then upload the graph. It isn’t necessarily Xively’s fault – I get the feeling my ADSL connection just isn’t reliable enough for something that needs to update every minute,
RRDtool looks a bit more grungy, though I kinda like that here –
Anyway, back to Xively, and how easy graphing from Xively worked for me –
It was easy to find out how to do it on Pachube and Cosm but because Xively is all about provisioning IoT bits rather than hackers’ web charting now it’s the devil’s own job to find out how to do it. Xively favour stackexchange for support nowadays, but they can be less than helpful to n00bs or those who do not show that they feel The Force to the required extent 😉
Here is what I did to embed a Xively chart:
Read this from Xively on how to embed a chart – it’s not linked from any obvious place.
It so happens that the png chart option is a specific instance of reading a single datastream. You, me, and everybody else asks Google ‘how do I embed a Xively chart image’. Xively think of this in terms of ‘how do I display as single datastream’ and they’d rather you not do that, but parse the data returned from the feed, so they hide the relevant page in a backwater of their documentation because they don’t see it as important. Which is entirely their right – after all, you get the support you pay for!
obviously fiddle with width height, colour to taste. Note that it doesn’t need you API KEY, because presumably a PNG isn’t about to start posting data back to your feed any time soon. One of the more useful extra items is the duration=3hours
which doesn’t happen to be one of the official parameters in the Xively historical parameters documentation, but it is in the example they give and is a useful undocumented feature. A shorter duration works better with the Geiger count.
Your device needs to be public access if you want other people to see it, natch 😉 Make it private and they will be invited to log in on Xively, which sort of defeats the point.
Now why did that have to be so hard?
Alternatives ways of showing Xively data
The embedded Xively chart is loaded when you load your page, it doesn’t auto-refresh like the display on your Xively Workbench. That’s fine for many things, but it is very Web 1.0 so if you want more then take a look at Dave’s method of using Google charts by getting the raw data back from Xively. That way you get a more responsive chart and can plot more than one thing at a time, but you are looking at a lot more work compared to the quick-and-dirty method of an embedded image.
Poundland in the UK sell batteries branded by Kodak, but the cheapest ones are zinc chloride. I thought ZnCl has gone out of use decades ago, but it is surprising how often people are penny-wise and pound foolish. Particularly in pound shops 😉
So I am looking to make something to track the discharge of a battery. This is also useful to qualify NiMH and NiCd batteries – these fade with time and it is good to know what sort of capacity is left.
Every minute it reports the voltage and current from the batteries running through a 2.5V torch bulb, the third bulb is maintained at 2.5V to provide a reference. It transmits the signal using radio to a datalogger. I got a camera to take a picture every 15 minutes, as a video the results are reasonably clear. A picture tells a thousand words – the Zinc Chloride batteries are crap
The left-hand bulb is powered by the ‘cheap’ battery that Poundland sell for 9p, the middle is powered by the ‘dearer’ alkalines they sell at about 17p.
I have a Ciseco OpenKontrol Gateway as a datalogger. It stores data to a SD card, but there is no law saying you couldn’t send it to some kind of IoT host like Xively. So all I need is the interface to the batteries to track the voltage and current; I used a 2.5V torch bulb for the load because it does not seem unreasonable to me to expect batteries to be used in a torch, I use two batteries in series, which seems to be the most common configuration in a torch. On the way to ground I use a 1 ohm resistor so I can measure between ground and the top of the bulb the battery voltage and between the resistor and ground the voltage corresponding to the current. Using 1 ohm makes the calculation easy, and also drops about 0.3V so the bulb is run at 2.7V at the start.
Now if you are looking to do this in a big way then these guys at Battery Showdown are doing this is a more rigorous way but their hardware is dearer. I’m also not absolutely sure that a constant current discharge is typical of many loads. something like a digital camera with a switch mode PSU actually increases its peak load with decreasing battery voltage because it is more a constant power device and needs to draw more current at a lower voltage to run the focusing bits. Something with a linear voltage regulator is a constant current to the dropout voltage of the regulator, but you don’t often see linear regulators in battery-powered kit.
The bulb isn’t ideal either, with a varying resistance with temperature, so I’d probably favour using resistive loads, and getting four/five battery channels instead of two. But the visuals were great for the video.
If you have a PC that can take RS232 then simply wire the output of the PIC to the PC serial port via a Max232 chip or single transistor inverter if you don’t need the radio connection. Since I have the datalogger logging other sensors it seems easy enough to use it as is.
I used a PIC16F870 that has five A/D channels – this gives me two battery channels. Each battery channel has one A/D port connected to A in the diagram, another one to B and 0V is connected to C. The voltage at B is the same as the current since R is 1Ω and the voltage at A is the battery voltage itself. The 10-bit A/D converter goes up to 1024, by setting the reference voltage to be the PIC power supply at the high end and PIC ground at the low end I made the maths easier by setting the PIC supply voltage to 5.12 V (this is within the PIC operational voltage spec of 4 to 5.5V) in which case a straight divide by two gives V in * 100, and matches the requirements acceptably.
Doing this for the Poundland batteries shows the difference between the alkaline and the ZnCl batteries
If we take the service life as down to 2V from 3V initially, then you get 1.7 hours from the cheap ones and 5.8 hours from the alkalines.Alkaline cost/hr is 17*2/5.8=5.9p/hr and the cheap batteries cost 9*2/1.7=10.6p/hr.
As a result you pay about twice as much to run something using the cheap Poundland batteries, and you get to replace the batteries three times as often. Saving money therefore costs you a fortune in aggravation and generates more waste. You also have this sort of problem if you don’t pay attention and leave the used batteries in too long.
The device offers an insight into the capacity of rechargeable batteries which is the more useful feature of it.
I am not really sure I understand the pathology behind the second wind the NiMH cells seem to have. I’d discard it as a fault in reading except that – the current shows the bulb did not blow or become loose, and the A/D and voltage reference is multiplexed, so the error should have shown elsewhere. will repeat this and see if it happens again – probably not as this battery has been totally flattened so it is not the same battery.
For rechargeables I will modify this rig to take four individual cells and discharge through four resistors. That way I don’t back-charge any weak cells, and I don’t need to measure the current as the terminal voltage and Ohms Law will tell me the current. If I run 2.7Ω I will run about 440mA.
-- JAL 2.4i
-- derived from univ-sensor_v06
-- Datalogger
-- adjust power supply to 5.12V using the LM317 regulator.
-- internal Vref = VCC = 5.12V
-- Can just divide ADC by 2 and shift dec point by two places
-- v06 12 Nov 2013 RM
-- CC BY-SA 3.0
-- http://creativecommons.org/licenses/by-sa/3.0/
-- serial port designed for use with a Ciseco XBBO board and radio ( Google it)
-- can also use w/o radio via a Max232 or a FTDI adapter instead
-- radio is powered down while ADC measurement made to avoid loading regulator voltage
include 16f870
pragma target clock 4_000_000
pragma target osc HS ; use a 4MHz crystal or resonator
pragma target WDT ENABLED
pragma target PWRTE ENABLED
pragma target BROWNOUT ENABLED
pragma target LVP DISABLED
pragma target CP DISABLED
pragma target CPD DISABLED
-- HS, -BOD, ,-LVP, -WDT, -CP = 0x3F22
;pragma target fuses 0x2007
include pic_general
include print
include format
-- set all interrupt sources disabled
;INTCON = 0
-- ADC includes delays so don't include this myself
-- V2 delay's
-- include delay_any_mc
-- more delay functions
-- include extradelay
-- the analogue pins
pin_a0_direction = input
pin_a1_direction = input
pin_a2_direction = input
pin_a3_direction = input
-- there is no a4 analogue channel
pin_a5_direction = input
pin_c7_direction = input
pin_c5_direction = output
pin_c6_direction = output
pin_A4_direction = output ; this is open drain so 3V3 pullup comes from XRF
alias xrf_sleep is pin_A4 ; pull this low to start the XRF, let go and float up to enter sleep
xrf_sleep=low ; must enable with ATSM2
-- now configure ADC (this is a 10-bit ADC)
const bit ADC_HIGH_RESOLUTION = true
const byte ADC_NCHANNEL = 1
const byte ADC_NVREF = ADC_NO_EXT_VREF
include adc
adc_init()
-- ok, now setup serial, we'll use this
-- to get ADC measures
-- all this is because the 16f870.inc file is not right for serial hardware 8/5/13
alias RCIF IS PIR1_RCIF
alias TXIF IS PIR1_TXIF
alias BRGH IS TXSTA_BRGH
alias RCIE IS PIE1_RCIE
alias TXIE IS PIE1_TXIE
alias TXEN IS TXSTA_TXEN
alias TRMT IS TXSTA_TRMT
alias SPEN IS RCSTA_SPEN
alias OERR IS RCSTA_OERR
alias CREN IS RCSTA_CREN
-- end of
-- all this is because the 16f870.inc file is not right for serial hardware
alias PEIE IS INTCON_PEIE
alias TMR1IE IS PIE1_TMR1IE
alias CCP1IF IS PIR1_CCP1IF
OPTION_REG = 0b00001111 ; PS asllocated to WDT and slowest rate selected (20ms*128)
PEIE = high
TMR1IE = high
T1CON=0b00110001 ; set timer 1 running internal osc 8xprescaled
CCP1CON=0b00001011 ; reset tmr1 on match, kicks off an A/D conversion
; these next values seem to call a period of 10s from previous project
CCPR1H=244 ; decimal
CCPR1L=36 ; decimal
-- so count is 244*256+36 = 62500 At 4MHz each tick is 1uS, prescsaled by 8 to 8uS ticks.
--
const byte cyclesmax=60*2 -- how many times to count 0.5s ticks
var word mincounter = 0
;var byte lastTMR0=0
;TMR0=0 -- clear at start
-- ok, now setup serial;
const serial_hw_baudrate = 9600
const bit usart_hw_serial = TRUE
const bit lowspeed=true
include print
include serial_hardware
serial_hw_init()
const byte booted[] = "aVVSTARTED--"
const byte prefix[] = "aVV"
const byte line1[] = "+++"
const byte line2[] = "ATSM2r"
const byte line3[] = "ATACr"
const byte line4[] = "ATDNr"
const byte line5[] = "ATID0AFFr"
const byte vpfx[]= "VLT"
procedure WaitOnOk() IS
-- state machine - aim is to hang and
-- if this doesn't get to 2 the WDT will start things over after 2 sec
-- if it gets to 2 it clrwdt anbd returns
var byte state=0
asm clrwdt
while state !=2 loop
if serial_hw_data_available then
if state==0 & serial_hw_data == "O" then
state=1 ; this is the only way to get to state 1
end if
if (state == 1) & (serial_hw_data == "K") then
state=2
else
state=0 ; because O was followed by something that wasn't K
end if
end if
asm clrwdt
end loop
end procedure
procedure justify(word IN myval) IS
-- value is going to be 0..1024 (in reality 0..512)
-- hundreds are integers, ie 2 d.p.
-- assumes radio is up and running
format_word_dec(serial_hw_data,myval,3,2) -- 0 .23
serial_hw_data="-"
end procedure
function get_volts() return bit is
var word reading1
var word reading2
var word reading3
var word reading4
var word reading5
asm clrwdt
-- get ADC result, high resolution
reading1 = adc_read_high_res(0)/2
asm clrwdt
-- get ADC result, high resolution
reading2 = adc_read_high_res(1)/2
asm clrwdt
-- get ADC result, high resolution
reading3 = adc_read_high_res(2)/2
asm clrwdt
-- get ADC result, high resolution
reading4 = adc_read_high_res(3)/2
asm clrwdt
-- get ADC result, high resolution
reading5 = adc_read_high_res(4)/2
asm clrwdt
-- send it back through serial
xrf_sleep=low -- start the radio a little before the data will be sent.
-- may want to change the LLAP prefix and comma del if not using radio
delay_100ms(2)
asm clrwdt
print_string(serial_hw_data,prefix)
print_string(serial_hw_data,vpfx)
serial_hw_data="A"
justify(reading1)
asm clrwdt
delay_100ms(5) -- no need for this dela if going straight out on serial
print_string(serial_hw_data,prefix)
print_string(serial_hw_data,vpfx)
serial_hw_data="B"
justify(reading2)
asm clrwdt
delay_100ms(5) -- allows the LLAP data to be received properly
asm clrwdt
print_string(serial_hw_data,prefix)
print_string(serial_hw_data,vpfx)
serial_hw_data="C"
justify(reading3)
asm clrwdt
print_string(serial_hw_data,prefix)
print_string(serial_hw_data,vpfx)
serial_hw_data="D"
justify(reading4)
asm clrwdt
delay_100ms(5)
asm clrwdt
print_string(serial_hw_data,prefix)
print_string(serial_hw_data,vpfx)
serial_hw_data="E"
justify(reading5)
asm clrwdt
delay_100ms(2) -- let the signal be transmitted and buffer empty
xrf_sleep=high -- stop the radio ftre the original data sent
return true
end function
-- MAIN PROGRAM START
asm clrwdt
asm sleep ; use the WDT to delay about 2s to
; boot the sensor and radio
asm clrwdt
-- now initialise the XRF to the system network ID and enable sleep mode
-- clear out any pre-existing RX buffer
var byte rxd
while serial_hw_data_available loop -- check if data is ready for us
rxd = serial_hw_data -- get the data
end loop
rxd=" "
if true then ; loop is simply to enable/disable
; if you have no radio then use if false then
asm clrwdt ; this setup routine for testing
xrf_sleep=low
; setup the radio using AT commands
print_string(serial_hw_data, line1) ; send +++
WaitOnOk
print_string(serial_hw_data, line5) ; change network with ATID0AFF
delay_100ms(1)
print_string(serial_hw_data, line2) ; enable sleep mode with ATSM2
delay_100ms(1)
print_string(serial_hw_data, line3) ; and use it - send commit
delay_100ms(1)
print_string(serial_hw_data, line4) ; ATDN we are done configuring
delay_100ms(10)
print_string(serial_hw_data,booted)
delay_100ms(2)
xrf_sleep=high -- power down the radio
end if
forever loop
asm clrwdt
if CCP1IF then
CCP1IF=false;
mincounter=mincounter+1
end if
if mincounter >= cyclesmax-1 then
mincounter=0
var bit b = get_volts()
end if
end loop