The following is a protocol I developed for a short range radio network without all the fluff associated with establishing and using a WiFi Ethernet presence. It is a simple star network with a master device initiating all communications to addressed slave modules. Messages may be passed back and forth-the content and interpretation of which is down to the application.
VB6 source code for the master is attached.
Protocol:
Master is always address 0
Slaves are address 1-998
Broadcast is address 999
Master initiates all communication and slaves only ever respond to Master requests for interaction (STAT or POLL).
Packets consist of a 4 character verb and then the payload.
All devices must be on the same "channel". It was explored that each slave might exist on its own channel but this was abandoned
early on because the cons outweighed the pros:
Separate channel: Pro slave does not need to parse the addresses from the packet-if it hears the packet then it must be for that slave.
Con Broadcast becomes impossible.
Device addresses limited to 127.
Cheap RxTx modules have no concept of channel.
Single channel: Pro Slave address is encoded as part of the packet header with a maximum of 998 slave devices. Different channels
permit single master with multiple slave zones if required, thus giving < 128000 devices (but broadcast and use of
cheapie modules will be impacted).
Broadcast is simple-all devices hear the broadcast and pick the addresses from the packet as normal.
Can use cheapie 433MHz TxRx pairs with no onboard processing (see preamble).
Con HC-12 module is more expensive
Command Source Details
---------------+-------+------------------------------------------
STAT: Master Master STATs address.
Slave MUST respond within 1.5 seconds and may send back data, thus a STAT contains an implicit POLL.
Nil response will result in the slave marked as not available in the status register (master will not POLL).
Slave must reply with ACK0 only-If the Slave has more to say, it must wait for a poll.
POLL: Master Master POLLs address.
Slave may respond but must do so within within 1.5 seconds if it has something to say but keeps quiet otherwise.
ACK0: Slave This is the terminating response to a STAT or POLL. This packet will cause the Master to accept the response and
move on to the next section of the cycle.
ACK1: Slave This is the response to a POLL only but signifies the slave has more to report. This packet will cause the
Master to accept the response and then immediately issue another POLL to the same slave. Multiple ACK1's may be
sent and the Master will continue to POLL and accept packets until receipt of ACK0 or a timeout occurs, at which
point the master will move on to the next section of the cycle. The slave must pause for 1.5 seconds between each
ACKx packet. An improvement would be to limit the amount of ACK1s to prevent a slave blocking the master.
INIT: Master The addressed slave must restart.
SAFE: Master The addressed slave must go to its safe mode state.
Broadcasts: Any packet with destination address 999 is assumed to be a broadcast. Usually only the Master Broadcasts. Slaves must not reply to broadcasts.
TIME: Master The payload is hh:mm:ss,dd/mm/yyyy
All slaves will respond to this packet and synchronise their clocks to the payload.
INIT: Master All slaves must restart.
SAFE: Master All slaves must go to their safe mode state.
FIRE: Master All slaves must go to their FIRE mode state.
Slaves must respond to STAT. The response (or lack of it) updates the internal status register for that slave. Slaves that do not
respond to a STAT will be skipped in the POLL cycle. The master will attempt to discover all addresses continually.
Master: STAT (nn)
Slave: Must reply with ACK0 to the Master. The payload may be empty. Failure to respond will record that address as
unavailable/empty in the slave register.
Master: POLL (nn)
Slave: May reply with ACK0 (or ACK1 if there are multiple payloads) to the Master. The payload may be empty but it is
recommended the slave not respond to a POLL if it has nothing to report. This reduces the cycle time and processing load.
There is no advantage to replying with an empty payload.
The Packet format (all nodes):
Note the bracket forms below do not form part of the message and are here simply to logically group sections.
[optional preamble]STX<checksum>GS{RC4<dest address><src address><<Packet>}ETX
The data between the GS and ETX is encrypted before transmission. The checksum is for all bytes between {}
RC4 (elsewhere on this wiki) is a complex algorithm with a moderate data requirement (0.5K). A simpler encryption algorithm XORing a
variable length key is available if the simple nature of the slaves cannot support it i.e. simple PIC/Atmel chips.
Smaller Micromites can struggle to maintain timings with RC4 and large packets-use simple encryption or slow the cycle.
The payload must only contain "Printable" characters in the range 32-126. This avoids erroneous control characters interfering with network operation.
A typical Tx from the master to device 05 looks like
Preamble When using cheap TxRx modules with no onboard processing, it is necessary to send a stream of 1's with occasional zero so the Rx
can set it's levels before the actual data arrives.
Without preamble, some bits may be lost while the Rx adjusts its levels. Anything to the left of the STX must be discarded.
Chr FE has a large amount of "1" space and the trailing 0 ensures the levels are set on Rx devices
immediately before they are required to be accurate. A string of at least 2 FE chars is effective.
Using devices like HC-12 where there is onboard processing to assure data integrity, a preamble is not necessary as it is
taken care of by the radio modem.
[FE FE ...]
Packet structure
02 = STX Start of Text, sync marker. This indicate the start of valid data.
CHK_HI ascii Hex of checksum of packet between the following GS and ETX
CHK_LO
1D = GS group separator, start of the addressing and instruction details.
30 = attention device 005 in ascii
30
35
30 = from the master 000 in ascii
30
30
50 Command verb, STAT, POLL, ACK0 etc...
49
4E
47
[ optional payload here ]
03 = ETX End of Text, marks the end of the packet. No further data is permitted.
Any response must begin transmission in the next 1.5 seconds
Master module.You need to add your own code to interpret the content of the packets (both master and slaves) but standardise functionality so the master and slaves know what each other is doing. The master does not have to be a MicroMite-I have working VB code which uses a HC-12 on a USB<->Serial adapter so a Windows server can control the network-this opens up all sorts of possibilities and is used in the Snooker Hall metering for club membership and time charging etc.
Slaves and (micromite)master log to their console but this may be disconnected in general use. The modules detect "first run" and will prompt for config (i.e. their address and name-which is usually the location) at the console, after that it stores it's config and just gets on with things. So use the console to set the thing up and then just leave it to run. The modules I built have their console connections brought to a 3.5mm stereo jack which goes to a USB<->Serial jobby plugged in the installers laptop, just plug in to each and turn on, configure and run then unplug and move on to the next.
The master "pings" every address from 001 to 998 every so often and if a module responds, it is marked as "live" and recorded along with it's name. Live Slave modules are polled in order for any messages interspersed with the next discovery. So the cycle might look like:
Stat 1 (responds)
Poll 1
Stat 2
Poll 1
Stat 3 (responds)
Poll 3
Stat 4
Poll 1
Stat 5
Poll 3
Stat 6
Poll 1
...
Stat 998
Time
Only known live salves are regularly polled and newly discovered slaves are added as they are discovered. This ensures that we discover all modules over time but do not delay polling known live modules.
Undiscovered addresses are polled continually in the above cycle allowing "live" addition of new modules without the need to re-initialse the master.
At the end of the cycle, a TIME packet is sent on the broadcast address and all slaves set their clocks.
INIT messages can be sent to restart an individual module, SAFE messages can be sent to individual or all modules to set them to their safe mode (maybe an engineer is about to open them up and doesn't want a mains circuit up his arm). A FIRE message can be sent to all modules-this sets the module to it's emergency mode-perhaps turn all the lights on, play a recorded message to evacuate the building etc...
Slave module. You will need to adapt it as you see fit but it works really well and offers some nice features.
Slave modules listen to radio transmissions for their address or the broadcast address and respond accordingly. I have used this for a number of different projects: Snooker/Pool table light control, Customer feedback pods etc.
The Master initiates all communication and transmits structured and encrypted packets for specific slave addresses.
The Master is always address 000 and slaves can be any from 001 to 998 (doesn't have to be contiguous). 999 is the broadcast address. You could easily extend this but probably a thousand slave modules will cover most installations. You don't have to use them all and the master won't waste time trying to talk to addresses that don't respond to discovery.
The master also sends out TIME packets so slaves don't need RTCs (much cheapness!), only a single RTC on a network of 1000 modules!
Slave Module Code:
Option Autorun On
Print "Node boot"
timer=0
Option Base 0
'Option Explicit
Const KEY$=">4!1x4q3z4+7%4{9?5\3HhH^5$9=6@1~6,7_7|1)7'3]7[9:8<3*8S9I9l7Z1eT0r1"
Const GS=&H1D
Const STX=2
Const ETX=3
Const io=1
Const DoPreamble=0 ' No. of preamble bytes. Only for when using cheap dumb RxTx module pairs. Drop the baud rate right down (2400 ish) And set this value to 2 or more
Const Link$="COM1:9600,260"
Dim MyAddr As Integer
Dim MyJob As String
Dim a$, tm$, cmd$, pl$, R ' R is the serial buffer
Dim Integer c,src,dst,n
Dim Integer FLAGS=0 ' 0 STX
' 1 GS
' 2 ETX
' 3 Init. device has restarted And never heard from the master
' 4 in FIRE mode
' 5 in SAFE mode
' 6 just been configured
' 7
' 8
' 9
'10
'11
'12
'13
'14
'15
'16
'17
'18
'19
'20
'21
'22
'23
'24
'25
'26
'27
'28
'29
'30
'31
Var Restore
FlagSet 3 ' just booted
IF MyAddr=0 Then
Do
Do
Input "Enter Node address (1-998) >", a$
MyAddr=int(val("0"+a$))
Loop Until MyAddr >0 And MyAddr<999
Do
Input "Enter Node Task Name >", a$
Myjob=A$
Loop Until MyJob<>""
Print "Is This Correct? (Y/N)";
Do:Input a$:a$=UCASE$(a$):Loop Until a$="Y" OR a$="N"
Loop While a$="N"
Var Save MyAddr,MyJob
FlagSet 6 ' Just completed setup-need INIT
EndIf
Open Link$ As #io ' may need to drop this for cheap TxRx modules, HC12 etc do not require
FlushIO io
Print "Node";MyAddr;" up in";Timer;"mS Name ";MyJob
Do
If Loc(#io)<>0 Then
C=Asc(Input$(1,#io))
Select Case C
Case 2
R=""
If FLAGS<>0 Then Goto SafeExit
FlagSet 0
Case &H1D ' test GS
If Flagtest(0)<>1 And FlagTest(2)<>0 Then Goto SafeExit
FlagSet 1
R=R+","
Case 3
If Flagtest(0)<>1 And FlagTest(2)<>1 Then Goto SafeExit
FlagSet 2
Case &H30 TO &h39
If Flagtest(0)<>1 Then Goto SafeExit ' only accept chars after STX
R=R+Chr$(C)
Case &h41 TO &H46
If Flagtest(0)<>1 Then Goto SafeExit
R=R+Chr$(C)
Case Else
'ignore anything else
End Select
EndIf
If (FLAGS And 7)=7 Then 'STX,GS,ETX
' check GS position(5)
c=Instr(R,",")
If c<>5 Then Goto BadFrame '? "GS bad position":
' data after GS should always be even count
c=Len(Mid$(R,6))
If (c And 1) Then Goto BadFrame '? "Data length is odd":GOTO BadFrame ' If bit0 set then odd number
'calculate checksum of rec'd data
c=Val("&H"+Left$(r,4))
a$=Mid$(R,6) 'payload
For n=1 To Len(a$):c=c-Asc(Mid$(a$,n,1)):Next
If c<>0 Then Goto BadFrame '? "bad checksum ";r:GOTO BadFrame
' For some reason, slaves calculate bad checksums For each others reply packets.
' haven't determined why but it gives a quick exit If the packet isn't For me
'decode valid frame
Timer=0
a$=DECRYPT$(a$)
dst=Val(Left$(a$,3)):src=Val(Mid$(a$,4,3)):cmd$=Mid$(a$,7,4):pl$=Mid$(a$,11)
If dst=MyAddr Then ' specific
Print Str$(dst,3);" ";TIME$;" ";cmd$;" ";pl$';Timer
Select Case cmd$
Case "STAT" ' must reply with info
RadioSend src,MyAddr,"ACK0:STAT"+Bin$(FLAGS,32)+":NAME="+MyJob+":NTYP="+MM.Device$+","+Str$(MM.Ver)
Case "POLL" ' may reply with data
If FlagTest 6=0 Then 'If 1 we are waiting INIT so do not answer
If int(Rnd*2))>0 Then ' decide To answer or not
RadioSend src,MyAddr,"ACK0"
EndIf
EndIf
Case "INIT"
CPU Restart
Case "SAFE"
Mode 5,pl$
' ignore anything else
End Select
ElseIf dst=999 Then ' broadcast only
Print Str$(dst,3);" ";Time$;" ";cmd$;" ";pl$
Select Case cmd$
Case "TIME"
If Len(pl$)=19 Then
If Mid$(pl$,9,1)="," Then
On Error Skip 1:Time$=Left$(pl$,8)
On Error Skip 1:Date$=Right$(pl$,10)
EndIf
EndIf
Case "INIT"
CPU Restart
Case "SAFE" ' safe mode For working on node
Mode 5,pl$
Case "FIRE" ' emergency mode-i.e. all nodes turn on lights
Mode 4,pl$
' other broadcast CASEs go here
End Select
EndIf
Goto SafeExit
EndIf
Goto LoopEnd
BadFrame:
' ignore it
SafeExit:
FlushIO(io)
LoopEnd:
Loop
SubMode(fg As Integer,x$)
If x$="OFF" Then
FlagRes fg
ELSE
FlagSet fg
EndIf
End Sub
Function ZPad$(x As Integer)
ZPad$=Right$("000"+Str$(x), 3)
End Function
SubRadioSend(d As Integer,s As Integer,msg As String)
Local qq$
Local Integer chk,m
qq$=ZPad$(d)+ZPad$(s)+msg
For m=1 To Len(qq$):chk=chk+Asc(Mid$(qq$,m,1)):Next
qq$=Chr$(STX)+HEX$(chk,4)+Chr$(GS)+ENCRYPT$(qq$)+Chr$(ETX)
Print#io, String$(DoPreamble,Chr$(&HFE))+qq$;
Pause Len(qq$)' wait For the packet To go
End Sub
Function ENCRYPT$(A$)
Local Integer N,P
Local B$
P=1
For N=1 To Len(A$)
B$=B$+HEX$(Asc(Mid$(A$,N,1),2) Xor Asc(Mid$(KEY$,P,1)),2)
P=P+1:If P>Len(KEY$) Then P=1
Next
ENCRYPT$=B$
End Function
Function DECRYPT$(A$)
Local Integer N,P
Local B$
P=1
For N=1 To Len(A$) Step 2
B$=B$+Chr$(Val("&H"+Mid$(A$,N,2)) Xor Asc(Mid$(KEY$,P,1)))
P=P+1:If P>Len(KEY$) Then P=1
Next
DECRYPT$=B$
End Function
SubFlagSet(bit As Integer)
FLAGS=FLAGS Or (2^bit)
End Sub
SubFlagRes(bit As Integer)
FLAGS=(FLAGS Or (2^bit)) Xor (2^bit)
End Sub
Function FlagTest(bit As Integer) As Integer
FlagTest=Abs(Sgn(FLAGS And (2^bit)))
End Function
SubFlushIO(CH)
Local A$
Do While LOC(#CH)>0
A$=Input$(Min(Loc(#CH),255),#CH)
Pause 20
Loop
R="":FLAGS=FLAGS And &HFFFFFFFFFFFFFFF8
End Sub