├── hassio ├── run.sh ├── config.json └── Dockerfile ├── ckoz0013 ├── README.TXT ├── lib_crc.h ├── lib_crc.c └── ckoz0013.c ├── Makefile ├── LICENSE ├── mqtt.h ├── main.h ├── usb.h ├── README.md ├── mqtt.cpp ├── usb.cpp ├── ckoz0014.c ├── ckoz0014.h └── main.cpp /hassio/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo "[Info] Starting gateway" 5 | 6 | xcomfortd/xcomfortd -v -h 172.30.32.1 -u username -P password 7 | 8 | -------------------------------------------------------------------------------- /ckoz0013/README.TXT: -------------------------------------------------------------------------------- 1 | This implements an attempt to reverse engineer the ckoz0013. I have 2 | abandoned this project, but it's included so that someone else can 3 | pick it up, if they like. 4 | 5 | -------------------------------------------------------------------------------- /hassio/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "XComfort", 3 | "version": "0.1", 4 | "slug": "xcomfort", 5 | "description": "Eaton XComfort to MQTT gateway", 6 | "startup": "before", 7 | "boot": "auto", 8 | "devices": ["/dev/bus/usb:/dev/bus/usb:rwm"], 9 | "options": {}, 10 | "schema": {} 11 | } 12 | -------------------------------------------------------------------------------- /hassio/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BUILD_FROM 2 | FROM $BUILD_FROM 3 | 4 | ENV LANG C.UTF-8 5 | RUN apk add libusb-dev mosquitto-dev build-base gcc git 6 | RUN git clone https://github.com/karloygard/xcomfortd && cd xcomfortd && make 7 | 8 | # Copy data for add-on 9 | COPY run.sh / 10 | RUN chmod a+x /run.sh 11 | 12 | CMD [ "/run.sh" ] 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LIBS = -lusb-1.0 -lmosquitto 2 | CFLAGS = -Wall -g -std=c++11 3 | CXXFLAGS = -Wall -g -std=c++11 4 | LDFLAGS = -g 5 | 6 | default: xcomfortd # test 7 | 8 | %.o: %.c 9 | $(CXX) $(CFLAGS) -c $< -o $@ 10 | 11 | xcomfortd: ckoz0014.o usb.o mqtt.o main.o 12 | $(CXX) $(LDFLAGS) $^ $(LIBS) -o $@ 13 | 14 | test: ckoz0013/ckoz0013.o ckoz0013/lib_crc.o 15 | $(CXX) $(LDFLAGS) $^ $(LIBS) -o $@ 16 | 17 | clean: 18 | rm -rf xcomfortd *.o 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Karl Anders Øygard 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | -------------------------------------------------------------------------------- /mqtt.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C++; c-file-style: "stroustrup" -*- */ 2 | 3 | /* 4 | * Copyright 2016 Karl Anders Oygard. All rights reserved. 5 | * Use of this source code is governed by a BSD-style license that can be 6 | * found in the LICENSE file. 7 | */ 8 | 9 | #ifndef _MQTT_GATEWAY_H_ 10 | #define _MQTT_GATEWAY_H_ 11 | 12 | #include "usb.h" 13 | 14 | int64_t getmseconds(); 15 | 16 | class MQTTGateway 17 | : public USB 18 | { 19 | public: 20 | 21 | MQTTGateway(bool verbose); 22 | 23 | virtual bool Init(int epoll_fd, 24 | const char* server, 25 | int port, 26 | const char* username, 27 | const char* password); 28 | virtual void Stop(); 29 | 30 | virtual int Prepoll(int epoll_fd); 31 | virtual void Poll(const epoll_event& event); 32 | 33 | protected: 34 | 35 | // Verbose logging 36 | 37 | bool verbose; 38 | 39 | // Mosquitto instance 40 | 41 | mosquitto* mosq; 42 | 43 | private: 44 | 45 | static void mqtt_connected(mosquitto* mosq, 46 | void* obj, 47 | int rc); 48 | 49 | static void mqtt_disconnected(mosquitto* mosq, 50 | void* obj, 51 | int rc); 52 | 53 | static void mqtt_message(mosquitto* mosq, 54 | void* obj, 55 | const struct mosquitto_message* message); 56 | 57 | void MQTTConnected(int rc); 58 | void MQTTDisconnected(int rc); 59 | virtual void MQTTMessage(const struct mosquitto_message* message) = 0; 60 | 61 | bool RegisterSocket(); 62 | 63 | // Time to reconnect, only set when we have been disconnected 64 | 65 | int64_t reconnect_time; 66 | }; 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /main.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C++; c-file-style: "stroustrup" -*- */ 2 | 3 | /* 4 | * Copyright 2016 Karl Anders Oygard. All rights reserved. 5 | * Use of this source code is governed by a BSD-style license that can be 6 | * found in the LICENSE file. 7 | */ 8 | 9 | #ifndef _XC_TO_MQTT_GATEWAY_H_ 10 | #define _XC_TO_MQTT_GATEWAY_H_ 11 | 12 | #include "mqtt.h" 13 | 14 | struct datapoint_change 15 | { 16 | // Linked list 17 | datapoint_change* next; 18 | 19 | // Datapoint this relates to 20 | int datapoint; 21 | 22 | int new_value; 23 | int sent_value; 24 | 25 | // number of retries 26 | int retries; 27 | 28 | // Event to be sent 29 | mci_tx_event event; 30 | 31 | // Non zero when we're waiting for an ack 32 | int64_t timeout; 33 | 34 | // The sequence number we're waiting for an ack for 35 | int active_message_id; 36 | }; 37 | 38 | class XCtoMQTT 39 | : public MQTTGateway 40 | { 41 | public: 42 | 43 | XCtoMQTT(bool verbose, bool use_syslog); 44 | 45 | int Prepoll(int epoll_fd); 46 | 47 | void SendDPValue(int datapoint, int value, mci_tx_event event); 48 | 49 | protected: 50 | 51 | virtual void Error(const char* fmt, ...); 52 | virtual void Info(const char* fmt, ...); 53 | 54 | private: 55 | 56 | void TrySendMore(); 57 | 58 | void MQTTMessage(const struct mosquitto_message* message); 59 | 60 | void PublishStatus(int datapoint, 61 | int value); 62 | 63 | virtual void Relno(int status, 64 | unsigned int rf_major, 65 | unsigned int rf_minor, 66 | unsigned int usb_major, 67 | unsigned int usb_minor); 68 | 69 | virtual void MessageReceived(mci_rx_event event, 70 | int datapoint, 71 | mci_rx_datatype data_type, 72 | int value, 73 | int signal, 74 | mgw_rx_battery battery, 75 | int seq_no); 76 | 77 | virtual void AckReceived(int success, int seq_no, int extra); 78 | 79 | /* Linked list that keeps track of requested datapoint changes. 80 | This buffers requests, in order to prevent overloading the 81 | stick. */ 82 | 83 | datapoint_change* change_buffer; 84 | 85 | // Next sequence no (0 to 15 looping) 86 | 87 | int next_message_id; 88 | 89 | // Messages in transit 90 | 91 | int messages_in_transit; 92 | 93 | // Log to syslog 94 | 95 | bool use_syslog; 96 | }; 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /usb.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C++; c-file-style: "stroustrup" -*- */ 2 | 3 | /* 4 | * Copyright 2016 Karl Anders Oygard. All rights reserved. 5 | * Use of this source code is governed by a BSD-style license that can be 6 | * found in the LICENSE file. 7 | */ 8 | 9 | #ifndef _USB_H_ 10 | #define _USB_H_ 11 | 12 | #include 13 | 14 | #include "ckoz0014.h" 15 | 16 | #define INTR_RECV_LENGTH 32 17 | #define INTR_SEND_LENGTH 32 18 | 19 | // This class implements the USB communication layer with the stick. 20 | 21 | class USB 22 | { 23 | public: 24 | 25 | USB(); 26 | 27 | virtual bool Init(int epoll_fd); 28 | virtual void Stop(); 29 | 30 | virtual void Poll(const epoll_event& event); 31 | 32 | bool CanSend() const { return !message_in_transit; } 33 | int Send(const char* buffer, size_t length); 34 | 35 | protected: 36 | 37 | virtual void Error(const char* fmt, ...) = 0; 38 | virtual void Info(const char* fmt, ...) = 0; 39 | 40 | int epoll_fd; 41 | 42 | private: 43 | 44 | static void relno(void* user_data, 45 | int status, 46 | unsigned int rf_major, 47 | unsigned int rf_minor, 48 | unsigned int usb_major, 49 | unsigned int usb_minor); 50 | 51 | static void message_received(void* user_data, 52 | mci_rx_event event, 53 | int datapoint, 54 | mci_rx_datatype data_type, 55 | int value, 56 | int signal, 57 | mgw_rx_battery battery, 58 | int seq_no); 59 | 60 | static void ack_received(void* user_data, 61 | int success, 62 | int seq_no, 63 | int extra); 64 | 65 | virtual void Relno(int status, 66 | unsigned int rf_major, 67 | unsigned int rf_minor, 68 | unsigned int usb_major, 69 | unsigned int usb_minor) {} 70 | 71 | virtual void MessageReceived(mci_rx_event event, 72 | int datapoint, 73 | mci_rx_datatype data_type, 74 | int value, 75 | int signal, 76 | mgw_rx_battery battery, 77 | int seq_no) {} 78 | 79 | virtual void AckReceived(int success, 80 | int seq_no, 81 | int extra) {} 82 | 83 | static void sent(struct libusb_transfer* transfer); 84 | static void received(struct libusb_transfer* transfer); 85 | 86 | void Sent(struct libusb_transfer* transfer); 87 | void Received(struct libusb_transfer* transfer); 88 | 89 | static void fd_added(int fd, short fd_events, void* source); 90 | static void fd_removed(int fd, void* source); 91 | 92 | void FDAdded(int fd, short fd_events); 93 | void FDRemoved(int fd); 94 | 95 | bool init_fds(); 96 | 97 | bool message_in_transit; 98 | 99 | xc_parse_data data; 100 | 101 | libusb_context* context; 102 | libusb_device_handle* handle; 103 | 104 | unsigned char recvbuf[INTR_RECV_LENGTH]; 105 | libusb_transfer* recv_transfer; 106 | 107 | unsigned char sendbuf[INTR_SEND_LENGTH]; 108 | libusb_transfer* send_transfer; 109 | }; 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | xComfort gateway 2 | ================ 3 | 4 | __Deprecated, development has moved to https://github.com/karloygard/xcomfortd-go.__ 5 | 6 | This code implements communication with the Eaton xComfort CKOZ-00/14 7 | Communication stick (CI stick). The CI stick needs to be added into 8 | the network with CKOZ-00/13. 9 | 10 | xComfort is a wireless European home automation system, using the 11 | 868,3MHz band. The system is regrettably closed source. This code 12 | was reverse engineered from a variety of sources, plus some initial 13 | inspiration from . 14 | 15 | This code has been tested with and recognizes at least the following 16 | messages: 17 | 18 | * MSG_ON 19 | * MSG_OFF 20 | * MSG_UP_PRESSED 21 | * MSG_UP_RELEASED 22 | * MSG_DOWN_PRESSED 23 | * MSG_DOWN_RELEASED 24 | * MSG_STATUS 25 | 26 | Furthermore, it can send on/off/dim%/start/stop messages to devices. 27 | 28 | xComfort status messages are not routed and have no delivery 29 | guarantees. When status messages are lost, lights may for instance be 30 | switched on and off, and you will never know. Careful placement of 31 | the USB stick is important, so that it can see these messages, 32 | however, in my case, some messages are still lost. Polling devices in 33 | a round robin fashion might provide a somewhat clumsy workaround for 34 | this, but that's presently not implemented. Newer xComfort devices 35 | support "extended status messages" that are routed, but I have no such 36 | devices and don't know if they work with this software. 37 | 38 | The code has been written without any kind of documentation from 39 | Eaton, and may not follow their specifications. 40 | 41 | The CKOZ-00/14 doesn't expose or know which devices hide behind the 42 | datapoints. It's the user's responsibility to send correct messages 43 | to the datapoints; this code does no validation of messages sent. 44 | However, the devices appear to ignore messages they don't understand. 45 | MRF can export a datapoint to device map, but I have not added support 46 | for that, as the benefits were limited. 47 | 48 | A simple application for forwarding events to and from an MQTT server is 49 | provided. This can be used eg. to interface an xComfort installation with 50 | [homebridge-mqttswitch](https://github.com/ilcato/homebridge-mqttswitch) 51 | or [Home Assistant](https://home-assistant.io/), with a little imagination. 52 | The application subscribes to the topics: 53 | 54 | "xcomfort/+/set/dimmer" (accepts values from 0-100) 55 | "xcomfort/+/set/switch" (accepts true or false) 56 | "xcomfort/+/set/shutter" (accepts down, up or stop) 57 | "xcomfort/+/set/requeststatus" (value is ignored) 58 | 59 | and publishes on the topics: 60 | 61 | "xcomfort/[datapoint number]/get/dimmer" (value from 0-100) 62 | "xcomfort/[datapoint number]/get/switch" (true or false) 63 | "xcomfort/[datapoint number]/get/shutter" (up, down or stop) 64 | 65 | Sending `true` to topic `xcomfort/1/set/switch` will send a message to 66 | datapoint 1 to turn on. This will work for both switches and dimmers. 67 | Sending the value `50` to `xcomfort/1/set/dimmer` will send a message 68 | to datapoint 1 to set 50% dimming. This will work only for dimmers. 69 | 70 | Likewise, `xcomfort/1/get/dimmer` and `xcomfort/1/get/switch` will be 71 | set to the value reported by the dimmer/switch, if and when datapoint 72 | 1 reports changes. Subscribe to the topic that's relevant for the 73 | device that's actually associated with the datapoint. Status reports 74 | are not routed in the xComfort network, so if your CI stick is not 75 | able to hear all devices, these status messages will be lost. 76 | 77 | By sending any message to the topic `xcomfort/1/set/requeststatus`, 78 | the application will ask datapoint 1 to report its status. 79 | 80 | _WARNING: The firmware "RF V2.08 - USB V2.05" is buggy and will read 81 | status reports from dimmers incorrectly as always off. This is 82 | resolved in the later "RF V2.10 - USB V2.05" firmware._ 83 | 84 | Copyright 2016 Karl Anders Øygard. All rights reserved. Use of this 85 | source code is governed by a BSD-style license that can be found in 86 | the LICENSE file. The code for shutters and more was contributed by 87 | Hans Karlinius. 88 | -------------------------------------------------------------------------------- /ckoz0013/lib_crc.h: -------------------------------------------------------------------------------- 1 | /*******************************************************************\ 2 | * * 3 | * Library : lib_crc * 4 | * File : lib_crc.h * 5 | * Author : Lammert Bies 1999-2008 * 6 | * E-mail : info@lammertbies.nl * 7 | * Language : ANSI C * 8 | * * 9 | * * 10 | * Description * 11 | * =========== * 12 | * * 13 | * The file lib_crc.h contains public definitions and proto- * 14 | * types for the CRC functions present in lib_crc.c. * 15 | * * 16 | * * 17 | * Dependencies * 18 | * ============ * 19 | * * 20 | * none * 21 | * * 22 | * * 23 | * Modification history * 24 | * ==================== * 25 | * * 26 | * Date Version Comment * 27 | * * 28 | * 2008-04-20 1.16 Added CRC-CCITT routine for Kermit * 29 | * * 30 | * 2007-04-01 1.15 Added CRC16 calculation for Modbus * 31 | * * 32 | * 2007-03-28 1.14 Added CRC16 routine for Sick devices * 33 | * * 34 | * 2005-12-17 1.13 Added CRC-CCITT with initial 0x1D0F * 35 | * * 36 | * 2005-02-14 1.12 Added CRC-CCITT with initial 0x0000 * 37 | * * 38 | * 2005-02-05 1.11 Fixed bug in CRC-DNP routine * 39 | * * 40 | * 2005-02-04 1.10 Added CRC-DNP routines * 41 | * * 42 | * 2005-01-07 1.02 Changes in tst_crc.c * 43 | * * 44 | * 1999-02-21 1.01 Added FALSE and TRUE mnemonics * 45 | * * 46 | * 1999-01-22 1.00 Initial source * 47 | * * 48 | \*******************************************************************/ 49 | 50 | 51 | 52 | #define CRC_VERSION "1.16" 53 | 54 | 55 | 56 | #define FALSE 0 57 | #define TRUE 1 58 | 59 | 60 | 61 | unsigned short update_crc_16( unsigned short crc, char c ); 62 | unsigned long update_crc_32( unsigned long crc, char c ); 63 | unsigned short update_crc_ccitt( unsigned short crc, char c ); 64 | unsigned short update_crc_dnp( unsigned short crc, char c ); 65 | unsigned short update_crc_kermit( unsigned short crc, char c ); 66 | unsigned short update_crc_sick( unsigned short crc, char c, char prev_byte ); 67 | -------------------------------------------------------------------------------- /mqtt.cpp: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C++; c-file-style: "stroustrup" -*- */ 2 | 3 | /* 4 | * Copyright 2016 Karl Anders Oygard. All rights reserved. 5 | * Use of this source code is governed by a BSD-style license that can be 6 | * found in the LICENSE file. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "mqtt.h" 17 | 18 | int64_t getmseconds() 19 | { 20 | struct timespec tp; 21 | 22 | clock_gettime(CLOCK_MONOTONIC_COARSE, &tp); 23 | return (int64_t(tp.tv_sec) * 1000) + (tp.tv_nsec / 1000000); 24 | } 25 | 26 | MQTTGateway::MQTTGateway(bool verbose) 27 | : verbose(verbose), 28 | mosq(NULL), 29 | reconnect_time(INT64_MAX) 30 | { 31 | } 32 | 33 | void 34 | MQTTGateway::mqtt_connected(mosquitto* mosq, void* obj, int rc) 35 | { 36 | MQTTGateway* this_object = (MQTTGateway*) obj; 37 | 38 | this_object->MQTTConnected(rc); 39 | } 40 | 41 | void 42 | MQTTGateway::MQTTConnected(int rc) 43 | { 44 | if (verbose) 45 | Info("MQTT Connected, %s\n", mosquitto_connack_string(rc)); 46 | 47 | mosquitto_subscribe(mosq, NULL, "xcomfort/+/set/+", 0); 48 | } 49 | 50 | void 51 | MQTTGateway::mqtt_disconnected(mosquitto* mosq, void* obj, int rc) 52 | { 53 | MQTTGateway* this_object = (MQTTGateway*) obj; 54 | 55 | this_object->MQTTDisconnected(rc); 56 | } 57 | 58 | void 59 | MQTTGateway::MQTTDisconnected(int rc) 60 | { 61 | if (verbose) 62 | Info("MQTT Disconnected, %s\n", mosquitto_strerror(rc)); 63 | 64 | // Attempt to reconnect in 15 seconds 65 | 66 | reconnect_time = getmseconds() + 15000; 67 | } 68 | 69 | void 70 | MQTTGateway::mqtt_message(mosquitto* mosq, void* obj, const struct mosquitto_message* message) 71 | { 72 | MQTTGateway* this_object = (MQTTGateway*) obj; 73 | 74 | this_object->MQTTMessage(message); 75 | } 76 | 77 | bool 78 | MQTTGateway::Init(int epoll_fd, const char* server, int port, const char* username, const char* password) 79 | { 80 | char clientid[24]; 81 | int err = 0; 82 | 83 | mosquitto_lib_init(); 84 | 85 | memset(clientid, 0, 24); 86 | snprintf(clientid, 23, "xcomfort"); 87 | mosq = mosquitto_new(clientid, 0, this); 88 | 89 | if (!mosq) 90 | return false; 91 | 92 | mosquitto_message_callback_set(mosq, mqtt_message); 93 | mosquitto_disconnect_callback_set(mosq, mqtt_disconnected); 94 | mosquitto_connect_callback_set(mosq, mqtt_connected); 95 | 96 | if (username && password) 97 | mosquitto_username_pw_set(mosq, username, password); 98 | 99 | err = mosquitto_connect(mosq, server, port, 30); 100 | 101 | if (err) 102 | { 103 | Error("failed to connect to MQTT server: %s\n", mosquitto_strerror(err)); 104 | return false; 105 | } 106 | 107 | if (!USB::Init(epoll_fd)) 108 | return false; 109 | 110 | return RegisterSocket(); 111 | } 112 | 113 | bool 114 | MQTTGateway::RegisterSocket() 115 | { 116 | epoll_event mosquitto_event; 117 | 118 | mosquitto_event.events = EPOLLIN; 119 | mosquitto_event.data.ptr = this; 120 | 121 | if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, mosquitto_socket(mosq), &mosquitto_event) < 0) 122 | { 123 | Error("epoll_ctl failed %s\n", strerror(errno)); 124 | return false; 125 | } 126 | 127 | return true; 128 | } 129 | 130 | void 131 | MQTTGateway::Stop() 132 | { 133 | USB::Stop(); 134 | 135 | if (mosq) 136 | { 137 | mosquitto_disconnect(mosq); 138 | mosquitto_destroy(mosq); 139 | 140 | mosq = NULL; 141 | } 142 | 143 | mosquitto_lib_cleanup(); 144 | } 145 | 146 | int 147 | MQTTGateway::Prepoll(int epoll_fd) 148 | { 149 | epoll_event mosquitto_event; 150 | 151 | mosquitto_event.data.ptr = this; 152 | 153 | if (reconnect_time < getmseconds()) 154 | { 155 | int rc = mosquitto_reconnect(mosq); 156 | 157 | if (rc) 158 | { 159 | Info("MQTT, Reconnecting failed, %s\n", mosquitto_strerror(rc)); 160 | 161 | // Attempt to reconnect in 15 seconds 162 | 163 | reconnect_time = getmseconds() + 15000; 164 | } 165 | else 166 | { 167 | RegisterSocket(); 168 | reconnect_time = INT64_MAX; 169 | } 170 | } 171 | 172 | if (mosquitto_socket(mosq) != -1) 173 | { 174 | // Mosquitto isn't making this easy 175 | 176 | if (mosquitto_want_write(mosq)) 177 | { 178 | mosquitto_event.events = EPOLLIN|EPOLLOUT; 179 | epoll_ctl(epoll_fd, EPOLL_CTL_MOD, mosquitto_socket(mosq), &mosquitto_event); 180 | } 181 | else 182 | { 183 | mosquitto_event.events = EPOLLIN; 184 | epoll_ctl(epoll_fd, EPOLL_CTL_MOD, mosquitto_socket(mosq), &mosquitto_event); 185 | } 186 | } 187 | 188 | // Should be called "fairly frequently" 189 | 190 | mosquitto_loop_misc(mosq); 191 | 192 | // 500ms minimum timeout, for the above call 193 | 194 | return 500; 195 | } 196 | 197 | void 198 | MQTTGateway::Poll(const epoll_event& event) 199 | { 200 | if (event.data.ptr == this) 201 | { 202 | // This is for mosquitto 203 | 204 | if (event.events & POLLIN) 205 | mosquitto_loop_read(mosq, 1); 206 | if (event.events & POLLOUT) 207 | mosquitto_loop_write(mosq, 1); 208 | } 209 | else 210 | USB::Poll(event); 211 | } 212 | 213 | -------------------------------------------------------------------------------- /usb.cpp: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C++; c-file-style: "stroustrup" -*- */ 2 | 3 | /* 4 | * Copyright 2016 Karl Anders Oygard. All rights reserved. 5 | * Use of this source code is governed by a BSD-style license that can be 6 | * found in the LICENSE file. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "usb.h" 17 | 18 | #define EP_IN (1 | LIBUSB_ENDPOINT_IN) 19 | #define EP_OUT (2 | LIBUSB_ENDPOINT_OUT) 20 | 21 | extern int do_exit; 22 | 23 | void 24 | USB::received(struct libusb_transfer* transfer) 25 | { 26 | USB* this_object = (USB*) transfer->user_data; 27 | 28 | this_object->Received(transfer); 29 | } 30 | 31 | void 32 | USB::Received(struct libusb_transfer* transfer) 33 | { 34 | if (transfer->status != LIBUSB_TRANSFER_COMPLETED) 35 | { 36 | Error("irq transfer status %d, terminating\n", transfer->status); 37 | 38 | do_exit = 2; 39 | libusb_free_transfer(transfer); 40 | recv_transfer = NULL; 41 | } 42 | else 43 | { 44 | xc_parse_packet((unsigned char*) transfer->buffer, transfer->length, &data); 45 | 46 | // Resubmit transfer 47 | 48 | if (libusb_submit_transfer(recv_transfer) < 0) 49 | do_exit = 2; 50 | } 51 | } 52 | 53 | void 54 | USB::relno(void* user_data, 55 | int status, 56 | unsigned int rf_major, 57 | unsigned int rf_minor, 58 | unsigned int usb_major, 59 | unsigned int usb_minor) 60 | { 61 | USB* this_object = (USB*) user_data; 62 | 63 | this_object->Relno(status, rf_major, rf_minor, usb_major, usb_minor); 64 | } 65 | 66 | void 67 | USB::message_received(void* user_data, 68 | mci_rx_event event, 69 | int datapoint, 70 | mci_rx_datatype data_type, 71 | int value, 72 | int signal, 73 | mgw_rx_battery battery, 74 | int seq_no) 75 | { 76 | USB* this_object = (USB*) user_data; 77 | 78 | this_object->MessageReceived(event, 79 | datapoint, 80 | data_type, 81 | value, 82 | signal, 83 | battery, 84 | seq_no); 85 | } 86 | 87 | void 88 | USB::ack_received(void* user_data, 89 | int success, 90 | int seq_no, 91 | int extra) 92 | { 93 | USB* this_object = (USB*) user_data; 94 | 95 | this_object->AckReceived(success, seq_no, extra); 96 | } 97 | 98 | void 99 | USB::sent(struct libusb_transfer* transfer) 100 | { 101 | USB* this_object = (USB*) transfer->user_data; 102 | 103 | this_object->Sent(transfer); 104 | } 105 | 106 | void 107 | USB::Sent(struct libusb_transfer* transfer) 108 | { 109 | if (transfer->status != LIBUSB_TRANSFER_COMPLETED) 110 | { 111 | Error("irq transfer status %d?\n", transfer->status); 112 | 113 | do_exit = 2; 114 | libusb_free_transfer(transfer); 115 | send_transfer = NULL; 116 | } 117 | else 118 | message_in_transit = false; 119 | } 120 | 121 | void 122 | USB::fd_added(int fd, short fd_events, void * source) 123 | { 124 | USB* this_object = (USB*) source; 125 | 126 | this_object->FDAdded(fd, fd_events); 127 | } 128 | 129 | void 130 | USB::FDAdded(int fd, short fd_events) 131 | { 132 | epoll_event events; 133 | 134 | bzero(&events, sizeof(events)); 135 | if (fd_events & POLLIN) 136 | events.events |= EPOLLIN; 137 | if (fd_events & POLLOUT) 138 | events.events |= EPOLLOUT; 139 | 140 | if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &events) < 0) 141 | Error("epoll_ctl failed %s\n", strerror(errno)); 142 | } 143 | 144 | void 145 | USB::fd_removed(int fd, void* source) 146 | { 147 | USB* this_object = (USB*) source; 148 | 149 | this_object->FDRemoved(fd); 150 | } 151 | 152 | void 153 | USB::FDRemoved(int fd) 154 | { 155 | if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL) < 0) 156 | Error("epoll_ctl failed %s\n", strerror(errno)); 157 | } 158 | 159 | bool 160 | USB::init_fds() 161 | { 162 | const struct libusb_pollfd** usb_fds = libusb_get_pollfds(context); 163 | 164 | if (!usb_fds) 165 | return false; 166 | 167 | for (int numfds = 0; usb_fds[numfds] != NULL; ++numfds) 168 | FDAdded(usb_fds[numfds]->fd, usb_fds[numfds]->events); 169 | 170 | free(usb_fds); 171 | 172 | libusb_set_pollfd_notifiers(context, fd_added, fd_removed, this); 173 | 174 | return true; 175 | } 176 | 177 | USB::USB() 178 | : epoll_fd(-1), 179 | message_in_transit(true), 180 | context(NULL), 181 | handle(NULL), 182 | recv_transfer(NULL), 183 | send_transfer(NULL) 184 | { 185 | data.recv = message_received; 186 | data.ack = ack_received; 187 | data.relno = relno; 188 | data.user_data = this; 189 | } 190 | 191 | bool 192 | USB::Init(int fd) 193 | { 194 | int err; 195 | 196 | epoll_fd = fd; 197 | 198 | err = libusb_init(&context); 199 | if (err < 0) 200 | { 201 | Error("failed to initialise libusb\n"); 202 | return false; 203 | } 204 | 205 | handle = libusb_open_device_with_vid_pid(context, 0x188a, 0x1101); 206 | if (!handle) 207 | { 208 | Error("Could not find/open xComfort USB device\n"); 209 | return false; 210 | } 211 | 212 | if (libusb_kernel_driver_active(handle, 0) == 1) 213 | { 214 | err = libusb_detach_kernel_driver(handle, 0); 215 | if (err < 0) 216 | { 217 | Error("usb_detach_kernel_driver %d\n", err); 218 | return false; 219 | } 220 | } 221 | 222 | err = libusb_set_configuration(handle, 1); 223 | if (err < 0) 224 | { 225 | Error("libusb_set_configuration error %d\n", err); 226 | return false; 227 | } 228 | 229 | err = libusb_claim_interface(handle, 0); 230 | if (err < 0) 231 | { 232 | Error("usb_claim_interface error %d\n", err); 233 | return false; 234 | } 235 | 236 | recv_transfer = libusb_alloc_transfer(0); 237 | if (!recv_transfer) 238 | { 239 | Error("failed to allocate transfer %d\n", err); 240 | return false; 241 | } 242 | 243 | libusb_fill_interrupt_transfer(recv_transfer, handle, EP_IN, recvbuf, 244 | sizeof(recvbuf), received, (void*) this, 0); 245 | 246 | send_transfer = libusb_alloc_transfer(0); 247 | if (!send_transfer) 248 | { 249 | Error("failed to allocate transfer %d\n", err); 250 | return false; 251 | } 252 | 253 | libusb_fill_interrupt_transfer(send_transfer, handle, EP_OUT, sendbuf, 254 | sizeof(sendbuf), sent, this, 0); 255 | 256 | err = libusb_submit_transfer(recv_transfer); 257 | if (err < 0) 258 | return false; 259 | 260 | xc_make_config_msg((char*) sendbuf, MGW_CT_RELEASE, 0x0); 261 | 262 | err = libusb_submit_transfer(send_transfer); 263 | if (err < 0) 264 | return false; 265 | 266 | if (!init_fds()) 267 | return false; 268 | 269 | return true; 270 | } 271 | 272 | void 273 | USB::Poll(const epoll_event& event) 274 | { 275 | struct timeval tv = { 0, 0 }; 276 | 277 | libusb_handle_events_timeout(context, &tv); 278 | } 279 | 280 | 281 | int 282 | USB::Send(const char* buffer, size_t length) 283 | { 284 | int err; 285 | 286 | assert(!message_in_transit); 287 | 288 | bzero(sendbuf, INTR_SEND_LENGTH); 289 | memcpy(sendbuf, buffer, length); 290 | 291 | err = libusb_submit_transfer(send_transfer); 292 | if (err < 0) 293 | { 294 | Error("failed to submit transfer\n"); 295 | return -1; 296 | } 297 | 298 | message_in_transit = true; 299 | 300 | return 0; 301 | } 302 | 303 | void 304 | USB::Stop() 305 | { 306 | if (context) 307 | { 308 | if (recv_transfer) 309 | if (!libusb_cancel_transfer(recv_transfer)) 310 | while (recv_transfer) 311 | if (libusb_handle_events(NULL) < 0) 312 | break; 313 | 314 | if (send_transfer) 315 | if (!libusb_cancel_transfer(send_transfer)) 316 | while (send_transfer) 317 | if (libusb_handle_events(NULL) < 0) 318 | break; 319 | 320 | if (recv_transfer) 321 | libusb_free_transfer(recv_transfer); 322 | 323 | if (send_transfer) 324 | libusb_free_transfer(send_transfer); 325 | 326 | if (handle) 327 | { 328 | libusb_release_interface(handle, 0); 329 | libusb_close(handle); 330 | } 331 | 332 | libusb_exit(context); 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /ckoz0014.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C++; c-file-style: "stroustrup" -*- */ 2 | 3 | /* 4 | * Reverse engineered from a variety of sources, for the purpose of 5 | * interoperability. 6 | * 7 | * Copyright 2016 Karl Anders Oygard. All rights reserved. 8 | * Use of this source code is governed by a BSD-style license that can be 9 | * found in the LICENSE file. 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "ckoz0014.h" 17 | 18 | const char* xc_rxevent_name(enum mci_rx_event event) 19 | { 20 | switch (event) 21 | { 22 | case MSG_ACK: return "MSG_ACK"; 23 | case MSG_STAY_ONLINE: return "MSG_STAY_ONLINE"; 24 | case MSG_ALLIVE: return "MSG_ALLIVE"; 25 | case MSG_GET_OFFLINE: return "MSG_GET_OFFLINE"; 26 | case MSG_GET_EEPROM: return "MSG_GET_EEPROM"; 27 | case MSG_SET_EEPROM: return "MSG_SET_EEPROM"; 28 | case MSG_GET_CRC: return "MSG_GET_CRC"; 29 | case MSG_TIME: return "MSG_TIME"; 30 | case MSG_DATE: return "MSG_DATE"; 31 | case MSG_PAKET: return "MSG_PAKET"; 32 | case MSG_KILL: return "MSG_KILL"; 33 | case MSG_FACTORY: return "MSG_FACTORY"; 34 | case MSG_ON: return "MSG_ON"; 35 | case MSG_OFF: return "MSG_OFF"; 36 | case MSG_SWITCH_ON: return "MSG_SWITCH_ON"; 37 | case MSG_SWITCH_OFF: return "MSG_SWITCH_OFF"; 38 | case MSG_UP_PRESSED: return "MSG_UP_PRESSED"; 39 | case MSG_UP_RELEASED: return "MSG_UP_RELEASED"; 40 | case MSG_DOWN_PRESSED: return "MSG_DOWN_PRESSED"; 41 | case MSG_DOWN_RELEASED: return "MSG_DOWN_RELEASED"; 42 | case MSG_PWM: return "MSG_PWM"; 43 | case MSG_FORCED: return "MSG_FORCED"; 44 | case MSG_SINGLE_ON: return "MSG_SINGLE_ON"; 45 | case MSG_TOGGLE: return "MSG_TOGGLE"; 46 | case MSG_VALUE: return "MSG_VALUE"; 47 | case MSG_ZU_KALT: return "MSG_ZU_KALT"; 48 | case MSG_ZU_WARM: return "MSG_ZU_WARM"; 49 | case MSG_STATUS: return "MSG_STATUS"; 50 | case MSG_STATUS_APPL: return "MSG_STATUS_APPL"; 51 | case MSG_STATUS_REQ_APPL: return "MSG_STATUS_REQ_APPL"; 52 | default: return "-- unknown --"; 53 | } 54 | } 55 | 56 | const char* xc_rssi_status_name(int rssi) 57 | { 58 | if (rssi <= 67) 59 | return "good"; 60 | else if (rssi <= 75) 61 | return "normal"; 62 | else if (rssi <= 90) 63 | return "weak"; 64 | else if (rssi <= 120) 65 | return "very weak"; 66 | else 67 | return "unknown"; 68 | } 69 | 70 | const char* xc_battery_status_name(enum mgw_rx_battery state) 71 | { 72 | switch (state) 73 | { 74 | default: 75 | case MGW_RB_NA: return "not available"; 76 | case MGW_RB_0: return "empty"; 77 | case MGW_RB_25: return "very weak"; 78 | case MGW_RB_50: return "weak"; 79 | case MGW_RB_75: return "good"; 80 | case MGW_RB_100: return "new"; 81 | case MGW_RB_PWR: return "powerline"; 82 | } 83 | } 84 | 85 | const char* xc_shutter_status_name(int state) 86 | { 87 | switch (state) 88 | { 89 | default: 90 | case SHUTTER_STOPPED: return "stopped"; 91 | case SHUTTER_UP: return "up"; 92 | case SHUTTER_DOWN: return "down"; 93 | } 94 | } 95 | 96 | void xc_parse_packet(const unsigned char* buffer, size_t size, xc_parse_data* data) 97 | { 98 | struct xc_ci_message* msg = (struct xc_ci_message*) buffer; 99 | 100 | if (size < 2 || 101 | size < msg->message_size) 102 | return; 103 | 104 | switch (msg->type) 105 | { 106 | case MGW_PT_RX: 107 | data->recv(data->user_data, 108 | (enum mci_rx_event) msg->packet_rx.rx_event, 109 | msg->packet_rx.datapoint, 110 | (enum mci_rx_datatype) msg->packet_rx.rx_data_type, 111 | msg->packet_rx.value, 112 | msg->packet_rx.rssi, 113 | (enum mgw_rx_battery) msg->packet_rx.battery, 114 | msg->packet_rx.seqno); 115 | 116 | break; 117 | 118 | case MGW_PT_STATUS: 119 | { 120 | int i; 121 | int seq_and_pri = -1; 122 | int extra = -1; 123 | 124 | // The ACK parsing isn't completely understood 125 | 126 | switch (msg->pt_status.type) 127 | { 128 | case MGW_STT_SERIAL: 129 | printf("serial number: %08x\n", ntohl(msg->pt_status.data)); 130 | return; 131 | 132 | case MGW_STT_RELEASE: 133 | data->relno(data->user_data, msg->pt_status.status, buffer[4], buffer[5], buffer[6], buffer[7]); 134 | return; 135 | 136 | case MGW_CT_COUNTER_RX: 137 | printf("counter rx: %08x\n", msg->pt_status.data); 138 | return; 139 | 140 | case MGW_CT_COUNTER_TX: 141 | printf("counter tx: %08x\n", msg->pt_status.data); 142 | return; 143 | 144 | case MGW_STT_TIMEACCOUNT: 145 | printf("time account: %d%%\n", buffer[4]); 146 | return; 147 | 148 | case MGW_STT_SEND_RFSEQNO: 149 | printf("RF sequence no flag: %d\n", msg->pt_status.status); 150 | return; 151 | 152 | default: 153 | printf("received MGW_PT_STATUS(%d) [", msg->message_size); 154 | 155 | for (i = 2; i < msg->message_size; ++i) 156 | printf("%02hhx ", buffer[i]); 157 | 158 | printf("]\n"); 159 | return; 160 | 161 | case MGW_STT_OK: 162 | seq_and_pri = buffer[4]; 163 | extra = buffer[5]; 164 | break; 165 | 166 | case MGW_STT_ERROR: 167 | printf("error message: "); 168 | 169 | seq_and_pri = buffer[5]; 170 | extra = buffer[4]; 171 | 172 | switch (msg->pt_status.status) 173 | { 174 | case MGW_STS_GENERAL: 175 | printf("general error\n"); 176 | break; 177 | 178 | case MGW_STS_UNKNOWN: 179 | printf("unknown command\n"); 180 | break; 181 | 182 | case MGW_STS_DP_OOR: 183 | printf("datapoint out of range\n"); 184 | break; 185 | 186 | case MGW_STS_BUSY_MRF: 187 | printf("rf busy (tx message lost)\n"); 188 | break; 189 | 190 | case MGW_STS_BUSY_MRF_RX: 191 | printf("rf busy (rx in progress)\n"); 192 | break; 193 | 194 | case MGW_STS_TX_MSG_LOST: 195 | printf("tx message lost; repeat it\n"); 196 | break; 197 | 198 | case MGW_STS_NO_ACK: 199 | printf("timeout; no ack received\n"); 200 | seq_and_pri = buffer[4]; 201 | break; 202 | } 203 | break; 204 | } 205 | 206 | if (seq_and_pri != -1) 207 | data->ack(data->user_data, msg->pt_status.type != MGW_STT_ERROR, seq_and_pri >> 4, extra); 208 | 209 | break; 210 | } 211 | 212 | case MGW_PT_FW: 213 | printf("Firmware version: %d.%02d\n", buffer[11], buffer[12]); 214 | break; 215 | 216 | default: 217 | printf("unprocessed: received %02x: %d\n", msg->type, msg->message_size); 218 | break; 219 | } 220 | } 221 | 222 | void xc_make_jalo_msg(char* buffer, int datapoint, mci_sb_command cmd, int seq_no) 223 | { 224 | struct xc_ci_message* message = (struct xc_ci_message*) buffer; 225 | 226 | message->message_size = 0x9; 227 | message->type = MGW_PT_TX; 228 | message->packet_tx.datapoint = datapoint; 229 | message->packet_tx.tx_event = MGW_TE_JALO; 230 | message->packet_tx.value = cmd; 231 | message->packet_tx.seq_and_pri = seq_no << 4; 232 | } 233 | 234 | void xc_make_dim_msg(char* buffer, int datapoint, int value, int seq_no) 235 | { 236 | struct xc_ci_message* message = (struct xc_ci_message*) buffer; 237 | 238 | message->message_size = 0x9; 239 | message->type = MGW_PT_TX; 240 | message->packet_tx.datapoint = datapoint; 241 | message->packet_tx.tx_event = MGW_TE_DIM; 242 | message->packet_tx.value = (value << 8) + 0x40; 243 | message->packet_tx.seq_and_pri = seq_no << 4; 244 | } 245 | 246 | void xc_make_switch_msg(char* buffer, int datapoint, int on, int seq_no) 247 | { 248 | struct xc_ci_message* message = (struct xc_ci_message*) buffer; 249 | 250 | message->message_size = 0x9; 251 | message->type = MGW_PT_TX; 252 | message->packet_tx.datapoint = datapoint; 253 | message->packet_tx.tx_event = MGW_TE_SWITCH; 254 | message->packet_tx.value = on; 255 | message->packet_tx.seq_and_pri = seq_no << 4; 256 | } 257 | 258 | void xc_make_request_msg (char* buffer, int datapoint, int seq_no) 259 | { 260 | struct xc_ci_message* message = (struct xc_ci_message*) buffer; 261 | 262 | message->message_size = 0x9; 263 | message->type = MGW_PT_TX; 264 | message->packet_tx.datapoint = datapoint; 265 | message->packet_tx.tx_event = MGW_TE_REQUEST; 266 | message->packet_tx.seq_and_pri = seq_no << 4; 267 | } 268 | 269 | void xc_make_config_msg(char* buffer, int type, int mode) 270 | { 271 | struct xc_ci_message* message = (struct xc_ci_message*) buffer; 272 | 273 | message->message_size = 0x4; 274 | message->type = MGW_PT_CONFIG; 275 | message->pt_config.type = type; 276 | message->pt_config.mode = mode; 277 | } 278 | 279 | -------------------------------------------------------------------------------- /ckoz0014.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C++; c-file-style: "stroustrup" -*- */ 2 | 3 | /* 4 | * Reverse engineered from a variety of sources, for the purpose of 5 | * interoperability. 6 | * 7 | * Copyright 2016 Karl Anders Oygard. All rights reserved. 8 | * Use of this source code is governed by a BSD-style license that can be 9 | * found in the LICENSE file. 10 | */ 11 | 12 | #ifndef _CKOZ0014_H_ 13 | #define _CKOZ0014_H_ 14 | 15 | // Known commands that the CKOZ 00/14 understands 16 | 17 | enum mci_pt_action 18 | { 19 | MGW_PT_TX = 0xB1, 20 | MGW_PT_CONFIG = 0xB2, 21 | MGW_PT_RX = 0xC1, 22 | MGW_PT_STATUS = 0xC3, 23 | MGW_PT_FW = 0xD1 // Messages relating to firmware 24 | }; 25 | 26 | /* Events that can be sent to datapoints. Not all events are valid 27 | for all devices. */ 28 | 29 | enum mci_tx_event 30 | { 31 | MGW_TE_SWITCH = 0x0a, 32 | MGW_TE_REQUEST = 0x0b, // Requests MSG_STATUS event from the datapoint 33 | MGW_TE_DIM = 0x0d, 34 | MGW_TE_JALO = 0x0e, /* For dimmer, boolean value indicates direction. 35 | For shutter, see mci_sb_command */ 36 | MGW_TE_INT16_1POINT = 0x11, 37 | MGW_TE_FLOAT = 0x1a, 38 | MGW_TE_RM_TIME = 0x2a, 39 | MGW_TE_RM_DATE = 0x2b, 40 | MGW_TE_RC_DATA = 0x2c, 41 | MGW_TE_UINT32 = 0x30, 42 | MGW_TE_UINT32_1POINT = 0x31, 43 | MGW_TE_UINT32_2POINT = 0x32, 44 | MGW_TE_UINT32_3POINT = 0x33, 45 | MGW_TE_UINT16 = 0x40, 46 | MGW_TE_UINT16_1POINT = 0x41, 47 | MGW_TE_UINT16_2POINT = 0x42, 48 | MGW_TE_UINT16_3POINT = 0x43, 49 | MGW_TE_DIMPLEX_CONFIG = 0x44, 50 | MGW_TE_DIMPLEX_TEMP = 0x45, 51 | MGW_TE_HRV_INR = 0x46, 52 | MGW_TE_PUSHBUTTON = 0x50, 53 | MGW_TE_BASIC_MODE = 0x80, 54 | SET_DIRECT_ON = 0xa0 55 | }; 56 | 57 | /* Events that can be received from datapoints. 58 | 59 | These are events that are known by MRF, they may not all be 60 | applicable to datapoints. */ 61 | 62 | enum mci_rx_event 63 | { 64 | MSG_ACK = 0x01, 65 | MSG_STAY_ONLINE = 0x09, 66 | MSG_ALLIVE = 0x11, 67 | MSG_GET_OFFLINE = 0x18, 68 | MSG_GET_EEPROM = 0x30, 69 | MSG_SET_EEPROM = 0x31, 70 | MSG_GET_CRC = 0x32, 71 | MSG_TIME = 0x37, 72 | MSG_DATE = 0x38, 73 | MSG_PAKET = 0x39, 74 | MSG_KILL = 0x43, 75 | MSG_FACTORY = 0x44, 76 | MSG_ON = 0x50, 77 | MSG_OFF = 0x51, 78 | MSG_SWITCH_ON = 0x52, 79 | MSG_SWITCH_OFF = 0x53, 80 | MSG_UP_PRESSED = 0x54, 81 | MSG_UP_RELEASED = 0x55, 82 | MSG_DOWN_PRESSED = 0x56, 83 | MSG_DOWN_RELEASED = 0x57, 84 | MSG_PWM = 0x59, 85 | MSG_FORCED = 0x5a, 86 | MSG_SINGLE_ON = 0x5b, 87 | MSG_TOGGLE = 0x61, 88 | MSG_VALUE = 0x62, 89 | MSG_ZU_KALT = 0x63, 90 | MSG_ZU_WARM = 0x64, 91 | MSG_STATUS = 0x70, 92 | MSG_STATUS_APPL = 0x71, 93 | MSG_STATUS_REQ_APPL = 0x72, 94 | MSG_BASIC_MODE = 0x80 95 | }; 96 | 97 | /* Commands that can be sent to the stick itself. These are sent with 98 | the MGW_PT_CONFIG message and is received via the MGW_PT_STATUS 99 | message. */ 100 | 101 | enum mgw_cf_type 102 | { 103 | MGW_CT_CONNEX = 0x02, 104 | MGW_CT_RS232_BAUD = 0x03, 105 | MGW_CT_SEND_OK_MRF = 0x04, 106 | MGW_CT_RS232_FLOW = 0x05, 107 | MGW_CT_RS232_CRC = 0x06, 108 | MGW_CT_TIMEACCOUNT = 0x0a, 109 | MGW_CT_COUNTER_RX = 0x0b, 110 | MGW_CT_COUNTER_TX = 0x0c, 111 | MGW_CT_SERIAL = 0x0e, 112 | MGW_CT_LED = 0x0f, 113 | MGW_CT_LED_DIM = 0x1a, 114 | MGW_CT_RELEASE = 0x1b, 115 | MGW_CT_SEND_CLASS = 0x1d, 116 | MGW_CT_SEND_RFSEQNO = 0x1e, 117 | MGW_CT_BACK_TO_FACTORY = 0x1f 118 | }; 119 | 120 | enum mgw_st_type 121 | { 122 | MGW_STT_CONNEX = 0x02, 123 | MGW_STT_RS232_BAUD = 0x03, 124 | MGW_STT_RS232_FLOW = 0x05, 125 | MGW_STT_RS232_CRC = 0x06, 126 | MGW_STT_ERROR = 0x09, 127 | MGW_STT_TIMEACCOUNT = 0x0a, 128 | MGW_STT_SEND_OK_MRF = 0x0d, 129 | MGW_STT_SERIAL = 0x0e, 130 | MGW_STT_LED = 0x0f, 131 | MGW_STT_LED_DIM = 0x1a, 132 | MGW_STT_RELEASE = 0x1b, 133 | MGW_STT_OK = 0x1c, 134 | MGW_STT_SEND_CLASS = 0x1d, 135 | MGW_STT_SEND_RFSEQNO = 0x1e 136 | }; 137 | 138 | enum mstt_error 139 | { 140 | MGW_STS_GENERAL = 0x00, 141 | MGW_STS_UNKNOWN = 0x01, 142 | MGW_STS_DP_OOR = 0x02, 143 | MGW_STS_BUSY_MRF = 0x03, 144 | MGW_STS_BUSY_MRF_RX = 0x04, 145 | MGW_STS_TX_MSG_LOST = 0x05, 146 | MGW_STS_NO_ACK = 0x06 147 | 148 | }; 149 | 150 | 151 | 152 | // Battery status reported by datapoints 153 | 154 | enum mgw_rx_battery 155 | { 156 | MGW_RB_NA = 0x0, 157 | MGW_RB_0 = 0x1, 158 | MGW_RB_25 = 0x2, 159 | MGW_RB_50 = 0x3, 160 | MGW_RB_75 = 0x4, 161 | MGW_RB_100 = 0x5, 162 | MGW_RB_PWR = 0x10 163 | }; 164 | 165 | // Shutter status reported by datapoints 166 | 167 | enum mci_shutter_status 168 | { 169 | SHUTTER_STOPPED = 0x00, 170 | SHUTTER_UP = 0x01, 171 | SHUTTER_DOWN = 0x02 172 | }; 173 | 174 | // Commands for shutters 175 | 176 | enum mci_sb_command 177 | { 178 | MGW_TED_CLOSE = 0x00, 179 | MGW_TED_OPEN = 0x01, 180 | MGW_TED_JSTOP = 0x02, 181 | MGW_TED_SETP_CLOSE = 0x10, 182 | MGW_TED_SETP_OPEN = 0x11 183 | }; 184 | 185 | /* Data types that can be received from a datapoint 186 | 187 | These are events that are known by MRF, they may not all 188 | be applicable to datapoints. */ 189 | 190 | enum mci_rx_datatype 191 | { 192 | NO_DATA = 0x00, 193 | PERCENT = 0x01, 194 | UINT8 = 0x02, 195 | INT16_1POINT = 0x03, 196 | FLOAT = 0x04, 197 | UINT16 = 0x0d, 198 | UINT32 = 0x0e, 199 | UINT32_1POINT = 0x0f, 200 | UINT32_2POINT = 0x10, 201 | UINT32_3POINT = 0x11, 202 | RC_DATA = 0x17, 203 | DATA_TYPE_TIME = 0x1e, 204 | DATA_TYPE_DATE = 0x1f, 205 | UINT16_1POINT = 0x21, 206 | UINT16_2POINT = 0x22, 207 | UINT16_3POINT = 0x23, 208 | ROSETTA = 0x35, 209 | HRV_OUT = 0x37 210 | }; 211 | 212 | #pragma pack(push,1) 213 | 214 | // Message format 215 | 216 | struct xc_ci_message 217 | { 218 | unsigned char message_size; 219 | unsigned char type; 220 | union 221 | { 222 | struct 223 | { 224 | unsigned char datapoint; 225 | unsigned char tx_event; 226 | int value; 227 | unsigned char seq_and_pri; 228 | } packet_tx; 229 | struct 230 | { 231 | unsigned char type; 232 | unsigned char mode; 233 | } pt_config; 234 | struct 235 | { 236 | unsigned char datapoint; 237 | unsigned char rx_event; 238 | unsigned char rx_data_type; 239 | int value; 240 | unsigned char unknown; 241 | unsigned char rssi; // range 0 - 120 242 | unsigned char battery; // see BatteryStatus 243 | unsigned char seqno; // 0-15 monotonously increasing 244 | } packet_rx; 245 | struct 246 | { 247 | unsigned char type; 248 | unsigned char status; 249 | int data; 250 | } pt_status; 251 | }; 252 | }; 253 | 254 | #pragma pack(pop) 255 | 256 | typedef void (*xc_recv_fn)(void* user_data, 257 | enum mci_rx_event, 258 | int, 259 | enum mci_rx_datatype, 260 | int, 261 | int, 262 | enum mgw_rx_battery, 263 | int); 264 | 265 | typedef void (*xc_ack_fn)(void* user_data, 266 | int success, 267 | int seq_no, 268 | int extra); 269 | 270 | typedef void (*xc_relno_fn)(void* user_data, 271 | int status, 272 | unsigned int rf_major, 273 | unsigned int rf_minor, 274 | unsigned int usb_major, 275 | unsigned int usb_minor); 276 | 277 | struct xc_parse_data { 278 | xc_ack_fn ack; 279 | xc_recv_fn recv; 280 | xc_relno_fn relno; 281 | void* user_data; 282 | }; 283 | 284 | void xc_parse_packet(const unsigned char* buffer, size_t size, xc_parse_data* data); 285 | 286 | const char* xc_shutter_status_name(int state); 287 | 288 | const char* xc_rssi_status_name(int rssi); 289 | const char* xc_battery_status_name(enum mgw_rx_battery state); 290 | const char* xc_rxevent_name(enum mci_rx_event event); 291 | 292 | void xc_make_jalo_msg(char* buffer, int datapoint, mci_sb_command cmd, int message_id); 293 | void xc_make_dim_msg(char* buffer, int datapoint, int value, int message_id); 294 | void xc_make_switch_msg(char* buffer, int datapoint, int on, int message_id); 295 | void xc_make_request_msg(char* buffer, int datapoint, int message_id); 296 | void xc_make_config_msg(char* buffer, int type, int mode); 297 | 298 | #endif 299 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C++; c-file-style: "stroustrup" -*- */ 2 | 3 | /* 4 | * Copyright 2016 Karl Anders Oygard. All rights reserved. 5 | * Use of this source code is governed by a BSD-style license that can be 6 | * found in the LICENSE file. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "main.h" 23 | 24 | int do_exit = 0; 25 | 26 | enum mqtt_topics 27 | { 28 | MQTT_TOPIC_SWITCH, 29 | MQTT_TOPIC_DIMMER, 30 | MQTT_TOPIC_SHUTTER, 31 | MQTT_TOPIC_REQUEST_STATUS, 32 | MQTT_DEBUG 33 | }; 34 | 35 | std::map mqtt_topic_type = { 36 | { "switch", MQTT_TOPIC_SWITCH }, 37 | { "dimmer", MQTT_TOPIC_DIMMER }, 38 | { "shutter", MQTT_TOPIC_SHUTTER }, 39 | { "requeststatus", MQTT_TOPIC_REQUEST_STATUS }, 40 | { "debug", MQTT_DEBUG } 41 | }; 42 | 43 | std::map shutter_cmd_type = { 44 | { "down", MGW_TED_CLOSE }, 45 | { "up", MGW_TED_OPEN }, 46 | { "stop", MGW_TED_JSTOP } 47 | }; 48 | 49 | static void 50 | sighandler(int signum) 51 | { 52 | do_exit = 1; 53 | } 54 | 55 | XCtoMQTT::XCtoMQTT(bool verbose, bool use_syslog) 56 | : MQTTGateway(verbose), 57 | change_buffer(NULL), 58 | next_message_id(0), 59 | messages_in_transit(0), 60 | use_syslog(use_syslog) 61 | { 62 | } 63 | 64 | void 65 | XCtoMQTT::Relno(int status, 66 | unsigned int rf_major, 67 | unsigned int rf_minor, 68 | unsigned int usb_major, 69 | unsigned int usb_minor) 70 | { 71 | if (verbose) 72 | { 73 | if (status == 0x10) 74 | Info("CKOZ-00/14 revision numbers: HW-Rev %d, RF-Rev %d, FW-Rev %d\n", 75 | rf_major, 76 | rf_minor, 77 | (usb_major << 8) + usb_minor); 78 | else 79 | Info("CKOZ-00/14 version numbers: RFV%d.%02d, USBV%d.%02d\n", 80 | rf_major, 81 | rf_minor, 82 | usb_major, 83 | usb_minor); 84 | } 85 | } 86 | 87 | void 88 | XCtoMQTT::PublishStatus(int datapoint, 89 | int value) 90 | { 91 | // Received message that datapoint value changed 92 | 93 | char topic[128]; 94 | char state[128]; 95 | 96 | snprintf(topic, 128, "xcomfort/%d/get/dimmer", datapoint); 97 | snprintf(state, 128, "%d", value); 98 | 99 | if (mosquitto_publish(mosq, NULL, topic, strlen(state), (const uint8_t*) state, 1, true)) 100 | Error("failed to publish message\n"); 101 | 102 | snprintf(topic, 128, "xcomfort/%d/get/switch", datapoint); 103 | 104 | if (mosquitto_publish(mosq, NULL, topic, value ? 4 : 5, value ? "true" : "false", 1, true)) 105 | Error("failed to publish message\n"); 106 | 107 | snprintf(topic, 128, "xcomfort/%d/get/shutter", datapoint); 108 | 109 | if (mosquitto_publish(mosq, NULL, topic, strlen(xc_shutter_status_name(value)), xc_shutter_status_name(value), 1, true)) 110 | Error("failed to publish message\n"); 111 | } 112 | 113 | void 114 | XCtoMQTT::MessageReceived(mci_rx_event event, 115 | int datapoint, 116 | mci_rx_datatype data_type, 117 | int value, 118 | int rssi, 119 | mgw_rx_battery battery, 120 | int seq_no) 121 | { 122 | if (verbose) 123 | Info("received MGW_PT_RX(%s): datapoint: %d value_type: %d value: %d (signal: %s) (battery: %s) (seq no: %d)\n", 124 | xc_rxevent_name(event), 125 | datapoint, 126 | data_type, 127 | value, 128 | xc_rssi_status_name(rssi), 129 | xc_battery_status_name(battery), 130 | seq_no); 131 | 132 | switch (event) 133 | { 134 | case MSG_STATUS: 135 | { 136 | PublishStatus(datapoint, value); 137 | 138 | for (datapoint_change* dp = change_buffer; dp; dp = dp->next) 139 | if (dp->datapoint == datapoint) 140 | { 141 | if (dp->event == MGW_TE_REQUEST) 142 | // We're done 143 | 144 | dp->retries = 5; 145 | 146 | break; 147 | } 148 | } 149 | break; 150 | 151 | default: 152 | break; 153 | } 154 | } 155 | 156 | void 157 | XCtoMQTT::AckReceived(int success, int seq_no, int extra) 158 | { 159 | if (--messages_in_transit < 0) 160 | /* Messages can be acked after we have given up waiting for 161 | them. */ 162 | 163 | messages_in_transit = 0; 164 | 165 | for (datapoint_change* dp = change_buffer; dp; dp = dp->next) 166 | if (dp->active_message_id == seq_no) 167 | { 168 | // We got an ack for this message; clear to send next 169 | // messages, if any 170 | 171 | dp->active_message_id = -1; 172 | 173 | if (verbose) 174 | { 175 | if (success) 176 | Info("Seq no %d acked after %d ms (extra %d)\n", seq_no, int(getmseconds() - (dp->timeout - 5500)), extra); 177 | else 178 | Info("Seq no %d failed after %d ms, retrying\n", seq_no, int(getmseconds() - (dp->timeout - 5500))); 179 | } 180 | 181 | if (success && dp->event != MGW_TE_REQUEST) 182 | PublishStatus(dp->datapoint, dp->sent_value); 183 | 184 | if (dp->new_value != -1) 185 | // Value was updated; send asap 186 | 187 | dp->timeout = 0; 188 | else 189 | if (!success) 190 | { 191 | // Resend on failure 192 | 193 | dp->new_value = dp->sent_value; 194 | dp->timeout = 0; 195 | } 196 | 197 | return; 198 | } 199 | 200 | if (verbose) 201 | Info("received spurious ack %d; message timeout is possibly too low\n", seq_no); 202 | } 203 | 204 | void 205 | XCtoMQTT::SendDPValue(int datapoint, int value, mci_tx_event event) 206 | { 207 | datapoint_change* dp = change_buffer; 208 | 209 | for (; dp; dp = dp->next) 210 | if (dp->datapoint == datapoint) 211 | break; 212 | 213 | if (dp) 214 | { 215 | // This datapoint has pending or active messages, update 216 | // values in place and let the system handle it when it's 217 | // ready 218 | 219 | if (event != MGW_TE_REQUEST) 220 | { 221 | // No need to do this for MGW_TE_REQUEST, status will be 222 | // reported implicitly or requested explicity if missing 223 | // anyways 224 | 225 | dp->new_value = value; 226 | dp->event = event; 227 | 228 | if (dp->active_message_id == -1) 229 | dp->timeout = 0; 230 | } 231 | } 232 | else 233 | { 234 | dp = new datapoint_change; 235 | 236 | if (!dp) 237 | return; 238 | 239 | dp->next = change_buffer; 240 | dp->datapoint = datapoint; 241 | dp->new_value = value; 242 | dp->sent_value = -1; 243 | dp->event = event; 244 | dp->timeout = 0; 245 | 246 | dp->active_message_id = -1; 247 | 248 | change_buffer = dp; 249 | } 250 | 251 | dp->retries = 0; 252 | } 253 | 254 | void 255 | XCtoMQTT::TrySendMore() 256 | { 257 | if (messages_in_transit >= 1) 258 | /* Number of messages we can run in parallel. 259 | 260 | The stick appears to run into issues when handling multiple 261 | requests in parallel; it starts silently dropping messages 262 | or throwing unknown errors. If you are adventurous, you 263 | can try bumping this for higher throughput when changing 264 | multiple datapoints; I saw issues with 4+ parallel 265 | requests. */ 266 | 267 | return; 268 | 269 | if (CanSend()) 270 | { 271 | int64_t current_time = getmseconds(); 272 | datapoint_change* dp = change_buffer; 273 | datapoint_change* prev = NULL; 274 | 275 | while (dp) 276 | { 277 | if (dp->timeout <= current_time) 278 | { 279 | // Time to inspect this datapoint 280 | 281 | if ((dp->active_message_id != -1 || 282 | dp->new_value != -1 || 283 | dp->event == MGW_TE_REQUEST) && dp->retries < 5) 284 | { 285 | // Unacked or unsent; needs attention 286 | 287 | char buffer[9]; 288 | bool timed_out = dp->active_message_id != -1; 289 | int value; 290 | 291 | if (dp->new_value != -1) 292 | value = dp->new_value; 293 | else 294 | value = dp->sent_value; 295 | 296 | if (verbose) 297 | { 298 | if (timed_out) 299 | { 300 | if (dp->event == MGW_TE_REQUEST) 301 | Info("message %d was lost; retrying status request from DP %d (new id %d, retry %d)\n", 302 | dp->active_message_id, dp->datapoint, next_message_id, dp->retries); 303 | else 304 | Info("message %d was lost; retrying setting DP %d to %d (new id %d, retry %d)\n", 305 | dp->active_message_id, dp->datapoint, value, next_message_id, dp->retries); 306 | } 307 | else 308 | { 309 | if (dp->event == MGW_TE_REQUEST) 310 | Info("requesting status from DP %d (seq no %d, retry %d)\n", 311 | dp->datapoint, next_message_id, dp->retries); 312 | else 313 | Info("setting DP %d to %d (seq no %d, retry %d)\n", 314 | dp->datapoint, value, next_message_id, dp->retries); 315 | } 316 | } 317 | 318 | dp->retries++; 319 | dp->active_message_id = next_message_id; 320 | dp->new_value = -1; 321 | dp->sent_value = value; 322 | 323 | // This is how long we'll wait until we consider the message to be lost 324 | dp->timeout = current_time + 5500; 325 | 326 | switch (dp->event) 327 | { 328 | case MGW_TE_SWITCH: 329 | xc_make_switch_msg(buffer, dp->datapoint, value != 0, next_message_id); 330 | break; 331 | 332 | case MGW_TE_DIM: 333 | xc_make_dim_msg(buffer, dp->datapoint, value, next_message_id); 334 | break; 335 | 336 | case MGW_TE_JALO: 337 | xc_make_jalo_msg(buffer, dp->datapoint, (mci_sb_command) value, next_message_id); 338 | break; 339 | 340 | case MGW_TE_REQUEST: 341 | xc_make_request_msg(buffer, dp->datapoint, next_message_id); 342 | break; 343 | 344 | default: 345 | Error("Unsupported event\n"); 346 | return; 347 | } 348 | 349 | if (++next_message_id == 16) 350 | next_message_id = 0; 351 | 352 | Send(buffer, 9); 353 | 354 | if (!timed_out) 355 | messages_in_transit++; 356 | 357 | return; 358 | } 359 | else 360 | { 361 | // Expired and not updated; delete entry 362 | 363 | datapoint_change* tmp = dp->next; 364 | 365 | delete dp; 366 | 367 | if (prev) 368 | dp = prev->next = tmp; 369 | else 370 | dp = change_buffer = tmp; 371 | 372 | continue; 373 | } 374 | } 375 | 376 | prev = dp; 377 | dp = dp->next; 378 | } 379 | } 380 | } 381 | 382 | void 383 | XCtoMQTT::MQTTMessage(const struct mosquitto_message* message) 384 | { 385 | int value = 0; 386 | char** topics; 387 | int topic_count; 388 | 389 | mosquitto_sub_topic_tokenise(message->topic, &topics, &topic_count); 390 | 391 | int datapoint = strtol(topics[1], NULL, 10); 392 | 393 | if (errno == EINVAL || errno == ERANGE) 394 | return; 395 | 396 | switch (mqtt_topic_type[topics[3]]) 397 | { 398 | case MQTT_TOPIC_SWITCH: 399 | if (strcmp((char*) message->payload, "true") == 0) 400 | value = true; 401 | else 402 | value = false; 403 | 404 | SendDPValue(datapoint, value, MGW_TE_SWITCH); 405 | break; 406 | 407 | case MQTT_TOPIC_DIMMER: 408 | value = strtol((char*) message->payload, NULL, 10); 409 | 410 | if (errno == EINVAL || errno == ERANGE) 411 | return; 412 | 413 | SendDPValue(datapoint, value, MGW_TE_DIM); 414 | break; 415 | 416 | case MQTT_TOPIC_SHUTTER: 417 | SendDPValue(datapoint, shutter_cmd_type[(char*) message->payload], MGW_TE_JALO); 418 | break; 419 | 420 | case MQTT_TOPIC_REQUEST_STATUS: 421 | SendDPValue(datapoint, -1, MGW_TE_REQUEST); 422 | break; 423 | 424 | case MQTT_DEBUG: 425 | if (datapoint == 0) 426 | { 427 | if (strcmp((char*) message->payload, "true") == 0) 428 | verbose = true; 429 | else 430 | verbose = false; 431 | } 432 | break; 433 | 434 | default: 435 | Error("Unknown topic\n"); 436 | break; 437 | } 438 | 439 | mosquitto_sub_topic_tokens_free(&topics, topic_count); 440 | } 441 | 442 | int 443 | XCtoMQTT::Prepoll(int epoll_fd) 444 | { 445 | int next_change = INT_MAX; 446 | int timeout = MQTTGateway::Prepoll(epoll_fd); 447 | int64_t current_time = getmseconds(); 448 | 449 | if (change_buffer) 450 | { 451 | TrySendMore(); 452 | 453 | // Find lowest timeout 454 | 455 | for (datapoint_change* dp = change_buffer; dp; dp = dp->next) 456 | if (dp->new_value != -1 && next_change > dp->timeout - current_time) 457 | next_change = dp->timeout - current_time; 458 | } 459 | 460 | if (timeout < next_change) 461 | return timeout; 462 | 463 | return next_change; 464 | } 465 | 466 | void 467 | XCtoMQTT::Info(const char* fmt, ...) 468 | { 469 | va_list argptr; 470 | va_start(argptr, fmt); 471 | 472 | if (use_syslog) 473 | vsyslog(LOG_INFO, fmt, argptr); 474 | else 475 | vprintf(fmt, argptr); 476 | 477 | va_end(argptr); 478 | } 479 | 480 | void 481 | XCtoMQTT::Error(const char* fmt, ...) 482 | { 483 | va_list argptr; 484 | va_start(argptr, fmt); 485 | 486 | if (use_syslog) 487 | vsyslog(LOG_ERR, fmt, argptr); 488 | else 489 | vfprintf(stderr, fmt, argptr); 490 | 491 | va_end(argptr); 492 | } 493 | 494 | int 495 | main(int argc, char* argv[]) 496 | { 497 | bool daemon = false; 498 | bool verbose = false; 499 | int epoll_fd = -1; 500 | char hostname[32] = "localhost"; 501 | struct sigaction sigact; 502 | char* password = NULL; 503 | char* username = NULL; 504 | int port = 1883; 505 | 506 | int argindex = 0; 507 | 508 | static struct option long_options[] = 509 | { 510 | {"verbose", no_argument, 0, 'v'}, 511 | {"daemon", no_argument, 0, 'd'}, 512 | {"help", no_argument, 0, 0}, 513 | {"port", required_argument, 0, 'p'}, 514 | {"host", required_argument, 0, 'h'}, 515 | {"username", required_argument, 0, 'u'}, 516 | {"password", required_argument, 0, 'P'}, 517 | {0, 0, 0, 0} 518 | }; 519 | 520 | for (;;) 521 | { 522 | int c = getopt_long(argc, argv, "vdh:p:u:P:", 523 | long_options, &argindex); 524 | 525 | if (c == -1) 526 | break; 527 | 528 | switch (c) 529 | { 530 | case 'v': 531 | verbose = true; 532 | break; 533 | 534 | case 'd': 535 | daemon = true; 536 | break; 537 | 538 | case 'p': 539 | port = atoi(optarg); 540 | break; 541 | 542 | case 'h': 543 | snprintf(hostname, sizeof(hostname), "%s", optarg); 544 | break; 545 | 546 | case 'u': 547 | username = strdup(optarg); 548 | break; 549 | 550 | case 'P': 551 | password = strdup(optarg); 552 | break; 553 | 554 | default: 555 | printf("Usage: %s [OPTION]\n", argv[0]); 556 | printf("xComfort to MQTT gateway.\n\n"); 557 | printf("Options:\n"); 558 | printf(" -v, --verbose\n"); 559 | printf(" -d, --daemon\n"); 560 | printf(" -h, --host (default: localhost)\n"); 561 | printf(" -p, --port (default: 1883)\n"); 562 | printf(" -u, --username\n"); 563 | printf(" -P, --password\n"); 564 | printf("\n"); 565 | exit(EXIT_SUCCESS); 566 | } 567 | } 568 | 569 | // Daemonize for startup script 570 | 571 | if (daemon) 572 | { 573 | pid_t pid, sid; 574 | 575 | pid = fork(); 576 | if (pid < 0) 577 | exit(EXIT_FAILURE); 578 | if (pid > 0) 579 | exit(EXIT_SUCCESS); 580 | 581 | umask(0); 582 | 583 | openlog("xcomfortd", LOG_PID, LOG_DAEMON); 584 | 585 | sid = setsid(); 586 | if (sid < 0) 587 | exit(EXIT_FAILURE); 588 | 589 | if ((chdir("/tmp/")) < 0) 590 | exit(EXIT_FAILURE); 591 | 592 | close(STDIN_FILENO); 593 | close(STDOUT_FILENO); 594 | close(STDERR_FILENO); 595 | } 596 | 597 | XCtoMQTT gateway(verbose, daemon); 598 | 599 | epoll_fd = epoll_create(10); 600 | 601 | if (!gateway.Init(epoll_fd, hostname, port, username, password)) 602 | goto out; 603 | 604 | sigact.sa_handler = sighandler; 605 | sigemptyset(&sigact.sa_mask); 606 | sigact.sa_flags = 0; 607 | 608 | sigaction(SIGTERM, &sigact, NULL); 609 | sigaction(SIGQUIT, &sigact, NULL); 610 | 611 | while (!do_exit) 612 | { 613 | int events; 614 | epoll_event event; 615 | int timeout = gateway.Prepoll(epoll_fd); 616 | 617 | events = epoll_wait(epoll_fd, &event, 1, timeout); 618 | 619 | if (events < 0) 620 | break; 621 | 622 | if (events) 623 | gateway.Poll(event); 624 | } 625 | 626 | out: 627 | gateway.Stop(); 628 | 629 | if (password) 630 | free(password); 631 | 632 | if (username) 633 | free(username); 634 | 635 | if (do_exit == 1) 636 | return 0; 637 | 638 | return 1; 639 | } 640 | -------------------------------------------------------------------------------- /ckoz0013/lib_crc.c: -------------------------------------------------------------------------------- 1 | #include "lib_crc.h" 2 | 3 | 4 | 5 | /*******************************************************************\ 6 | * * 7 | * Library : lib_crc * 8 | * File : lib_crc.c * 9 | * Author : Lammert Bies 1999-2008 * 10 | * E-mail : info@lammertbies.nl * 11 | * Language : ANSI C * 12 | * * 13 | * * 14 | * Description * 15 | * =========== * 16 | * * 17 | * The file lib_crc.c contains the private and public func- * 18 | * tions used for the calculation of CRC-16, CRC-CCITT and * 19 | * CRC-32 cyclic redundancy values. * 20 | * * 21 | * * 22 | * Dependencies * 23 | * ============ * 24 | * * 25 | * lib_crc.h CRC definitions and prototypes * 26 | * * 27 | * * 28 | * Modification history * 29 | * ==================== * 30 | * * 31 | * Date Version Comment * 32 | * * 33 | * 2008-04-20 1.16 Added CRC-CCITT calculation for Kermit * 34 | * * 35 | * 2007-04-01 1.15 Added CRC16 calculation for Modbus * 36 | * * 37 | * 2007-03-28 1.14 Added CRC16 routine for Sick devices * 38 | * * 39 | * 2005-12-17 1.13 Added CRC-CCITT with initial 0x1D0F * 40 | * * 41 | * 2005-05-14 1.12 Added CRC-CCITT with start value 0 * 42 | * * 43 | * 2005-02-05 1.11 Fixed bug in CRC-DNP routine * 44 | * * 45 | * 2005-02-04 1.10 Added CRC-DNP routines * 46 | * * 47 | * 1999-02-21 1.01 Added FALSE and TRUE mnemonics * 48 | * * 49 | * 1999-01-22 1.00 Initial source * 50 | * * 51 | \*******************************************************************/ 52 | 53 | 54 | 55 | /*******************************************************************\ 56 | * * 57 | * #define P_xxxx * 58 | * * 59 | * The CRC's are computed using polynomials. The coefficients * 60 | * for the algorithms are defined by the following constants. * 61 | * * 62 | \*******************************************************************/ 63 | 64 | #define P_16 0xA001 65 | #define P_32 0xEDB88320L 66 | #define P_CCITT 0x1021 67 | #define P_DNP 0xA6BC 68 | #define P_KERMIT 0x8408 69 | #define P_SICK 0x8005 70 | 71 | 72 | 73 | /*******************************************************************\ 74 | * * 75 | * static int crc_tab...init * 76 | * static unsigned ... crc_tab...[] * 77 | * * 78 | * The algorithms use tables with precalculated values. This * 79 | * speeds up the calculation dramaticaly. The first time the * 80 | * CRC function is called, the table for that specific calcu- * 81 | * lation is set up. The ...init variables are used to deter- * 82 | * mine if the initialization has taken place. The calculated * 83 | * values are stored in the crc_tab... arrays. * 84 | * * 85 | * The variables are declared static. This makes them invisi- * 86 | * ble for other modules of the program. * 87 | * * 88 | \*******************************************************************/ 89 | 90 | static int crc_tab16_init = FALSE; 91 | static int crc_tab32_init = FALSE; 92 | static int crc_tabccitt_init = FALSE; 93 | static int crc_tabdnp_init = FALSE; 94 | static int crc_tabkermit_init = FALSE; 95 | 96 | static unsigned short crc_tab16[256]; 97 | static unsigned long crc_tab32[256]; 98 | static unsigned short crc_tabccitt[256]; 99 | static unsigned short crc_tabdnp[256]; 100 | static unsigned short crc_tabkermit[256]; 101 | 102 | 103 | 104 | /*******************************************************************\ 105 | * * 106 | * static void init_crc...tab(); * 107 | * * 108 | * Three local functions are used to initialize the tables * 109 | * with values for the algorithm. * 110 | * * 111 | \*******************************************************************/ 112 | 113 | static void init_crc16_tab( void ); 114 | static void init_crc32_tab( void ); 115 | static void init_crcccitt_tab( void ); 116 | static void init_crcdnp_tab( void ); 117 | static void init_crckermit_tab( void ); 118 | 119 | 120 | 121 | /*******************************************************************\ 122 | * * 123 | * unsigned short update_crc_ccitt( unsigned long crc, char c ); * 124 | * * 125 | * The function update_crc_ccitt calculates a new CRC-CCITT * 126 | * value based on the previous value of the CRC and the next * 127 | * byte of the data to be checked. * 128 | * * 129 | \*******************************************************************/ 130 | 131 | unsigned short update_crc_ccitt( unsigned short crc, char c ) { 132 | 133 | unsigned short tmp, short_c; 134 | 135 | short_c = 0x00ff & (unsigned short) c; 136 | 137 | if ( ! crc_tabccitt_init ) init_crcccitt_tab(); 138 | 139 | tmp = (crc >> 8) ^ short_c; 140 | crc = (crc << 8) ^ crc_tabccitt[tmp]; 141 | 142 | return crc; 143 | 144 | } /* update_crc_ccitt */ 145 | 146 | 147 | 148 | /*******************************************************************\ 149 | * * 150 | * unsigned short update_crc_sick( * 151 | * unsigned long crc, char c, char prev_byte ); * 152 | * * 153 | * The function update_crc_sick calculates a new CRC-SICK * 154 | * value based on the previous value of the CRC and the next * 155 | * byte of the data to be checked. * 156 | * * 157 | \*******************************************************************/ 158 | 159 | unsigned short update_crc_sick( unsigned short crc, char c, char prev_byte ) { 160 | 161 | unsigned short short_c, short_p; 162 | 163 | short_c = 0x00ff & (unsigned short) c; 164 | short_p = ( 0x00ff & (unsigned short) prev_byte ) << 8; 165 | 166 | if ( crc & 0x8000 ) crc = ( crc << 1 ) ^ P_SICK; 167 | else crc = crc << 1; 168 | 169 | crc &= 0xffff; 170 | crc ^= ( short_c | short_p ); 171 | 172 | return crc; 173 | 174 | } /* update_crc_sick */ 175 | 176 | 177 | 178 | /*******************************************************************\ 179 | * * 180 | * unsigned short update_crc_16( unsigned short crc, char c ); * 181 | * * 182 | * The function update_crc_16 calculates a new CRC-16 value * 183 | * based on the previous value of the CRC and the next byte * 184 | * of the data to be checked. * 185 | * * 186 | \*******************************************************************/ 187 | 188 | unsigned short update_crc_16( unsigned short crc, char c ) { 189 | 190 | unsigned short tmp, short_c; 191 | 192 | short_c = 0x00ff & (unsigned short) c; 193 | 194 | if ( ! crc_tab16_init ) init_crc16_tab(); 195 | 196 | tmp = crc ^ short_c; 197 | crc = (crc >> 8) ^ crc_tab16[ tmp & 0xff ]; 198 | 199 | return crc; 200 | 201 | } /* update_crc_16 */ 202 | 203 | 204 | 205 | /*******************************************************************\ 206 | * * 207 | * unsigned short update_crc_kermit( unsigned short crc, char c ); * 208 | * * 209 | * The function update_crc_kermit calculates a new CRC value * 210 | * based on the previous value of the CRC and the next byte * 211 | * of the data to be checked. * 212 | * * 213 | \*******************************************************************/ 214 | 215 | unsigned short update_crc_kermit( unsigned short crc, char c ) { 216 | 217 | unsigned short tmp, short_c; 218 | 219 | short_c = 0x00ff & (unsigned short) c; 220 | 221 | if ( ! crc_tabkermit_init ) init_crckermit_tab(); 222 | 223 | tmp = crc ^ short_c; 224 | crc = (crc >> 8) ^ crc_tabkermit[ tmp & 0xff ]; 225 | 226 | return crc; 227 | 228 | } /* update_crc_kermit */ 229 | 230 | 231 | 232 | /*******************************************************************\ 233 | * * 234 | * unsigned short update_crc_dnp( unsigned short crc, char c ); * 235 | * * 236 | * The function update_crc_dnp calculates a new CRC-DNP value * 237 | * based on the previous value of the CRC and the next byte * 238 | * of the data to be checked. * 239 | * * 240 | \*******************************************************************/ 241 | 242 | unsigned short update_crc_dnp( unsigned short crc, char c ) { 243 | 244 | unsigned short tmp, short_c; 245 | 246 | short_c = 0x00ff & (unsigned short) c; 247 | 248 | if ( ! crc_tabdnp_init ) init_crcdnp_tab(); 249 | 250 | tmp = crc ^ short_c; 251 | crc = (crc >> 8) ^ crc_tabdnp[ tmp & 0xff ]; 252 | 253 | return crc; 254 | 255 | } /* update_crc_dnp */ 256 | 257 | 258 | 259 | /*******************************************************************\ 260 | * * 261 | * unsigned long update_crc_32( unsigned long crc, char c ); * 262 | * * 263 | * The function update_crc_32 calculates a new CRC-32 value * 264 | * based on the previous value of the CRC and the next byte * 265 | * of the data to be checked. * 266 | * * 267 | \*******************************************************************/ 268 | 269 | unsigned long update_crc_32( unsigned long crc, char c ) { 270 | 271 | unsigned long tmp, long_c; 272 | 273 | long_c = 0x000000ffL & (unsigned long) c; 274 | 275 | if ( ! crc_tab32_init ) init_crc32_tab(); 276 | 277 | tmp = crc ^ long_c; 278 | crc = (crc >> 8) ^ crc_tab32[ tmp & 0xff ]; 279 | 280 | return crc; 281 | 282 | } /* update_crc_32 */ 283 | 284 | 285 | 286 | /*******************************************************************\ 287 | * * 288 | * static void init_crc16_tab( void ); * 289 | * * 290 | * The function init_crc16_tab() is used to fill the array * 291 | * for calculation of the CRC-16 with values. * 292 | * * 293 | \*******************************************************************/ 294 | 295 | static void init_crc16_tab( void ) { 296 | 297 | int i, j; 298 | unsigned short crc, c; 299 | 300 | for (i=0; i<256; i++) { 301 | 302 | crc = 0; 303 | c = (unsigned short) i; 304 | 305 | for (j=0; j<8; j++) { 306 | 307 | if ( (crc ^ c) & 0x0001 ) crc = ( crc >> 1 ) ^ P_16; 308 | else crc = crc >> 1; 309 | 310 | c = c >> 1; 311 | } 312 | 313 | crc_tab16[i] = crc; 314 | } 315 | 316 | crc_tab16_init = TRUE; 317 | 318 | } /* init_crc16_tab */ 319 | 320 | 321 | 322 | /*******************************************************************\ 323 | * * 324 | * static void init_crckermit_tab( void ); * 325 | * * 326 | * The function init_crckermit_tab() is used to fill the array * 327 | * for calculation of the CRC Kermit with values. * 328 | * * 329 | \*******************************************************************/ 330 | 331 | static void init_crckermit_tab( void ) { 332 | 333 | int i, j; 334 | unsigned short crc, c; 335 | 336 | for (i=0; i<256; i++) { 337 | 338 | crc = 0; 339 | c = (unsigned short) i; 340 | 341 | for (j=0; j<8; j++) { 342 | 343 | if ( (crc ^ c) & 0x0001 ) crc = ( crc >> 1 ) ^ P_KERMIT; 344 | else crc = crc >> 1; 345 | 346 | c = c >> 1; 347 | } 348 | 349 | crc_tabkermit[i] = crc; 350 | } 351 | 352 | crc_tabkermit_init = TRUE; 353 | 354 | } /* init_crckermit_tab */ 355 | 356 | 357 | 358 | /*******************************************************************\ 359 | * * 360 | * static void init_crcdnp_tab( void ); * 361 | * * 362 | * The function init_crcdnp_tab() is used to fill the array * 363 | * for calculation of the CRC-DNP with values. * 364 | * * 365 | \*******************************************************************/ 366 | 367 | static void init_crcdnp_tab( void ) { 368 | 369 | int i, j; 370 | unsigned short crc, c; 371 | 372 | for (i=0; i<256; i++) { 373 | 374 | crc = 0; 375 | c = (unsigned short) i; 376 | 377 | for (j=0; j<8; j++) { 378 | 379 | if ( (crc ^ c) & 0x0001 ) crc = ( crc >> 1 ) ^ P_DNP; 380 | else crc = crc >> 1; 381 | 382 | c = c >> 1; 383 | } 384 | 385 | crc_tabdnp[i] = crc; 386 | } 387 | 388 | crc_tabdnp_init = TRUE; 389 | 390 | } /* init_crcdnp_tab */ 391 | 392 | 393 | 394 | /*******************************************************************\ 395 | * * 396 | * static void init_crc32_tab( void ); * 397 | * * 398 | * The function init_crc32_tab() is used to fill the array * 399 | * for calculation of the CRC-32 with values. * 400 | * * 401 | \*******************************************************************/ 402 | 403 | static void init_crc32_tab( void ) { 404 | 405 | int i, j; 406 | unsigned long crc; 407 | 408 | for (i=0; i<256; i++) { 409 | 410 | crc = (unsigned long) i; 411 | 412 | for (j=0; j<8; j++) { 413 | 414 | if ( crc & 0x00000001L ) crc = ( crc >> 1 ) ^ P_32; 415 | else crc = crc >> 1; 416 | } 417 | 418 | crc_tab32[i] = crc; 419 | } 420 | 421 | crc_tab32_init = TRUE; 422 | 423 | } /* init_crc32_tab */ 424 | 425 | 426 | 427 | /*******************************************************************\ 428 | * * 429 | * static void init_crcccitt_tab( void ); * 430 | * * 431 | * The function init_crcccitt_tab() is used to fill the array * 432 | * for calculation of the CRC-CCITT with values. * 433 | * * 434 | \*******************************************************************/ 435 | 436 | static void init_crcccitt_tab( void ) { 437 | 438 | int i, j; 439 | unsigned short crc, c; 440 | 441 | for (i=0; i<256; i++) { 442 | 443 | crc = 0; 444 | c = ((unsigned short) i) << 8; 445 | 446 | for (j=0; j<8; j++) { 447 | 448 | if ( (crc ^ c) & 0x8000 ) crc = ( crc << 1 ) ^ P_CCITT; 449 | else crc = crc << 1; 450 | 451 | c = c << 1; 452 | } 453 | 454 | crc_tabccitt[i] = crc; 455 | } 456 | 457 | crc_tabccitt_init = TRUE; 458 | 459 | } /* init_crcccitt_tab */ 460 | -------------------------------------------------------------------------------- /ckoz0013/ckoz0013.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C++; c-file-style: "k&r" -*- */ 2 | 3 | /* 4 | * Reverse engineered from a variety of sources, for the purpose of 5 | * interoperability. 6 | * 7 | * Copyright 2016 Karl Anders Oygard. All rights reserved. 8 | * Use of this source code is governed by a BSD-style license that can be 9 | * found in the LICENSE file. 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | #define mqtt_host "192.168.1.1" 25 | #define mqtt_port 1883 26 | 27 | #define EP_IN (4 | LIBUSB_ENDPOINT_IN) 28 | #define EP_OUT (5 | LIBUSB_ENDPOINT_OUT) 29 | #define CTRL_IN (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN) 30 | #define CTRL_OUT (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT) 31 | #define USB_RQ 0x04 32 | #define INTR_LENGTH 62 33 | 34 | #include "lib_crc.h" 35 | 36 | struct mosquitto* mosq; 37 | 38 | unsigned short 39 | calculate_crc(char* buf, size_t len) 40 | { 41 | unsigned short crc = 0; 42 | size_t i = 0; 43 | 44 | for (i = 0; i < len; i++) 45 | crc = update_crc_kermit(crc, buf[i]); 46 | 47 | return crc; 48 | } 49 | 50 | enum message_action 51 | { 52 | A = 0x02, // 2 bytes 53 | REPORT = 0x03, 54 | B = 0x04, // Init or something? 0x04 0x0b 55 | INIT = 0x0b, // No parameters 56 | C = 0x11, 57 | DISCOVER = 0x17, 58 | STARTSTOP = 0x1a, // First thing to send, just one byte payload 1 = start, 0 = stop 59 | SENDMSG = 0x1b, 60 | E = 0x22, 61 | F = 0x30, 62 | G = 0x31, 63 | H = 0x33, 64 | I = 0x34, 65 | J = 0x36, // No parameters 66 | Q = 0x39, 67 | P = 0x40, // two bytes (2, 0 or 2, 1) 68 | SERIALNO = 0x42, // No parameters 69 | SOMESTRING = 0x44, // No parameters 70 | M = 0x47, // 9 bytes long, one long + extra byte 71 | N = 0x48, // 9 bytes long, one long + extra byte 72 | O = 0x49, 73 | MCI_PT_TX = 0xB1, 74 | MCI_PT_RX = 0xC1 75 | }; 76 | 77 | enum message_action_type 78 | { 79 | MSG_ACK = 0x01, 80 | MSG_STAY_ONLINE = 0x09, 81 | MSG_ALLIVE = 0x11, 82 | MSG_GET_OFFLINE = 0x18, 83 | MSG_GET_EEPROM = 0x30, 84 | MSG_SET_EEPROM = 0x31, 85 | MSG_GET_CRC = 0x32, 86 | MSG_TIME = 0x37, 87 | MSG_DATE = 0x38, 88 | MSG_PAKET = 0x39, 89 | MSG_KILL = 0x43, 90 | MSG_FACTORY = 0x44, 91 | MSG_ON = 0x50, 92 | MSG_OFF = 0x51, 93 | MSG_SWITCH_ON = 0x52, 94 | MSG_SWITCH_OFF = 0x53, 95 | MSG_UP_PRESSED = 0x54, 96 | MSG_UP_RELEASED = 0x55, 97 | MSG_DOWN_PRESSED = 0x56, 98 | MSG_DOWN_RELEASED = 0x57, 99 | MSG_PWM = 0x59, 100 | MSG_FORCED = 0x5a, 101 | MSG_SINGLE_ON = 0x5b, 102 | MSG_TOGGLE = 0x61, 103 | MSG_VALUE = 0x62, 104 | MSG_ZU_KALT = 0x63, 105 | MSG_ZU_WARM = 0x64, 106 | MSG_STATUS = 0x70, 107 | MSG_STATUS_APPL = 0x71, 108 | MSG_STATUS_REQ_APPL = 0x72, 109 | MSG_NONE = 0x1000 110 | }; 111 | 112 | struct Argh 113 | { 114 | const char* name; 115 | message_action_type type; 116 | }; 117 | 118 | Argh message_lookup[] = 119 | { 120 | { "MSG_ACK", MSG_ACK}, 121 | { "MSG_STAY_ONLINE", MSG_STAY_ONLINE}, 122 | { "MSG_ALLIVE", MSG_ALLIVE}, 123 | { "MSG_GET_OFFLINE", MSG_GET_OFFLINE}, 124 | { "MSG_GET_EEPROM", MSG_GET_EEPROM}, 125 | { "MSG_SET_EEPROM", MSG_SET_EEPROM}, 126 | { "MSG_GET_CRC", MSG_GET_CRC}, 127 | { "MSG_TIME", MSG_TIME}, 128 | { "MSG_DATE", MSG_DATE}, 129 | { "MSG_PAKET", MSG_PAKET}, 130 | { "MSG_KILL", MSG_KILL}, 131 | { "MSG_FACTORY", MSG_FACTORY}, 132 | { "MSG_ON", MSG_ON}, 133 | { "MSG_OFF", MSG_OFF}, 134 | { "MSG_SWITCH_ON", MSG_SWITCH_ON}, 135 | { "MSG_SWITCH_OFF", MSG_SWITCH_OFF}, 136 | { "MSG_UP_PRESSED", MSG_UP_PRESSED}, 137 | { "MSG_UP_RELEASED", MSG_UP_RELEASED}, 138 | { "MSG_DOWN_PRESSED", MSG_DOWN_PRESSED}, 139 | { "MSG_DOWN_RELEASED", MSG_DOWN_RELEASED}, 140 | { "MSG_PWM", MSG_PWM}, 141 | { "MSG_FORCED", MSG_FORCED}, 142 | { "MSG_SINGLE_ON", MSG_SINGLE_ON}, 143 | { "MSG_TOGGLE", MSG_TOGGLE}, 144 | { "MSG_VALUE", MSG_VALUE}, 145 | { "MSG_ZU_KALT", MSG_ZU_KALT}, 146 | { "MSG_ZU_WARM", MSG_ZU_WARM}, 147 | { "MSG_STATUS", MSG_STATUS}, 148 | { "MSG_STATUS_APPL", MSG_STATUS_APPL}, 149 | { "MSG_STATUS_REQ_APPL", MSG_STATUS_REQ_APPL}, 150 | { NULL, MSG_NONE} 151 | }; 152 | 153 | enum battery_state 154 | { 155 | BATTERY_EMPTY = 0x0, 156 | BATTERY_WEAK = 0x1, 157 | BATTERY_AVERAGE = 0x2, 158 | BATTERY_ALMOSTFULL = 0x3, 159 | BATTERY_FULL = 0x4, 160 | POWERLINE = 0x3f, 161 | }; 162 | 163 | enum data_type 164 | { 165 | NO_TELEGRAM_DATA = 0x00, 166 | PERCENT = 0x01, 167 | DATA_TYPE_UCHAR = 0x02, 168 | SSHORT_1COMMA = 0x03, 169 | ANSI_FLOAT = 0x04, 170 | MEMORY = 0x06, 171 | MEMORY_REQ = 0x07, 172 | ALLIVE_FILTER = 0x0c, 173 | USHORT_NO_COMMA = 0x0d, 174 | RC_DATA = 0x17, 175 | MEMORY32 = 0x1a, 176 | MEMORY32_REQ = 0x1b, 177 | DATA_TYPE_TIME = 0x1e, 178 | DATA_TYPE_DATE = 0x1f, 179 | DATA_TYPE_PACKET = 0x20, 180 | DATA_TYPE_UNSIGNED_LONG = 0x25, 181 | DATA_TYPE_ULONG_1COMMA = 0x26, 182 | DATA_TYPE_ULONG_2COMMA = 0x27, 183 | DATA_TYPE_ULONG_3COMMA = 0x28, 184 | USHORT_1COMMA = 0x29, 185 | USHORT_2COMMA = 0x2a, 186 | USHORT_3COMMA = 0x2b, 187 | DIMPLEX_DATA = 0x2d, 188 | DATA_TYPE_SLONG_NOCOMMA = 0x2e, 189 | DATA_TYPE_SLONG_1COMMA = 0x2f, 190 | DATA_TYPE_SLONG_2COMMA = 0x30, 191 | DATA_TYPE_SLONG_3COMMA = 0x31, 192 | DATA_TYPE_SSHORT_NOCOMMA = 0x32, 193 | DATA_TYPE_SSHORT_2COMMA = 0x33, 194 | DATA_TYPE_SSHORT_3COMMA = 0x34, 195 | DATE = 0x37, 196 | ARRAY = 0xf0 197 | }; 198 | 199 | const char* 200 | lookup_message_type(message_action_type type) 201 | { 202 | for (int i = 0; message_lookup[i].name; ++i) 203 | if (message_lookup[i].type == type) 204 | return message_lookup[i].name; 205 | 206 | return NULL; 207 | } 208 | 209 | const char* 210 | Blah(int type) 211 | { 212 | // May be off by one! 213 | 214 | switch (type) 215 | { 216 | case 0: 217 | return "Taster"; 218 | case 1: 219 | return "Taster 2-fach"; 220 | case 2: 221 | return "Taster 4-fach"; 222 | case 4: 223 | case 50: 224 | return "Raumcontroller"; 225 | case 15: 226 | return "Schaltaktor"; 227 | case 16: 228 | case 76: 229 | return "Dimmaktor"; 230 | case 17: 231 | case 26: 232 | return "Jalousieaktor"; 233 | case 18: 234 | return "Bin 230"; 235 | case 19: 236 | return "Bin Batt"; 237 | case 20: 238 | return "Fernbedienung"; 239 | case 21: 240 | return "Home Manager"; 241 | case 22: 242 | return "Temperatursensor"; 243 | case 23: 244 | return "Analogeingang"; 245 | case 24: 246 | return "Analogaktor"; 247 | case 25: 248 | case 66: 249 | return "Room-Manager"; 250 | case 27: 251 | return "Komm.-Schnittstelle"; 252 | case 28: 253 | return "Bewegungsmelder"; 254 | case 47: 255 | return "Fernbedienung 2K"; 256 | case 48: 257 | return "Fernbedienung 12K"; 258 | case 49: 259 | return "FB Display"; 260 | return "Fernbedienung LCD"; 261 | case 51: 262 | return "Router"; 263 | case 52: 264 | return "Impulseingang"; 265 | case 53: 266 | return "Energiesensor"; 267 | return "EMS"; 268 | case 54: 269 | return "Heizungsaktor"; 270 | case 55: 271 | return "FB Alarmtaster"; 272 | case 56: 273 | return "BOSCOS Interface"; 274 | case 57: 275 | return "Testgerät"; 276 | case 58: 277 | return "Aktor-Sensor-Kombination"; 278 | case 59: 279 | return "Generic device"; 280 | case 60: 281 | return "RF_CC1110"; 282 | case 61: 283 | return "MEP-Device"; 284 | case 62: 285 | return "Serial Interface"; 286 | return "Smart Home Controller"; 287 | case 64: 288 | return "Heizkörperthermostat"; 289 | case 65: 290 | return "Multi-Heizaktor 6-fach"; 291 | case 67: 292 | return "Rosetta-Sensor"; 293 | case 68: 294 | return "Rosetta-Router"; 295 | case 69: 296 | return "Ethernet Communication Interface"; 297 | case 70: 298 | return "MCH_12x-Testgerät"; 299 | return "Multi-Heizaktor 12-fach"; 300 | case 71: 301 | return "Kommunikationsstick"; 302 | return "USB-Gateway"; 303 | case 73: 304 | return "Schaltaktor neu"; 305 | case 75: 306 | return "Fenster/Tür Sensor"; 307 | default: 308 | return NULL; 309 | } 310 | } 311 | 312 | #pragma pack(push,1) 313 | 314 | struct message 315 | { 316 | unsigned char begin; // Always 5A 317 | unsigned char size; 318 | unsigned char action; 319 | union 320 | { 321 | char data[59]; 322 | struct { 323 | unsigned char type; 324 | short int magic; 325 | unsigned char sequence; // bitfield aaaa:ssss (a = 4 means something) 326 | char something2; // bitfield aaa:b:0:cc:d 327 | char batterystate; // bitfield 00:aaa:bbb 328 | char channel; 329 | int source; 330 | short int somethingelse; 331 | int destination; 332 | char signalstrength[2]; 333 | short int time2; 334 | short int strength; 335 | unsigned char end; // Always A5 336 | } report; 337 | struct { 338 | unsigned char type; 339 | unsigned char magic; // 00 340 | unsigned char length; // 15 341 | unsigned char sequence; // 10-1f 342 | unsigned char bitfield; // aaaaaaa:b, b = wants ack 343 | unsigned char magic3; // 07 344 | short int magic1; // 00 80 345 | int source; 346 | int destination; 347 | short int magic2; // 01 00 or 0c 00 348 | unsigned char level; 349 | short int crc; 350 | unsigned char end; // Always A5 351 | } sendmsg; 352 | struct { 353 | int serialno; 354 | } serialno; 355 | struct { 356 | unsigned char stop; 357 | unsigned char end; // Always A5 358 | } startstop; 359 | struct { 360 | int blah; 361 | unsigned char index; 362 | unsigned char blah3; // A5 if short packet 363 | unsigned char blah4; 364 | unsigned char blah5; // Always A5 365 | } m; 366 | struct { 367 | unsigned char datapoint; 368 | unsigned char opcode; 369 | unsigned char value; 370 | int unused; 371 | } datapoint_tx; 372 | struct { 373 | unsigned char datapoint; 374 | unsigned char infoshort; 375 | unsigned char datatype; 376 | int data; 377 | unsigned char unknown; 378 | unsigned char signal; 379 | unsigned char battery; 380 | } datapoint_rx; 381 | struct { 382 | short int something; 383 | unsigned char end; 384 | } i; 385 | struct { 386 | unsigned char end; // Always A5 387 | } somestring; 388 | }; 389 | }; 390 | 391 | #pragma pack(pop) 392 | 393 | static int next_state(void); 394 | 395 | enum 396 | { 397 | STATE_AWAIT_INIT = 1, 398 | STATE_READY, 399 | STATE_22, 400 | STATE_H, 401 | STATE_SERIALNO, 402 | STATE_47, 403 | STATE_44, 404 | STATE_SECOND_47, 405 | STATE_STARTSTOP, 406 | STATE_STARTSTOP2 407 | }; 408 | 409 | struct xcomfort_device 410 | { 411 | xcomfort_device* next; 412 | int serial_number; 413 | 414 | bool waiting_for_ack; 415 | 416 | int last_known_value; 417 | int desired_value; 418 | }; 419 | 420 | xcomfort_device* device_list = NULL; 421 | bool message_in_transit = false; 422 | bool waiting_for_ack = false; 423 | 424 | static int state = 0; 425 | static struct libusb_device_handle* devh = NULL; 426 | 427 | static unsigned char recvbuf[INTR_LENGTH]; 428 | static struct libusb_transfer* recv_transfer = NULL; 429 | 430 | static unsigned char sendbuf[INTR_LENGTH]; 431 | static struct libusb_transfer* send_transfer = NULL; 432 | 433 | static int do_exit = 0; 434 | 435 | void 436 | construct_sendmsg(struct message* question, int target, message_action_type type, unsigned char level) 437 | { 438 | static int sequence = 0x10; 439 | 440 | question->begin = 0x5a; 441 | question->size = 0x19; 442 | question->action = SENDMSG; 443 | question->sendmsg.type = type; 444 | question->sendmsg.magic = 0x0; 445 | question->sendmsg.length = 0x15; 446 | question->sendmsg.sequence = sequence++; 447 | question->sendmsg.bitfield = 0x82; // wants ack 448 | question->sendmsg.magic3 = 0x07; 449 | question->sendmsg.magic1 = 0x8000; 450 | question->sendmsg.source = 0x0; 451 | question->sendmsg.destination = target; 452 | question->sendmsg.magic2 = 0x1; 453 | question->sendmsg.level = level; 454 | question->sendmsg.crc = calculate_crc((char*) &(question->sendmsg), sizeof(question->sendmsg) - 3); 455 | question->sendmsg.end = 0xa5; 456 | 457 | if (sequence == 0x20) 458 | sequence = 0x10; 459 | 460 | for (int i = 0; i < 0x16; ++i) 461 | printf("%02hhx", question->data[i]); 462 | printf("\n"); 463 | } 464 | 465 | void 466 | send_next_message() 467 | { 468 | xcomfort_device* i; 469 | 470 | for (i = device_list; i; i = i->next) 471 | if (i->desired_value != i->last_known_value && 472 | !i->waiting_for_ack) 473 | { 474 | message_action_type type = MSG_OFF; 475 | 476 | struct message* question = (struct message*) sendbuf; 477 | int r; 478 | 479 | if (i->desired_value == 255) 480 | type = MSG_ON; 481 | else 482 | if (i->desired_value > 0) 483 | type = MSG_FORCED; 484 | 485 | i->waiting_for_ack = true; 486 | construct_sendmsg(question, i->serial_number, type, i->desired_value); 487 | 488 | r = libusb_submit_transfer(send_transfer); 489 | if (r < 0) 490 | fprintf(stderr, "failed to submit transfer\n"); 491 | 492 | assert(!message_in_transit); 493 | message_in_transit = true; 494 | waiting_for_ack = true; 495 | 496 | return; 497 | } 498 | } 499 | 500 | bool 501 | ack_received(int serial_number) 502 | { 503 | xcomfort_device* i; 504 | 505 | waiting_for_ack = false; 506 | 507 | for (i = device_list; i; i = i->next) 508 | if (i->serial_number == serial_number) 509 | if (i->waiting_for_ack) 510 | { 511 | printf("received expected ack for %d to change to %d\n", serial_number, i->desired_value); 512 | 513 | i->waiting_for_ack = false; 514 | i->last_known_value = i->desired_value; 515 | 516 | send_next_message(); 517 | 518 | return true; 519 | } 520 | else 521 | break; 522 | 523 | return false; 524 | } 525 | 526 | void 527 | set_value(int serial_number, int value, bool reported = false) 528 | { 529 | // See if it's already known and inactive 530 | 531 | xcomfort_device* i; 532 | 533 | for (i = device_list; i; i = i->next) 534 | if (i->serial_number == serial_number) 535 | { 536 | if (i->desired_value == value) 537 | { 538 | // This value is already desired, nothing to do 539 | 540 | printf("device %d already setting to %d\n", serial_number, value); 541 | 542 | return; 543 | } 544 | else 545 | if (i->last_known_value == value) 546 | if (!i->waiting_for_ack) 547 | { 548 | // Value is already active, nothing to do 549 | 550 | printf("device %d already set to %d\n", serial_number, value); 551 | 552 | return; 553 | } 554 | 555 | if (reported) 556 | { 557 | i->last_known_value = value; 558 | 559 | printf("reported change %d to %d\n", serial_number, value); 560 | } 561 | else 562 | printf("have to change %d to %d\n", serial_number, value); 563 | 564 | i->desired_value = value; 565 | i->waiting_for_ack = false; 566 | 567 | break; 568 | } 569 | 570 | if (!i) 571 | { 572 | // Previously unknown device 573 | 574 | printf("creating %d with value %d\n", serial_number, value); 575 | 576 | i = (xcomfort_device*) malloc(sizeof(xcomfort_device)); 577 | 578 | if (!i) 579 | return; 580 | 581 | i->next = device_list; 582 | i->serial_number = serial_number; 583 | 584 | if (reported) 585 | i->last_known_value = value; 586 | else 587 | i->last_known_value = 0; 588 | 589 | i->desired_value = value; 590 | i->waiting_for_ack = false; 591 | 592 | device_list = i; 593 | } 594 | 595 | if (!reported && 596 | !message_in_transit && 597 | !waiting_for_ack) 598 | send_next_message(); 599 | } 600 | 601 | static int 602 | next_state(void) 603 | { 604 | int r = 0; 605 | 606 | switch (state) 607 | { 608 | case STATE_AWAIT_INIT: 609 | state = STATE_AWAIT_INIT; 610 | break; 611 | 612 | default: 613 | printf("unrecognised state %d\n", state); 614 | } 615 | 616 | if (r < 0) 617 | { 618 | fprintf(stderr, "error detected changing state\n"); 619 | return r; 620 | } 621 | 622 | return 0; 623 | } 624 | 625 | static void LIBUSB_CALL 626 | cb_recv(struct libusb_transfer* transfer) 627 | { 628 | struct message* answer = (struct message*) transfer->buffer; 629 | 630 | if (transfer->status != LIBUSB_TRANSFER_COMPLETED) 631 | { 632 | fprintf(stderr, "irq transfer status %d?\n", transfer->status); 633 | do_exit = 2; 634 | libusb_free_transfer(transfer); 635 | recv_transfer = NULL; 636 | return; 637 | } 638 | 639 | switch (answer->action) 640 | { 641 | case REPORT: 642 | { 643 | static message_action_type last_type = MSG_ACK; 644 | 645 | const char* name = lookup_message_type((message_action_type) answer->report.type); 646 | 647 | printf("received REPORT(%s, %d): %d 0x%08x %d (battery: %d) (strength: %d) (channel: %d) %04x %02x %02x\t[", 648 | name, 649 | answer->size, 650 | answer->report.sequence >> 4, 651 | answer->report.source, 652 | answer->report.batterystate >> 3, 653 | answer->report.batterystate & 0x7, 654 | *((short int*) (answer->data + answer->size - 6)), 655 | answer->report.channel, 656 | answer->report.magic, 657 | answer->report.sequence, 658 | answer->report.something2); 659 | 660 | for (int i = 0; i < answer->size - 21; ++i) 661 | printf("%02hhx ", answer->report.signalstrength[i]); 662 | 663 | printf("]\n"); 664 | 665 | if (answer->report.type == MSG_ACK) 666 | { 667 | const char* state = NULL; 668 | int value = 0; 669 | 670 | ack_received(answer->report.source); 671 | 672 | switch (last_type) 673 | { 674 | case MSG_ON: 675 | state = "true"; 676 | value = 255; 677 | break; 678 | 679 | case MSG_OFF: 680 | state = "false"; 681 | value = 0; 682 | break; 683 | 684 | default: 685 | break; 686 | } 687 | 688 | if (state) 689 | { 690 | char topic[128]; 691 | 692 | set_value(answer->report.source, value, true); 693 | 694 | sprintf(topic, "%08x", answer->report.source); 695 | mosquitto_publish(mosq, NULL, topic, strlen(state), (const uint8_t*) state, 1, true); 696 | } 697 | } 698 | 699 | last_type = (message_action_type) answer->report.type; 700 | 701 | break; 702 | } 703 | 704 | case INIT: 705 | { 706 | struct message* question = (struct message*) sendbuf; 707 | int r; 708 | 709 | // Should be 5f 2a 5f 710 | 711 | printf("received 0x0b INIT(%d): %02x, %02x, %02x, %02x, %02x, %02x\n", 712 | answer->size, answer->data[0], answer->data[1], answer->data[2], 713 | answer->data[3], answer->data[4], answer->data[5]); 714 | 715 | question->begin = 0x5a; 716 | question->size = 0x05; 717 | question->action = STARTSTOP; 718 | question->startstop.stop = 0; 719 | question->startstop.end = 0xa5; 720 | 721 | r = libusb_submit_transfer(send_transfer); 722 | if (r < 0) 723 | fprintf(stderr, "failed to submit transfer\n"); 724 | 725 | state = STATE_STARTSTOP; 726 | } 727 | break; 728 | 729 | case E: 730 | { 731 | struct message* question = (struct message*) sendbuf; 732 | int r; 733 | 734 | printf("received 0x22 E(%d)\n", answer->size); 735 | 736 | question->begin = 0x5a; 737 | question->size = 0x05; 738 | question->action = H; 739 | question->startstop.stop = 1; 740 | question->startstop.end = 0xa5; 741 | 742 | r = libusb_submit_transfer(send_transfer); 743 | if (r < 0) 744 | fprintf(stderr, "failed to submit transfer\n"); 745 | 746 | state = STATE_H; 747 | } 748 | break; 749 | 750 | case SERIALNO: 751 | { 752 | struct message* question = (struct message*) sendbuf; 753 | int r; 754 | 755 | printf("received 0x42 SERIALNO: %d\n", answer->serialno.serialno); 756 | 757 | question->begin = 0x5a; 758 | question->size = 0x04; 759 | question->action = E; 760 | question->somestring.end = 0xa5; 761 | 762 | r = libusb_submit_transfer(send_transfer); 763 | if (r < 0) 764 | fprintf(stderr, "failed to submit transfer\n"); 765 | 766 | state = STATE_22; 767 | } 768 | break; 769 | 770 | case 0x2: 771 | printf("received 0x02(%d): %08x\n", answer->size, answer->serialno.serialno); 772 | { 773 | struct message* question = (struct message*) sendbuf; 774 | int r; 775 | 776 | printf("broadcasting MSG_ALLIVE\n"); 777 | 778 | construct_sendmsg(question, 0, MSG_ALLIVE, 0x3); 779 | 780 | r = libusb_submit_transfer(send_transfer); 781 | if (r < 0) 782 | fprintf(stderr, "failed to submit transfer\n"); 783 | 784 | state = STATE_READY; 785 | } 786 | break; 787 | 788 | case SOMESTRING: 789 | { 790 | char name[128]; 791 | struct message* question = (struct message*) sendbuf; 792 | int r; 793 | 794 | strcpy(name, answer->data); 795 | printf("received 0x44 SOMESTRING(%d): %s\n", answer->size, name); 796 | 797 | question->begin = 0x5a; 798 | question->size = 0x04; 799 | question->action = SERIALNO; 800 | question->somestring.end = 0xa5; 801 | 802 | r = libusb_submit_transfer(send_transfer); 803 | if (r < 0) 804 | fprintf(stderr, "failed to submit transfer\n"); 805 | 806 | state = STATE_SERIALNO; 807 | } 808 | break; 809 | 810 | case 0x47: 811 | printf("received 0x47(%d): %08x %02x %02x %02x %02x\n", answer->size, answer->m.blah, answer->m.index, answer->m.blah3, answer->m.blah4, answer->m.blah5); 812 | 813 | if (state != STATE_SECOND_47) 814 | { 815 | struct message* question = (struct message*) sendbuf; 816 | int r; 817 | 818 | question->begin = 0x5a; 819 | question->size = 0x09; 820 | question->action = M; 821 | question->m.blah = 0x50; 822 | question->m.index = 2; 823 | question->m.blah3 = 0xa5; 824 | 825 | r = libusb_submit_transfer(send_transfer); 826 | if (r < 0) 827 | fprintf(stderr, "failed to submit transfer\n"); 828 | 829 | state = STATE_SECOND_47; 830 | } 831 | else 832 | { 833 | struct message* question = (struct message*) sendbuf; 834 | int r; 835 | 836 | question->begin = 0x5a; 837 | question->size = 0x04; 838 | question->action = SOMESTRING; 839 | question->somestring.end = 0xa5; 840 | 841 | r = libusb_submit_transfer(send_transfer); 842 | if (r < 0) 843 | fprintf(stderr, "failed to submit transfer\n"); 844 | 845 | state = STATE_44; 846 | } 847 | break; 848 | 849 | default: 850 | printf("unprocessed: received %02x: %d\n", answer->action, answer->size); 851 | break; 852 | } 853 | 854 | if (libusb_submit_transfer(recv_transfer) < 0) 855 | do_exit = 2; 856 | } 857 | 858 | static void LIBUSB_CALL 859 | cb_send(struct libusb_transfer* transfer) 860 | { 861 | message_in_transit = false; 862 | 863 | if (transfer->status != LIBUSB_TRANSFER_COMPLETED) 864 | { 865 | fprintf(stderr, "irq transfer status %d?\n", transfer->status); 866 | do_exit = 2; 867 | libusb_free_transfer(transfer); 868 | send_transfer = NULL; 869 | return; 870 | } 871 | 872 | switch (state) 873 | { 874 | case STATE_STARTSTOP2: 875 | { 876 | //struct message* question = (struct message*) sendbuf; 877 | int r; 878 | 879 | printf("----- sending i\n"); 880 | 881 | /*question->begin = 0x5a; 882 | question->size = 0x06; 883 | question->action = I; 884 | question->i.something = 0xffff; 885 | question->i.end = 0xa5; 886 | 887 | r = libusb_submit_transfer(send_transfer); 888 | if (r < 0) 889 | fprintf(stderr, "failed to submit transfer\n");*/ 890 | 891 | state = STATE_READY; 892 | } 893 | break; 894 | 895 | case STATE_H: 896 | { 897 | struct message* question = (struct message*) sendbuf; 898 | int r; 899 | 900 | printf("----- sending startstop 2\n"); 901 | 902 | question->begin = 0x5a; 903 | question->size = 0x05; 904 | question->action = STARTSTOP; 905 | question->startstop.stop = 1; 906 | question->startstop.end = 0xa5; 907 | 908 | r = libusb_submit_transfer(send_transfer); 909 | if (r < 0) 910 | fprintf(stderr, "failed to submit transfer\n"); 911 | 912 | state = STATE_STARTSTOP2; 913 | } 914 | break; 915 | 916 | 917 | case STATE_STARTSTOP: 918 | { 919 | struct message* question = (struct message*) sendbuf; 920 | int r; 921 | 922 | printf("----- sending first 0x47\n"); 923 | 924 | question->begin = 0x5a; 925 | question->size = 0x09; 926 | question->action = M; 927 | question->m.blah = 0x8; 928 | question->m.index = 6; 929 | question->m.blah3 = 0xa5; 930 | 931 | r = libusb_submit_transfer(send_transfer); 932 | if (r < 0) 933 | fprintf(stderr, "failed to submit transfer\n"); 934 | 935 | state = STATE_47; 936 | ; 937 | } 938 | break; 939 | } 940 | } 941 | 942 | static int 943 | init_communication(void) 944 | { 945 | int r; 946 | 947 | r = libusb_submit_transfer(recv_transfer); 948 | if (r < 0) 949 | return r; 950 | 951 | r = libusb_submit_transfer(send_transfer); 952 | if (r < 0) 953 | return r; 954 | 955 | /* start state machine */ 956 | state = STATE_AWAIT_INIT; 957 | return next_state(); 958 | } 959 | 960 | static int 961 | alloc_transfers(void) 962 | { 963 | recv_transfer = libusb_alloc_transfer(0); 964 | if (!recv_transfer) 965 | return -ENOMEM; 966 | 967 | libusb_fill_interrupt_transfer(recv_transfer, devh, EP_IN, recvbuf, 968 | sizeof(recvbuf), cb_recv, NULL, 0); 969 | 970 | struct message* question = (struct message*) sendbuf; 971 | 972 | question->begin = 0x5a; 973 | question->size = 0x04; 974 | question->action = INIT; 975 | question->m.blah = 0xa5; 976 | 977 | printf("init\n"); 978 | 979 | send_transfer = libusb_alloc_transfer(0); 980 | if (!send_transfer) 981 | return -ENOMEM; 982 | 983 | libusb_fill_interrupt_transfer(send_transfer, devh, EP_OUT, sendbuf, 984 | sizeof(sendbuf), cb_send, NULL, 0); 985 | 986 | return 0; 987 | } 988 | 989 | static void 990 | sighandler(int signum) 991 | { 992 | do_exit = 1; 993 | } 994 | 995 | void 996 | connect_callback(mosquitto* mosq, void* obj, int result) 997 | { 998 | printf("connected to MQTT server, rc=%d\n", result); 999 | } 1000 | 1001 | void 1002 | message_callback(mosquitto* mosq, void* obj, const struct mosquitto_message* message) 1003 | { 1004 | bool match = 0; 1005 | int value = 0; 1006 | 1007 | if (state != STATE_READY) 1008 | return; 1009 | 1010 | int target = strtol(message->topic, NULL, 16); 1011 | 1012 | if (errno == EINVAL || 1013 | errno == ERANGE) 1014 | return; 1015 | 1016 | if (strcmp((char*) message->payload, "true") == 0) 1017 | value = 255; 1018 | else 1019 | if (strcmp((char*) message->payload, "false") == 0) 1020 | value = 0; 1021 | else 1022 | return; 1023 | 1024 | printf("got message '%s' for topic '%s'\n", (char*) message->payload, message->topic); 1025 | 1026 | set_value(target, value); 1027 | 1028 | /*mosquitto_topic_matches_sub("/devices/wb-adc/controls/+", message->topic, &match); 1029 | if (match) { 1030 | printf("got message for ADC topic\n"); 1031 | }*/ 1032 | } 1033 | 1034 | int fds = 0; 1035 | pollfd fdset[10]; 1036 | 1037 | void usb_fd_added_cb(int fd, short events, void * source) 1038 | { 1039 | pollfd* mod = fdset; 1040 | 1041 | for (int i = 0; i < fds; ++i) 1042 | { 1043 | if (mod->fd == fd) 1044 | break; 1045 | 1046 | mod++; 1047 | } 1048 | 1049 | if (mod->fd == 0) 1050 | fds++; 1051 | 1052 | mod->fd = fd; 1053 | mod->events = events; 1054 | mod->revents = 0; 1055 | } 1056 | 1057 | void usb_fd_removed_cb(int fd, void* source) 1058 | { 1059 | pollfd* mod = fdset; 1060 | 1061 | for (int i = 0; i < fds; ++i) 1062 | { 1063 | if (mod->fd == fd) 1064 | break; 1065 | 1066 | mod++; 1067 | } 1068 | 1069 | printf("removing %d\n", fd); 1070 | mod->events = 0; 1071 | mod->revents = 0; 1072 | } 1073 | 1074 | int usb_init_fds(libusb_context *context) 1075 | { 1076 | const struct libusb_pollfd ** usb_fds = libusb_get_pollfds(context); 1077 | 1078 | if (!usb_fds) 1079 | return -1; 1080 | 1081 | for (int numfds = 0; usb_fds[numfds] != NULL; ++numfds) 1082 | { 1083 | usb_fd_added_cb(usb_fds[numfds]->fd, usb_fds[numfds]->events, NULL); 1084 | } 1085 | 1086 | free(usb_fds); 1087 | 1088 | libusb_set_pollfd_notifiers(context, usb_fd_added_cb, usb_fd_removed_cb, NULL); 1089 | return 0; 1090 | } 1091 | 1092 | int main(void) 1093 | { 1094 | char clientid[24]; 1095 | int rc = 0; 1096 | 1097 | libusb_context *context; 1098 | 1099 | mosquitto_lib_init(); 1100 | 1101 | memset(clientid, 0, 24); 1102 | snprintf(clientid, 23, "xcomfort_%d", getpid()); 1103 | mosq = mosquitto_new(clientid, 0, NULL); 1104 | 1105 | if (mosq) 1106 | { 1107 | mosquitto_connect_callback_set(mosq, connect_callback); 1108 | mosquitto_message_callback_set(mosq, message_callback); 1109 | 1110 | rc = mosquitto_connect(mosq, mqtt_host, mqtt_port, 60); 1111 | 1112 | if (rc) 1113 | printf("error connecting to MQTT server: %d\n", rc); 1114 | else 1115 | { 1116 | int err = mosquitto_subscribe(mosq, NULL, "#", 1); 1117 | } 1118 | } 1119 | 1120 | fds++; 1121 | 1122 | fdset[0].fd = mosquitto_socket(mosq); 1123 | fdset[0].events = POLLIN; 1124 | fdset[0].revents = 0; 1125 | 1126 | struct sigaction sigact; 1127 | int r = 1; 1128 | 1129 | r = libusb_init(&context); 1130 | if (r < 0) 1131 | { 1132 | fprintf(stderr, "failed to initialise libusb\n"); 1133 | exit(1); 1134 | } 1135 | 1136 | devh = libusb_open_device_with_vid_pid(NULL, 0x188a, 0x1102); 1137 | if (!devh) 1138 | { 1139 | fprintf(stderr, "Could not find/open device\n"); 1140 | goto out; 1141 | } 1142 | 1143 | if (libusb_kernel_driver_active(devh, 0) == 1) 1144 | { 1145 | r = libusb_detach_kernel_driver(devh, 0); 1146 | if (r < 0) 1147 | { 1148 | fprintf(stderr, "usb_detach_kernel_driver %d\n", r); 1149 | goto out; 1150 | } 1151 | 1152 | printf("detached kernel from device\n"); 1153 | } 1154 | 1155 | r = libusb_set_configuration(devh, 1); 1156 | if (r < 0) 1157 | { 1158 | fprintf(stderr, "libusb_set_configuration error %d\n", r); 1159 | goto out; 1160 | } 1161 | 1162 | r = libusb_claim_interface(devh, 0); 1163 | if (r < 0) 1164 | { 1165 | fprintf(stderr, "usb_claim_interface error %d\n", r); 1166 | goto out; 1167 | } 1168 | printf("claimed interface\n"); 1169 | 1170 | /* async from here onwards */ 1171 | 1172 | r = alloc_transfers(); 1173 | if (r < 0) 1174 | goto out_deinit; 1175 | 1176 | r = init_communication(); 1177 | if (r < 0) 1178 | goto out_deinit; 1179 | 1180 | sigact.sa_handler = sighandler; 1181 | sigemptyset(&sigact.sa_mask); 1182 | sigact.sa_flags = 0; 1183 | sigaction(SIGINT, &sigact, NULL); 1184 | sigaction(SIGTERM, &sigact, NULL); 1185 | sigaction(SIGQUIT, &sigact, NULL); 1186 | 1187 | usb_init_fds(context); 1188 | 1189 | while (!do_exit) 1190 | { 1191 | struct timeval tv = { 0, 0 }; 1192 | 1193 | if (mosquitto_want_write(mosq)) 1194 | fdset[0].events = POLLIN | POLLOUT; 1195 | else 1196 | fdset[0].events = POLLIN; 1197 | 1198 | if (poll(fdset, fds, 500) < 0) 1199 | break; 1200 | 1201 | if (fdset[0].revents & POLLIN) 1202 | mosquitto_loop_read(mosq, 1); 1203 | if (fdset[0].revents & POLLOUT) 1204 | mosquitto_loop_write(mosq, 1); 1205 | 1206 | fdset[0].revents = 0; 1207 | mosquitto_loop_misc(mosq); 1208 | 1209 | libusb_handle_events_timeout(context, &tv); 1210 | } 1211 | 1212 | printf("shutting down...\n"); 1213 | 1214 | if (recv_transfer) 1215 | { 1216 | r = libusb_cancel_transfer(recv_transfer); 1217 | if (r < 0) 1218 | goto out_deinit; 1219 | } 1220 | 1221 | while (recv_transfer) 1222 | if (libusb_handle_events(NULL) < 0) 1223 | break; 1224 | 1225 | if (send_transfer) 1226 | { 1227 | r = libusb_cancel_transfer(send_transfer); 1228 | if (r < 0) 1229 | goto out_deinit; 1230 | } 1231 | 1232 | while (send_transfer) 1233 | if (libusb_handle_events(NULL) < 0) 1234 | break; 1235 | 1236 | if (do_exit == 1) 1237 | r = 0; 1238 | else 1239 | r = 1; 1240 | 1241 | out_deinit: 1242 | 1243 | if (mosq) 1244 | mosquitto_destroy(mosq); 1245 | 1246 | mosquitto_lib_cleanup(); 1247 | 1248 | libusb_free_transfer(recv_transfer); 1249 | libusb_free_transfer(send_transfer); 1250 | 1251 | libusb_release_interface(devh, 0); 1252 | out: 1253 | libusb_close(devh); 1254 | libusb_exit(NULL); 1255 | 1256 | return r >= 0 ? r : -r; 1257 | } 1258 | 1259 | --------------------------------------------------------------------------------