The files contained in this repository can be downloaded to your computer using a svn client.
On Linux you simply type the command displayed below.

This URL has Read-Only access.

Statistics
| Revision:

root / trunk / DomotiGa / CHomeMatic.class @ 853

History | View | Annotate | Download (31.5 kB)

1
' Gambas class file
2
3
' Description:
4
' CHomeMatic.class
5
' Connect to HomeMatic LAN adapter via tcp socket.
6
7
' Development Status:
8
' Just started.
9
10
' Credits: JK
11
12
' DomotiGa - an open source home automation program.
13
' Copyright(C) 2008-2012 Ron Klinkien
14
15
' Read file called COPYING for license details.
16
17
PROPERTY TCPHost AS String
18
PROPERTY TCPPort AS Integer
19
PROPERTY HMDebug AS Boolean
20
PROPERTY HMLANid AS String
21
PROPERTY Pairing AS Boolean
22
23
PRIVATE sTCPHost AS String
24
PRIVATE iTCPPort AS Integer
25
PRIVATE bHMDebug AS Boolean
26
PRIVATE sHMLANid AS String
27
28
PUBLIC hHM AS NEW Socket
29
PUBLIC tHMBusy AS Timer
30
PUBLIC tHMConnect AS Timer
31
PUBLIC bHMPairEnabled AS Boolean
32
PUBLIC iStackCount AS Integer
33
34
PRIVATE bFirstByte AS Boolean = TRUE
35
PRIVATE RecBuf AS Byte[255]
36
PRIVATE bMessage AS Boolean = FALSE
37
PRIVATE iByteCount AS Integer = 0
38
PRIVATE cQueue AS NEW Collection  ' command buffer
39
PRIVATE bHMBusy AS Boolean  ' waiting for delay timer to finish
40
PRIVATE iConnectRetry AS Integer = 5 ' retries
41
PRIVATE iConnectDelay AS Integer = 60000 ' 1 minute
42
PRIVATE cAckstore AS NEW Collection
43
PRIVATE cMsgnr AS NEW Collection
44
PRIVATE cDestDevice_id AS NEW Collection
45
PRIVATE iMessageCounter AS Integer = 0
46
PRIVATE sPairing_lastMsg AS Integer ' need to retain after exiting the sub
47
PRIVATE bWaiting_Ack AS Boolean = FALSE
48
PRIVATE cDevice_state AS NEW Collection
49
PRIVATE cPayload AS NEW Collection
50
PRIVATE cTimeStamp AS NEW Collection
51
PRIVATE tKeepAlive AS Timer
52
PRIVATE sLastSent AS String
53
PRIVATE sNewDevice AS NEW String[7]
54
PRIVATE tAckTimer AS Timer
55
PRIVATE bHMLANConfirmed AS Boolean = TRUE
56
PRIVATE iUTCoffset AS Integer
57
PRIVATE iDelta AS Long
58
59
PUBLIC SUB HM_Ready()
60
61
  Main.WriteLog(("HomeMatic TCP socket connected."))
62
  Init()
63
64
END
65
66
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
67
' reconnect routine
68
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
69
PUBLIC SUB tHMConnect_Timer()
70
71
  IF NOT ConnectTCP() THEN
72
    INC iConnectRetry
73
    iConnectDelay *= iConnectRetry
74
    tHMConnect.Delay = iConnectDelay
75
  ENDIF
76
77
END
78
79
PUBLIC SUB HM_Closed()
80
81
  Main.WriteDebugLog(("[HomeMatic] ERROR: TCP socket closed by peer."))
82
  IF iConnectRetry < 6 THEN
83
    Main.WriteDebugLog(("[HomeMatic] Retry to connect" & IIf(iConnectRetry, " in " & (iConnectDelay / 60000) & " minutes.", ".")))
84
    tHMConnect.Start
85
  ENDIF
86
87
END
88
89
PUBLIC SUB HM_Found()
90
91
  Main.WriteLog(("HomeMatic IP address resolved."))
92
93
END
94
95
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
96
' connect to the host:port
97
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
98
PUBLIC FUNCTION ConnectTCP() AS Boolean
99
100
  ' try to close the connection
101
  TRY hHM.Close
102
103
  ' get a new one
104
  hHM = NEW Socket AS "HM"
105
  hHM.Connect(sTCPHost, iTCPPort)
106
107
  ' start keeping the HMLAN Adapter alive
108
  tKeepAlive = NEW timer AS "tKeepAlive"
109
  tKeepAlive.Delay = 20000
110
  tKeepAlive.Start
111
112
  ' all ok
113
  RETURN TRUE
114
115
CATCH ' some errors does not seem to catch an error if the cable is disconnected
116
117
  Main.WriteLog(("HomeMatic Error: ") & ERROR.Text)
118
  RETURN FALSE
119
120
END
121
122
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
123
' disconnect from the host
124
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
125
PUBLIC FUNCTION Disconnect() AS Boolean
126
127
  ' try to close the connection
128
  TRY tHMBusy.Stop
129
  TRY tHMConnect.Stop
130
  TRY hHM.Close
131
132
  Main.WriteLog(("HomeMatic TCP socket close."))
133
134
  ' all ok
135
  RETURN TRUE
136
137
CATCH ' some errors
138
139
  Main.WriteLog(("HM Error: ") & ERROR.Text)
140
  RETURN FALSE
141
142
END
143
144
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
145
' keep the HM LAN Adapter connection alive
146
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
147
148
PUBLIC SUB tKeepAlive_Timer()
149
150
  SendHMLAN("K")
151
152
  tKeepAlive.Stop
153
  tKeepAlive.Start
154
155
END
156
157
PUBLIC SUB HM_Error()
158
159
  DIM sString AS String = "HomeMatic: "
160
161
  ' handle error
162
  SELECT CASE hHM.Status
163
    CASE Net.CannotCreateSocket
164
      Main.WriteLog(sString & ("The system does not allow to create a socket."))
165
    CASE Net.HostNotFound
166
      Main.WriteLog(sString & ("Host '") & sTCPHost & ("' not found."))
167
    CASE Net.ConnectionRefused
168
      Main.WriteLog(sString & ("Unable to connect. Connection refused."))
169
    CASE Net.CannotRead
170
      Main.WriteLog(sString & ("Error reading data."))
171
    CASE Net.CannotWrite
172
      Main.WriteLog(sString & ("Error writing data."))
173
  END SELECT
174
175
END
176
177
PUBLIC SUB HM_Read()
178
179
  DIM bData AS Byte
180
181
  TRY READ #hHM, bData
182
  IF ERROR THEN Main.WriteDebugLog(("[HomeMatic] Error reading data from the TCP port! -> ") & Error.Text)
183
  ProcessReceivedChar(bData)
184
185
END
186
187
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
188
' control devices
189
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
190
PUBLIC SUB SendCommand(sAddress AS String, sValue AS String, iDeviceId AS Integer)
191
192
  DIM iValue, iSpace AS Integer
193
  DIM sCmd, sModel AS String
194
195
  ' find last space and split there to get command and value
196
  iSpace = RInStr(sValue, " ")
197
  IF iSpace = 0 THEN RETURN
198
199
  sCmd = Left(sValue, iSpace - 1)
200
  iValue = Int(Right(sValue, Len(sValue) - iSpace))
201
202
  ' get description of devicetype to use as model
203
  TRY sModel = Devices.FindDescrForDeviceType(Devices.FindModuleForDevice(iDeviceId))
204
  IF ERROR THEN RETURN
205
206
  IF sModel = "HM_CC_TC"
207
    SELECT sCmd
208
      CASE "Temperature"
209
        HM_CC_TC_Temperature(sAddress, iValue)
210
      CASE "Status Request"
211
        HM_CC_TC_Status(sAddress)
212
    END SELECT
213
    RETURN
214
  ENDIF
215
216
END
217
218
PRIVATE SUB ProcessPair(sAddress AS String, sStep AS String)
219
220
  SELECT sStep
221
    CASE "Pair1"
222
      Pair1(sAddress)
223
    CASE "Pair2"
224
      Pair2(sAddress)
225
    CASE "Pair3"
226
      Pair3(sAddress)
227
  END SELECT
228
229
END
230
231
PRIVATE SUB HM_CC_TC_Temperature(sAddress AS String, iValue AS Integer)
232
233
  DIM sStatusbits, sMsgtype, sSrc, sDst, sData, sCmd AS String
234
235
  sStatusbits = "A0"  ' wakeup, bidi, rpten
236
  sMsgtype = "11"
237
  sSrc = sHMLANid     ' address
238
  sDst = sAddress
239
  sData = "0202" & Hex$((iValue), 2)
240
  sCmd = sStatusbits & sMsgtype & sSrc & sDst & sData ' & iValue
241
  StackCommand(sCmd)
242
  Main.WriteDebugLog("[HomeMatic] " & sAddress & " Requested Temperature " & (iValue / 2) & " Degrees C")
243
244
END
245
246
PRIVATE SUB HM_CC_TC_Status(sAddress AS String)
247
248
  DIM sStatusbits, sMsgtype, sSrc, sDst, sData, sCmd AS String
249
250
  sStatusbits = "A1"  ' bidi, rpten
251
  sMsgtype = "01"
252
  sSrc = sHMLANid     ' address
253
  sDst = sAddress
254
  sData = "010E"
255
  sCmd = sStatusbits & sMsgtype & sSrc & sDst & sData
256
  StackCommand(sCmd)
257
  Main.WriteDebugLog("[HomeMatic] " & sAddress & " Requested status")
258
259
END
260
261
PRIVATE SUB Pair1(sAddress AS String)
262
263
  DIM sStatusbits, sMsgtype, sSrc, sDst, sData, sCmd AS String
264
265
  sStatusbits = "A0"  ' bidi, rpten
266
  sMsgtype = "01"
267
  sSrc = sHMLANid     ' address
268
  sDst = sAddress
269
  sData = "00050000000000"
270
  sCmd = sStatusbits & sMsgtype & sSrc & sDst & sData
271
  StackCommand(sCmd)
272
  Main.WriteDebugLog("[HomeMatic] " & sAddress & " Pairing stage 1")
273
274
END
275
276
PRIVATE SUB Pair2(sAddress AS String)
277
278
  DIM sStatusbits, sMsgtype, sSrc, sDst, sData, sCmd AS String
279
280
  sStatusbits = "A0"  ' bidi, rpten
281
  sMsgtype = "01"
282
  sSrc = sHMLANid     ' address
283
  sDst = sAddress
284
  sData = "000802010A0E0BFF0CDB" ' fixme hmlan id still hardcoded here
285
  sCmd = sStatusbits & sMsgtype & sSrc & sDst & sData 
286
  StackCommand(sCmd)
287
  Main.WriteDebugLog("[HomeMatic] " & sAddress & " Pairing stage 2")
288
289
END
290
291
PRIVATE SUB Pair3(sAddress AS String)
292
293
  DIM sStatusbits, sMsgtype, sSrc, sDst, sData, sCmd AS String
294
295
  sStatusbits = "A0"  ' bidi, rpten
296
  sMsgtype = "01"
297
  sSrc = sHMLANid     ' address
298
  sDst = sAddress
299
  sData = "0006"
300
  sCmd = sStatusbits & sMsgtype & sSrc & sDst & sData
301
  StackCommand(sCmd)
302
  Main.WriteDebugLog("[HomeMatic] " & sAddress & " Pairing stage 3")
303
304
END
305
306
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
307
' message for HMLAN adapter
308
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
309
PUBLIC SUB SendHMLAN(sBuffer AS String)
310
311
  QueueAdd(sBuffer & "\r\n")
312
313
END
314
315
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
316
' add command to stack
317
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
318
PUBLIC SUB StackCommand(sPayload AS String)
319
320
  DIM sMsgnr, sDst, sRaw, sHash, sSentHash, sMsgState, sState, sDummy AS String
321
322
  sDst = Mid$(sPayload, 11, 6)
323
  sMsgnr = MessageCounter()
324
325
  ' check how many commands have not been positive or negative confirmed.
326
  iStackCount = 0
327
  FOR EACH sDummy IN cAckstore
328
    IF sDummy = "QUEUED" THEN INC iStackCount
329
  NEXT
330
  IF iStackCount > 0 THEN Main.WriteDebugLog("[HomeMatic] FAILED COMMANDS " & iStackCount)
331
  ' populate with new commands
332
333
  sHash = sDst & sMsgnr
334
  cPayload.Add(sPayload, sHash)
335
  cAckstore.Add("STORED", sHash)
336
  cMsgnr.Add(sMsgnr, sHash)
337
  cDestDevice_id.Add(sDst, sHash)
338
  cTimestamp.Add(timestamp(), sHash)
339
340
  '-----------DEBUG- PRINT THE STACK------
341
  ' FOR EACH sPayload IN cPayload
342
  '   PRINT "dst " & cPayload.Key & " NR " & cMsgnr[cPayload.key] & " Payload " & sPayload & " time " & cTimestamp[cPayload.Key] & " ACK " & cAckstore[cPayload.Key]
343
  ' NEXT
344
  ' PRINT "-------------------------------"
345
346
  ' wait for Ack or NACK, else timeout
347
  FOR EACH sDummy IN cAckstore
348
    WHILE bHMLANConfirmed = FALSE
349
      SLEEP 0.1
350
    WEND
351
352
    TRY sState = cAckstore[cAckstore.Key]
353
    IF sState = "STORED" THEN
354
      WAIT 0.2  ' improves reliability
355
      TRY sRaw = "S" & timestamp() & "," & "00,00000000,01" & "," & Hex$((UNIXtime() - iDelta), 8) & "," & cMsgnr[cAckstore.Key] & cPayload[cAckstore.key]
356
      IF sRaw THEN
357
        TRY tAckTimer.Start
358
        bHMLANConfirmed = FALSE
359
        TRY sSentHash = Mid$(cPayload[cAckstore.key], 11, 6) & cMsgnr[cAckstore.Key]
360
        IF sSentHash THEN
361
          QueueAdd(sRaw, sSentHash)
362
        ENDIF
363
      ENDIF
364
    ENDIF
365
  NEXT
366
367
END
368
369
PUBLIC SUB QueueAdd(sCommand AS String, OPTIONAL SHash AS String) ' add command to queue
370
371
  cQueue.Add(sCommand & "\r\n", Rnd)
372
  IF sHash THEN cAckstore[sHash] = "QUEUED"
373
374
  ' process it
375
  IF bHMBusy = FALSE THEN DoQueue()
376
377
END
378
379
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
380
' process items in queue
381
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
382
PRIVATE SUB DoQueue()
383
384
 DIM sPacket AS String
385
386
 FOR EACH sPacket IN cQueue
387
   WriteCommand(sPacket, cQueue.Key)
388
   ' BREAK ' only do first one, or wait for echo and do next one ?
389
  NEXT
390
391
END
392
393
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
394
' initialize interface
395
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
396
PUBLIC FUNCTION Init()
397
398
  ' define timer for interface busy
399
  tHMBusy = NEW Timer AS "tHMBusy"
400
  tHMBusy.Stop
401
402
  ' define Timer for Ack/Nack timeouts
403
  tAckTimer = NEW timer AS "tAcktimer"
404
  tAckTimer.Delay = 1000
405
  tAckTimer.Stop
406
407
  ' define timer for reconnect
408
  tHMConnect = NEW Timer AS "tHMConnect"
409
  tHMConnect.Delay = iConnectRetry
410
  tHMConnect.Stop
411
412
  ' get version
413
  IF bHMDebug THEN
414
   Main.WriteDebugLog("[HomeMatic] Getting version info.")
415
   SendHmlan("K")
416
  ENDIF
417
418
  ' get offset from UTC for time calculations
419
  iUTCoffset = UTCoffset()
420
421
END
422
423
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
424
' simple send string to HMLAN interface
425
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
426
PUBLIC SUB WriteCommand(sBuffer AS String, sKey AS String)
427
428
  DIM bByte AS Byte
429
  DIM iCnt AS Integer
430
431
  ' return if not connected yet
432
  IF hHM.Status <> Net.Connected THEN RETURN
433
  ' send the message to the tcp stream
434
  TRY WRITE #hHM, sBuffer, Len(sBuffer)
435
436
  IF ERROR THEN Main.WriteRFXDebugLog(("[HomeMatic] Unable to write to TCP port! -> ") & Error.Text)
437
438
  ' remove sent command from queue
439
  cQueue.Remove(sKey) 
440
441
  tHMBusy.Delay = 100 ' 100mS
442
  IF bHMBusy = FALSE THEN bHMBusy = TRUE
443
  tHMBusy.Start
444
445
END
446
447
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
448
' interface busy reset
449
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
450
PUBLIC SUB tHMBusy_Timer()
451
452
  bHMBusy = FALSE
453
  tHMBusy.Stop
454
455
  IF cQueue.Count THEN ' if there are more commands left in queue, send next one
456
  DoQueue()
457
  ENDIF
458
459
END
460
461
PRIVATE SUB ProcessReceivedChar(bTemp AS Byte)
462
463
  IF bFirstByte = TRUE THEN
464
    bFirstByte = FALSE
465
    iByteCount = 0
466
  ENDIF
467
468
  Main.ControlLed("HM", "On")
469
470
  TRY RecBuf[iByteCount] = bTemp
471
  IF ERROR THEN RETURN
472
473
  IF bTemp = &HA THEN
474
    iByteCount -= 2 ' do not count \r\n
475
    bMessage = TRUE
476
  ELSE
477
    INC iByteCount
478
  ENDIF
479
480
  IF bMessage THEN Process_Raw_Message
481
482
END
483
484
' process the message received from the HM-LAN interface
485
' all messages are , separated text strings, closed with CR/LF
486
' so far 5 different types found
487
' with 1 segment: Response to D1 or D2 etc, don't know what it is
488
' with 3 segments: received when sending a D to the interface
489
' with 4 segments: the HM-LAN Ack I00, also received when sending a Y to the interface
490
' with 6 segments: Routine messages and pairing
491
' with 7 segments: Version information, response to "K"
492
' sending B turns on the red light and waits (for new FW ?)
493
' monitor (and store for transmission) received signal strength
494
495
PRIVATE SUB Process_Raw_Message()
496
497
  DIM iCnt, iMaxSegments AS Integer
498
  DIM sReceivedData AS String
499
  DIM sMsgSegments AS String[]
500
501
  bMessage = FALSE
502
  bFirstByte = TRUE
503
504
  ' convert to a string so it can be split
505
  FOR iCnt = 0 TO iByteCount + 2
506
    sReceivedData &= Chr(RecBuf[iCnt])
507
  NEXT
508
  ' split it
509
  sMsgSegments = Split(sReceivedData)
510
  iMaxSegments = sMsgSegments.Count
511
512
  IF bHMDebug THEN
513
    SELECT CASE iMaxSegments
514
     CASE 3
515
       ' Main.WriteDebugLog("[HomeMatic] D " & (sMsgSegments[0]) & " Seg1 " & (sMsgSegments[1]) & " Time " & (sMsgSegments[2]))
516
     CASE 6
517
       ' Main.WriteDebugLog("[HomeMatic] Received from: " & (sMsgSegments[0]) & " Seg1 " & (sMsgSegments[1]) & " Time " & (sMsgSegments[2]) & " Seg3 " & (sMsgSegments[3]) & " RecRSSI " & (sMsgSegments[4]) & " Payload " & (sMsgSegments[5]))
518
     CASE 7
519
       Main.WriteDebugLog("[HomeMatic] " & (sMsgSegments[0]) & ", Firmware " & (sMsgSegments[1]) & ", Serialnr " & (sMsgSegments[2]) & ", Address " & (sMsgSegments[4]) & ", Uptime " & (Val("&H" & sMsgSegments[5]) DIV 1000) & " Seconds")
520
     CASE 1
521
       ' Main.WriteDebugLog("[HomeMatic] Dx " & (sMsgSegments[0]))
522
     CASE 4
523
       ' Main.WriteDebugLog("[HomeMatic] Y " & (sMsgSegments[0]) & " Seg1 " & (sMsgSegments[1]) & " Time " & (sMsgSegments[2]) & " Seg3 " & (sMsgSegments[3]))
524
     CASE ELSE 
525
       ' Main.WriteDebugLog(("[HomeMatic] ERROR: " & Error.Text & " at " & Error.Where & " Parsing HM data packet: "))
526
       DisplayPacket(RecBuf)
527
    END SELECT
528
  ENDIF
529
530
  ' do some errorchecking on the segmentdata, notify if something's not normal.
531
  ' idea: notify for low rssi values.
532
533
  IF iMaxSegments > 0 THEN Process_Raw_Messagetypes(iMaxSegments, sMsgSegments)
534
535
END
536
537
' process the message from the radio
538
PRIVATE SUB Process_Raw_Messagetypes(iMax AS Integer, sSegments AS String[])
539
540
  DIM sSrc, sDst, sMsgnr, sMsgtype, sPayload, sCmd, sDummy, sHash AS String
541
  DIM sHMRssi AS String  ' signal strength from device to HM adapter, not yet implemented
542
  DIM sDeviceRssi AS String  'signal strength from HM adapter to device, not yet implemented
543
  DIM bAckRequested, bWakeup, bAsleep, bBroadcast AS Boolean
544
  DIM iPayload_length, iCnt, iHMLANUptimecounter AS Integer
545
546
  SELECT CASE iMax
547
    CASE 6 ' this covers all normal communication
548
      sMsgnr = Left(sSegments[5], 2)
549
      bWakeUp = BTst(Val("&H" & (Mid(sSegments[5], 3, 2)) & "&"), 0)       'for later use and diagnostics
550
      bAsleep = BTst(Val("&H" & (Mid(sSegments[5], 3, 2)) & "&"), 1)       'for later use and diagnostics
551
      bBroadcast = BTst(Val("&H" & (Mid(sSegments[5], 3, 2)) & "&"), 2)    'for later use and diagnostics
552
      bAckRequested = BTst(Val("&H" & (Mid(sSegments[5], 3, 2)) & "&"), 5) 'for later use and diagnostics
553
      sMsgtype = (Mid(sSegments[5], 5, 2))                                 '&00 &01 &02, &10, &58 &70 
554
      sSrc = (Mid(sSegments[5], 7, 6))   
555
      sDst = (Mid(sSegments[5], 13, 6))  
556
      sPayload = (Right(sSegments[5], -18))
557
      iPayload_length = Len(Right(sSegments[5], -18))                      'for later use and diagnostics
558
559
      ' Messagetypes :
560
      ' &00 config request from Device
561
      ' &01 config message to Device
562
      ' &02 normally a plain Ack
563
      ' &10 status from device, sometimes with ack request
564
      ' &70 weather info
565
      ' &58 status from radiator valve
566
567
      ' send a simple Ack to the device
568
      IF Sdst = sHMLANid AND bAckRequested = TRUE AND sMsgtype <> "00" AND sMsgtype <> "3F" AND Left$(sSegments[0], 1) = "E" THEN  
569
        sCmd = "+" & sSrc & ",00,00,"
570
        SendHMLAN(sCmd)
571
        sCmd = ("S" & Timestamp() & ",00,00000000,01," & Hex$((UNIXtime() - iDelta), 8) & "," & sMsgnr & "8002" & sDst & sSrc & "00")
572
        QueueAdd(sCmd)
573
      ENDIF
574
      IF Sdst = sHMLANid AND bAckRequested = TRUE AND sMsgtype = "3F" AND Left$(sSegments[0], 1) = "E" THEN 
575
        sCmd = "+" & sSrc & ",00,00,"
576
        SendHMLAN(sCmd)
577
        sCmd = ("S" & Timestamp() & ",00,00000000,01," & Hex$((UNIXtime() - iDelta), 8) & "," & sMsgnr & "803F" & sDst & sSrc & "0204" & Systemtime())
578
        QueueAdd(sCmd)
579
      ENDIF
580
581
      ' process received ACKs AND NACKs
582
583
      ' check for NACK
584
      sHash = sDst & sMsgnr
585
      IF (cMsgnr.exist(sHash) AND sSegments[1] = "0008") THEN
586
        cAckstore[sHash] = "NACK"
587
        sCmd = "+" & sDst & ",02,00,"
588
        SendHMLAN(sCmd)
589
        tAckTimer.Stop
590
        bHMLANConfirmed = TRUE
591
      ENDIF
592
593
      'check for ACK
594
      sHash = sSrc & sMsgnr 
595
      IF (cMsgnr.Exist(sHash) AND sSegments[1] = "0001") THEN
596
        cPayload.Remove(sHash)
597
        cAckstore.Remove(sHash)
598
        cMsgnr.Remove(sHash)
599
        cDestDevice_id.Remove(sHash)
600
        cTimestamp.Remove(sHash)
601
        sCmd = "+" & sSrc & ",00,00,"
602
        SendHMLAN(sCmd)
603
        tAckTimer.Stop
604
        bHMLANConfirmed = TRUE
605
      ENDIF
606
607
      ' check for open messages on the stack
608
      IF sSegments[1] = "0081" THEN
609
        FOR EACH sDummy IN cDestDevice_id
610
          IF cAckstore[cDestDevice_id.Key] = "NACK" AND Left$(cDestDevice_id.Key, 6) = sSrc
611
            sPayload = cPayload[cDestDevice_id.Key]
612
            cPayload.Remove(cDestDevice_id.Key)
613
            cAckstore.Remove(cDestDevice_id.Key)
614
            cMsgnr.Remove(cDestDevice_id.Key)
615
            cTimestamp.Remove(cDestDevice_id.Key)
616
            cDestDevice_id.Remove(cDestDevice_id.Key)
617
            StackCommand(sPayload)
618
          ENDIF
619
       NEXT
620
      ENDIF
621
622
      ' if Pairing enabled, run the pairing routine
623
      IF bHMPairEnabled
624
        Pair_me(sMsgnr, sSrc, sPayload, sMsgtype) ' run the pairing routine
625
        'ELSE 'Main.WriteDebugLog("[CUL] not pairing" ' deconstruct payload, and store all parameters in the database under the device id (for logs and graphs and actions)
626
      ENDIF
627
      ' analyze and display the data for 6 segment messages
628
      Display_Message(sSegments)
629
    CASE 7 ' HMLAN info
630
      ' sync the clocks
631
      iHMLANUptimecounter = Val("&H" & sSegments[5])
632
      iDelta = UNIXtime() - iHMLANUptimecounter
633
  END SELECT
634
635
END
636
637
PRIVATE SUB Pair_me(sMsgnr AS String, sNewDevice_id AS String, sPayload AS String, sMsgtype AS String)
638
639
  DIM sNewDevice_serH, sNewDevice_serA, sCmd, sDummy, sQuery, sValue AS String
640
  DIM sValues AS String[]
641
  DIM iCnt, iModuleref AS Integer
642
  DIM rDeviceInfo, rResult AS Result
643
644
  IF sMsgtype = "00" ' received a pairing request while pairing is enabled
645
    sNewDevice[0] = sNewDevice_id           ' address
646
    sNewDevice[2] = Left(sPayload, 2)       ' firmware version
647
    sNewDevice[3] = Mid(sPayload, 3, 4)     ' device type
648
    sNewDevice[4] = Mid(sPayload, 7, 2)     ' device sub type
649
    sNewDevice[5] = Mid(sPayload, 15, 4)    ' device class (not used yet, but maybe later)
650
    sNewDevice[6] = Mid(sPayload, 26, 6)    ' device additional information (not used yet)
651
    sNewDevice_serH = Mid(sPayload, 9, 20)  ' device serial in hex char
652
653
    FOR iCnt = 1 TO 17 STEP 2
654
      sNewDevice_serA &= Chr(Val("&" & (Mid(sNewDevice_serH, iCnt, 2))))
655
    NEXT
656
    sNewDevice[1] = sNewDevice_serA         ' device serial in ASCII
657
    Main.WriteDebugLog("[HomeMatic] Pairing Request from " & sNewDevice[0] & " of the type " & sNewDevice[3] & " and subtype " & sNewDevice[4])
658
659
    ' check if it's a supported HomeMatic device
660
    rDeviceInfo = Main.hDB.Exec("SELECT * FROM devicetypes_homematic WHERE type_code = &1", sNewDevice[3])
661
    IF rDeviceInfo!commands = "none supported"
662
      Main.WriteDebugLog("[HomeMatic] This device is not (yet) supported " & sNewDevice[0] & ", " & sNewDevice[1] & ", " & sNewDevice[2] & ", " & sNewDevice[3] & ", " & sNewDevice[4] & ", " & sNewDevice[5])
663
    ELSE
664
      ' pairing IS partially done IN hardware. A pairing timer makes sure it's not always responding to pairing requests
665
      ' the neighbours might be trying to pair ;)
666
667
      '----------------------------------------------
668
      '  respond to the pairing request
669
      '----------------------------------------------
670
671
      ' just clear any open commands for this device
672
      FOR EACH sDummy IN cDestDevice_id
673
        cDestDevice_id.Remove(cDestDevice_id.key)
674
      NEXT
675
      sCmd = "+" & sNewDevice_Id & ",00,00,"
676
      SendHMLAN(sCmd)
677
      sCmd = "+" & sNewDevice_Id & ",00,00,"
678
      SendHMLAN(sCmd)
679
      sCmd = "+" & sNewDevice_Id & ",00,00,"
680
      SendHMLAN(sCmd)
681
      sCmd = "-" & sNewDevice_Id & ",00,00,"
682
      SendHMLAN(sCmd)
683
      sCmd = "+" & sNewDevice_Id & ",00,00,"
684
      SendHMLAN(sCmd)
685
      sCmd = "+" & sNewDevice_Id & ",00,00,"
686
      SendHMLAN(sCmd)
687
      sCmd = "+" & sNewDevice_Id & ",00,00,"
688
      SendHMLAN(sCmd)
689
      sCmd = "+" & sNewDevice_Id & ",00,00,"
690
      SendHMLAN(sCmd)
691
      ProcessPair("Pair1")
692
693
      ' grab the last command from the stack (this assumes that we get there before the command is acknowledged and removed,which is after about 0.5 seconds)
694
      FOR EACH sDummy IN cDestDevice_id
695
        IF sDummy = sNewDevice_id THEN sLastSent = cDestDevice_id.Key
696
      NEXT
697
698
      cDevice_state.Add("idle", sNewDevice_Id)
699
      cDevice_state[sNewDevice_Id] = "pairing phase 1"
700
    ENDIF
701
  ENDIF
702
703
  IF cDevice_state[sNewDevice_Id] = "pairing phase 1"  ' add device address as well simultanous pairing requests
704
    IF NOT (cMsgnr.Exist(sLastSent))
705
      cDevice_state[sNewDevice_Id] = "pairing phase 2"
706
      ProcessPair("Pair2")
707
      ' grab the last command
708
      FOR EACH sDummy IN cDestDevice_id
709
        IF sDummy = sNewDevice_id THEN sLastSent = cDestDevice_id.Key
710
      NEXT
711
    ENDIF
712
  ENDIF
713
714
  IF cDevice_state[sNewDevice_Id] = "pairing phase 2" 
715
    IF NOT (cMsgnr.Exist(sLastSent))
716
      cDevice_state[sNewDevice_Id] = "pairing phase 3"
717
      ProcessPair("Pair3")
718
      FOR EACH sDummy IN cDestDevice_id
719
        IF sDummy = sNewDevice_id THEN sLastSent = cDestDevice_id.Key
720
      NEXT
721
    ENDIF
722
  ENDIF
723
724
  IF cDevice_state[sNewDevice_Id] = "pairing phase 3" 
725
    IF NOT (cMsgnr.Exist(sLastSent))
726
      cDevice_state[sNewDevice_Id] = "pairing complete"
727
    ENDIF
728
  ENDIF
729
730
  ' insert device in devices table if pairing was successful
731
  IF cDevice_state[sNewDevice_id] = "pairing complete"
732
    rDeviceInfo = Main.hDB.Exec("SELECT * FROM devicetypes_homematic WHERE type_code = &1", sNewDevice[3])
733
    sValue = rDeviceInfo!values
734
    sValues = Split(sValue)
735
    iModuleRef = rDeviceInfo!moduleref
736
    ' check if device exists with this address.
737
    rResult = Main.hDB.Exec("SELECT * FROM devices WHERE address=&1", sNewDevice_Id)
738
    IF NOT rResult.Available THEN 
739
      ' give it a temporary name (serialnumber)
740
      rResult = Main.hDB.Create("devices")
741
      rResult!name = sNewDevice[1]
742
      rResult!module = iModuleRef   ' translates from type_code, which is transmitted by the device to the correct devicetypes id
743
      rResult!interface = "36"  '
744
      rResult!address = sNewDevice_id
745
      rResult!enabled = TRUE
746
      rResult!onicon = "new.png"
747
      rResult!dimicon = "new.png"
748
      rResult!officon = "new.png"
749
      rResult!hide = FALSE
750
      rResult!log = FALSE
751
      rResult!logdisplay = FALSE
752
      rResult!logspeak = FALSE
753
      rResult!dimable = FALSE
754
      rResult!switchable = FALSE
755
      rResult!firstseen = Now()
756
      rResult!lastseen = Now()
757
      rResult!location = 1
758
      rResult.Update()
759
      Main.hDB.Commit()
760
761
      rResult = Main.hDB.Exec("SELECT id FROM devices WHERE address = &1", sNewDevice_id)
762
      sQuery = "CREATE TABLE domotigalogs." & rResult!id & " (id int, LastSeen CHAR(64), lastChanged CHAR(6))"
763
      Main.hDB.Exec(sQuery)
764
      FOR Icnt = 0 TO sValues.Count - 1
765
        sQuery = "ALTER TABLE domotigalogs." & rResult!id & " ADD COLUMN " & sValues[iCnt] & " FLOAT"
766
        Main.hDB.Exec(sQuery)
767
      NEXT
768
    ELSE
769
      Main.WriteDebugLog("[HomeMatic] Device with address " & sNewDevice_id & " already exists. Not creating any new tables. Delete the device and logtables first to clear old device data")
770
    ENDIF
771
  ENDIF
772
773
END
774
775
'----------------------------------------------
776
' display and log messages
777
'----------------------------------------------
778
PRIVATE SUB Display_Message(sSegments AS String[])
779
780
  DIM sSrc, sDst, sMsgnr, sMsgtype, sPayload AS String
781
  DIM bAckRequested, bWakeup, bAsleep, bBroadcast AS Boolean = FALSE
782
  DIM iCnt, iDeviceId, iHumidity, iValvestate, iPayload_length AS Integer
783
  DIM fTemperature, fSetTemperature AS Float
784
  DIM rResult AS Result
785
786
  sMsgnr = Left(sSegments[5], 2)
787
  bWakeUp = BTst(Val("&H" & (Mid(sSegments[5], 3, 2)) & "&"), 0)
788
  bAsleep = BTst(Val("&H" & (Mid(sSegments[5], 3, 2)) & "&"), 1)
789
  bBroadcast = BTst(Val("&H" & (Mid(sSegments[5], 3, 2)) & "&"), 2)
790
  bAckRequested = BTst(Val("&H" & (Mid(sSegments[5], 3, 2)) & "&"), 5)
791
  sMsgtype = (Mid(sSegments[5], 5, 2))  ' %1 &2, &10, &58 &70 &00
792
  sSrc = (Mid(sSegments[5], 7, 6))      ' sending device id
793
  sDst = (Mid(sSegments[5], 13, 6))     ' receiving device id
794
  sPayload = (Right(sSegments[5], -18))
795
  iPayload_length = Len(Right(sSegments[5], -18))
796
797
  TRY rResult = Main.hDB.Exec("SELECT id, module FROM devices WHERE address = &1", sSrc) ' don't bail out on unknown devices
798
  TRY rResult = Main.hDB.Exec("SELECT description FROM devicetypes WHERE id = &1", rResult!module)
799
  IF rResult.Available THEN
800
    SELECT rResult!description
801
      CASE "HM_CC_TC"
802
        SELECT sMsgtype
803
          CASE "70"
804
            iHumidity = (ascii2hex(Mid(sSegments[5], 23, 2)))
805
            fTemperature = (ascii2hex(Mid(sSegments[5], 19, 4))) / 10
806
            iDeviceId = Devices.Find(sSrc, Devices.FindInterface("HomeMatic LAN Adapter"))
807
            IF iDeviceId THEN Devices.ValueUpdate(iDeviceId, fTemperature, iHumidity, "", "")
808
            IF bHMDebug THEN Main.WriteDebugLog("[HomeMatic] " & sSrc & " Temperature " & fTemperature & " Humidity " & iHumidity)
809
          CASE "58"
810
            iValvestate = (ascii2hex(Mid(sSegments[5], 21, 2))) / 2.55
811
            iDeviceId = Devices.Find(sSrc, Devices.FindInterface("HomeMatic LAN Adapter"))
812
            IF bHMDebug THEN Main.WriteDebugLog("[HomeMatic] " & sSrc & " Requesting Valve for " & iValvestate & " % open")
813
            IF iDeviceId THEN Devices.ValueUpdate(iDeviceId, "", "", "", iValvestate)
814
          CASE "02"
815
            IF bAsleep = FALSE AND bAckRequested = FALSE AND Mid$(sSegments[5], 19, 4) = "0102" THEN 
816
             fTemperature = (ascii2hex(Mid(sSegments[5], 23, 2))) / 2
817
             IF bHMDebug THEN Main.WriteDebugLog("[HomeMatic] " & sSrc & " Set Temperature " & fTemperature)
818
             iDeviceId = Devices.Find(sSrc, Devices.FindInterface("HomeMatic LAN Adapter"))
819
             IF iDeviceId THEN Devices.ValueUpdate(iDeviceId, "", "", fTemperature, "")
820
            ELSE IF bAsleep = FALSE AND bAckRequested = FALSE AND Mid$(sSegments[5], 19, 2) = "00" THEN 
821
              Main.WriteDebugLog("[HomeMatic] " & sSrc & " Awake")
822
            ELSE
823
              Main.WriteDebugLog("[HomeMatic] " & sSrc & " Message type " & sMsgtype & " with unsupported content " & Left$(sSegments[5], -2))
824
            ENDIF
825
          CASE "10"
826
            IF (bAsleep = FALSE) AND IF (bAckRequested = TRUE) AND IF (Mid$(sSegments[5], 19, 4) = "0602") THEN 
827
              fTemperature = (ascii2hex(Mid(sSegments[5], 23, 2))) / 2
828
              IF bHMDebug THEN Main.WriteDebugLog("[HomeMatic] " & sSrc & " Manual override Temperature " & fTemperature)
829
              iDeviceId = Devices.Find(sSrc, Devices.FindInterface("HomeMatic LAN Adapter"))
830
              IF iDeviceId THEN Devices.ValueUpdate(iDeviceId, "", "", fTemperature, "")
831
            ELSE
832
              Main.WriteDebugLog("[HomeMatic] " & sSrc & " Message type " & sMsgtype & " with unsupported content " & Left$(sSegments[5], -2))
833
            ENDIF
834
          CASE "3F"
835
            Main.WriteDebugLog("[HomeMatic] " & sSrc & " Timesync") ' done in Ack message
836
          CASE ELSE
837
            IF bHMDebug THEN Main.WriteDebugLog("[HomeMatic] Unknown message " & Left$(sSegments[5], -2) & " from address " & sSrc)
838
        END SELECT ' HM-CC-TC
839
      CASE ELSE
840
        IF bHMDebug THEN Main.WriteDebugLog("[HomeMatic] Unknown Devicetype (Check Database)")
841
    END SELECT ' devices
842
  ELSE
843
    IF bHMDebug THEN
844
      IF sSrc = sHMLANid THEN
845
        ' Main.WriteDebugLog("[HomeMatic] sending " & Left$(sSegments[5], -2) & " to " & sDst)
846
      ELSE
847
        Main.WriteDebugLog("[HomeMatic] Received message " & Left$(sSegments[5], -2) & " from unknown device with address " & sSrc)
848
      ENDIF
849
    ENDIF
850
  ENDIF
851
852
END
853
854
PUBLIC SUB tAckTimer_Timer()
855
856
  ' PRINT "Command timed out"
857
  tAckTimer.Stop
858
  bHMLANConfirmed = TRUE
859
860
END
861
862
PRIVATE SUB Restack() ' not used
863
864
  DIM sSrc, sPayload AS String
865
866
  FOR EACH sSrc IN cDestDevice_id
867
    IF cAckstore[cDestDevice_id.Key] = "QUEUED"
868
      sPayload = cPayload[cDestDevice_id.Key]
869
      cPayload.Remove(cDestDevice_id.Key)
870
      cAckstore.Remove(cDestDevice_id.Key)
871
      cMsgnr.Remove(cDestDevice_id.Key)
872
      cTimestamp.Remove(cDestDevice_id.Key)
873
      cDestDevice_id.Remove(cDestDevice_id.Key)
874
      StackCommand(sPayload, "RESTACKED")
875
    ENDIF
876
  NEXT
877
878
END
879
880
PUBLIC SUB Timestamp() AS String ' mS since 00:00
881
882
  DIM sDateUnits AS String[4]
883
  DIM iMilliseconds, iSeconds, iMinutes AS Integer
884
885
  sdateUnits = Split(Format$(Now, "hh:nn:ssu"), ":")
886
  iMilliseconds = Val(Right$(sDateUnits[2], 3))
887
  iseconds = Val(Left$(sDateUnits[2], 2))
888
  iMilliseconds = 3600000 * sDateUnits[0] + 60000 * sDateUnits[1] + 1000 * iSeconds + iMilliseconds
889
  RETURN Hex$(iMilliseconds, 8)
890
891
END
892
893
' seconds since 01/01/2000
894
PUBLIC SUB Systemtime() AS String
895
896
  DIM fTimestamp AS Float
897
  DIM iTimestamp AS Integer
898
  DIM sHexTimestamp, sDate AS String
899
900
  fTimestamp = DateDiff("12/31/1999 00:00:00", Now, gb.Second)
901
  iTimestamp = fTimestamp - iUTCoffset
902
  sHexTimestamp = Hex$(iTimestamp, 8)
903
904
  RETURN sHexTimestamp
905
906
END
907
908
'offset in seconds from UTC
909
PUBLIC FUNCTION UTCoffset() AS Integer
910
911
  DIM iUTCtime, iLOCALtime AS Integer
912
913
  SHELL ("date -u +%s") TO iUTCtime
914
  iLOCALtime = DateDiff("01/01/1970 00:00:00", Now, gb.Second)
915
  RETURN iLOCALtime - iUTCtime
916
917
END
918
919
' unix time in mS since epoch
920
PUBLIC FUNCTION UNIXtime() AS Long
921
922
  DIM iReturnValue AS Long
923
  DIM sReturnValue AS String
924
925
  EXEC ["date", "-u", "+%s%N"] TO sReturnValue
926
  iReturnValue = (Val(sReturnValue)) / 1000000
927
928
  RETURN iReturnValue
929
930
END
931
932
PRIVATE SUB DisplayPacket(bBuf AS Byte[]) AS String
933
934
  DIM sMsg AS String
935
  DIM iCnt AS Integer
936
937
  FOR iCnt = 0 TO iByteCount
938
    sMsg &= Chr(bBuf[iCnt])
939
  NEXT
940
  RETURN sMsg
941
942
END
943
944
PRIVATE FUNCTION MessageCounter() AS String
945
946
  IF iMessageCounter < 255 THEN
947
    INC iMessageCounter
948
  ELSE
949
    iMessageCounter = 0
950
  ENDIF
951
952
  RETURN Hex$(iMessagecounter, 2)
953
954
END
955
956
PRIVATE FUNCTION ascii2hex(sAsciiString AS String) AS Short
957
958
  DIM iDecResult AS Short
959
960
  iDecResult = Val("&H" & sAsciiString & "&")
961
  IF iDecResult > 16384 THEN iDecResult = - (32768 - iDecResult)
962
963
  RETURN iDecResult
964
965
END
966
967
' implement properties
968
PRIVATE FUNCTION TCPHost_Read() AS String
969
970
  RETURN sTCPHost
971
972
END
973
974
PRIVATE SUB TCPHost_Write(Value AS String)
975
976
  sTCPHost = Value
977
978
END
979
980
PRIVATE FUNCTION TCPPort_Read() AS Integer
981
982
  RETURN iTCPPort
983
984
END
985
986
PRIVATE SUB TCPPort_Write(Value AS Integer)
987
988
  iTCPPort = Value
989
990
END
991
992
PRIVATE FUNCTION HMDebug_Read() AS Boolean
993
994
  RETURN bHMDebug
995
996
END
997
998
PRIVATE SUB HMDebug_Write(Value AS Boolean)
999
1000
  bHMDebug = Value
1001
1002
END
1003
1004
PRIVATE FUNCTION HMLANid_Read() AS String
1005
1006
  RETURN sHMLANid
1007
1008
END
1009
1010
PRIVATE SUB HMLANid_Write(Value AS String)
1011
1012
  sHMLANid = Value
1013
1014
END
1015
1016
PRIVATE SUB Pairing_Write(Value AS Boolean)
1017
1018
  bHMPairEnabled = Value
1019
1020
END
1021
1022
PRIVATE FUNCTION Pairing_Read() AS Boolean
1023
1024
  RETURN bHMPairEnabled
1025
1026
END