I2CWorker - vlizBE/dcafs GitHub Wiki
This manual won't go into the details of explaining what I2C is and how it's used. For that you should check the datasheet of the device you wish to implement.
See i2c:?
for the possible commands, the one to start with is i2c:adddevice,id,bus,address,script
Suggest we want to add the BME280 that uses address 0x76 on controller 0, this would become:
i2c:adddevice,bme,0,0x76,BME280
and in settings.xml the following section will be added (minus the comments).
<i2c>
<bus controller="0">
<device address="0x76" id="bme" script="bme280"/>
<!--
id -> this is used to identify the device in telnet and TaskManager etc
script -> id for the command script and name of the corresponding file in i2cscripts
address -> the 7-bit address of the device on the bus in hexadecimal format
Optionally, label can be set
-->
</bus>
</i2c>
And a blank commandset file in i2cscripts to get started:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<commandset script="bme280">
<!-- An empty command to start with -->
<command id="cmdname" info="what this does"/>
</commandset>
Currently there are five different instructions:
- read: to read data from a device
- write: to write data to a device
- alter: to read a register, apply a logical operation and write the result to the register read
- math: to apply a mathematical operation on read data
An example of a command with only reads
<commandset script="bme280">
<command id="calc_hum" info="Read the humidity registers." msbfirst="false">
<read reg="0xFD" msbfirst="true" bits="16" return="2"/>
<read reg="0xA1" bits="8" signed="false" return="1"/> <!-- H1: unsigned 8bit -->
<read reg="0xE1" bits="16" signed="true" return="2"/> <!-- H2: signed 16bit -->
<read reg="0xE3" bits="8" signed="false" return="1"/> <!-- H3: unsigned 8bit -->
<read reg="0xE4" msbfirst="true" bits="12" signed="true" return="2"/> <!-- H4: signed 12bit -->
<read reg="0xE5" bits="12" signed="true" return="2"/> <!-- H5: humidity signed short -->
<read reg="0xE5" bits="8" signed="true" return="1"/> <!-- H6: humidity signed char -->
</command>
</commandset>
Most attributes in a single read node can also be an attribute in the command node, when present in the command node this is considered the default for all read nodes unless the read node overrides this. Furthermore if not present in either node, the standard defaults are taken (see below).
Options:
-
msbfirst
For multibyte reads, this determines if the most significant byte is received first (true or false), default is true -
signed
Whether or not the result is signed 2's complement, default is false -
return
The total amount of bytes to read -
bits
how many bits should be combined to a single int, options: 8,10,12,16,20,24
Note: Not sure yet if the current way of bits/return is intuitive...
There are two options:
- return is the amount of bytes that are read and the amount of bits determines how these are divided in integers
- return is the amount of integers you want and with the amount of bits, the amount of bytes is calculated
Forgot why I picked the first option...
So for example
<read reg="0xFD" msbfirst="true" bits="16" return="2"/>
<!--
reg="0xFD" -> the register to start the read at is 0xFD
msbfirst="true -> the integers are send msbfirst, this is the default but the command node overrides this default to false
bits="16" -> Each integer is 16 bits long
return="2" -> Two bytes so 16 bit should be read, or a single 16bit integer
signed isn't mentioned, nor in the command node so this is false (globab default)
-->
<read reg="0xE5" bits="12" signed="true" return="2"/>
<!--
reg="0xE5" -> the register to start the read at is 0xFD
bits="12" -> Each integer is 12 bits long
signed="true"
return="2" -> Two bytes for a total of 16bits will be read, but only use 12 of them
msbfirst is false (command node default) but still is still todo for 10 and 12 bits,
for now it takes the full first byte and only the MS nibble/two bits of the second
-->
On the I2C bus the first example read would look like this:
[START]I2C_Address[W][A]->0xFD[AS]->[Start]I2C_Address[R][S]->readByte[0][AM]->readByte[1][NAK][STOP]
In which:
- [start] is the start condition from the master
- [W] last bit of the address is set to signify a write operation
- [R] last bit of the address is cleared to signify a read operation
- [stop] is the stop condition from the master
- [AS] is an acknowledge from the slave
- [AM] is an acknowledge from the master
- [NAK] is a 'Not' acknowlegde issued by the master to tell the slave to stop sending
An example of a command with only writes:
<command id="weather" info="Set weather recommends (1 sample/min, no filter, no oversampling)">
<write reg="0xF5" >0x00</write> <!-- standby dont care (forced mode), filter off, no spi 3w -->
<write reg="0xF2" >0x01</write> <!-- Humidity oversampling -->
<write reg="0xF4" >0x26 0x52</write>
</command>
Which is a lot simpler than the read operation, in I2C protocol, this looks like this
[START]I2C_Address[W][A]->0xF5[AS]->0x00[STOP]
[START]I2C_Address[W][A]->0xF2[AS]->0x01[STOP]
[START]I2C_Address[W][A]->0xF4[AS]->0x26[AS]->0x52[STOP]
This instruction is used to change the content of a byte register based on the current value and a logical operand.
<alter reg="0x0F" operand="or">0x10</alter> <!-- Read register 0x0F and apply 'OR 0x10' and overwrite it -->
<alter reg="0x0F" operand="and">0x10</alter> <!-- Read register 0x0F and apply 'AND 0x10' and overwrite it -->
<alter reg="0x0F" operand="xor">0x10</alter> <!-- Read register 0x0F and apply 'XOR 0x10' and overwrite it -->
<alter reg="0x0F" operand="not"></alter> <!-- Read register 0x0F, invert the result and overwrite it -->
This instruction is to make execution of the folllowing instructions wait till the device has responded to a address probe with Acknowlegde.
<!-- This will do up to 15 probe attempts until either an [AS] was received or 15 fails and the command is cancelled -->
<wait_ack>15</wait_ack>
This results in the following I2C operation: [START]I2C_Address [START]I2C_Address [START]I2C_Address ... [START]I2C_Address[AS]
This instruction is to perform mathematical operations on received data before it's send to targets. It follows the same rules as all the other math functionality
<command id="senses" info="Read the sense of the both outputs" bits="10">
<read reg="0xE0" return="2"/>
<!-- Because the packs per 10bits and we read 2 bytes, from this point i0 exists -->
<read reg="0xE2" return="2"/>
<!-- From this point i1 exists -->
<math>i0=i0*62.5</math> <!-- i0 refers to the first value read -->
<math>i1=i1*62.5</math> <!-- i1 refers to the second value read -->
<math>i1=i0*256+i1</math> <!-- This is allowed to -->
</command>
To use a command created with the script, type the following i2c:device id,command
or to use the earlier xml as an example i2c:bme,calc_hum
Note: device-id is case insensitive but command id isn't
What the above actually does:
- Check if there's a device with the id bme
- If so check if the script bme280 contains a command calc_hum
- If so, add the command to the execution queue
- If data was read:
- If the command had a label, the data would have been passed to the BaseWorker which processes it according to the label
- If any writables are registered for the device, they get
deviceid;cmdid;integer0;...;integerx
as a string
Given that all system commands can be used in TaskManager with the system output, the earlier i2c command can be used in a Task like this:
<!-- This will run the command 5 seconds after DAS has finished start-up -->
<task output="system" trigger="delay:5s" >i2c:bme,calc_hum</task>
Note: I2C commands are added to a queue so will be executed in order and one at a time. For now one worker services all controllers this might change in the future to have one worker per controller.