├── README.md ├── firmware ├── bootloader.bin ├── firmware.bin ├── micropython.bin └── partition-table.bin ├── main.py ├── misc.py └── periphery.py /README.md: -------------------------------------------------------------------------------- 1 | # esp32_canbus_gateway 2 | micropython + esp32 + tja1050 3 | -------------------------------------------------------------------------------- /firmware/bootloader.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/randomserj/esp32_canbus_gateway/6d134d364e4c352a94db6329ef7c68fd157a2efb/firmware/bootloader.bin -------------------------------------------------------------------------------- /firmware/firmware.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/randomserj/esp32_canbus_gateway/6d134d364e4c352a94db6329ef7c68fd157a2efb/firmware/firmware.bin -------------------------------------------------------------------------------- /firmware/micropython.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/randomserj/esp32_canbus_gateway/6d134d364e4c352a94db6329ef7c68fd157a2efb/firmware/micropython.bin -------------------------------------------------------------------------------- /firmware/partition-table.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/randomserj/esp32_canbus_gateway/6d134d364e4c352a94db6329ef7c68fd157a2efb/firmware/partition-table.bin -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import gc 2 | import machine 3 | from machine import SoftI2C, UART 4 | import usocket as socket 5 | 6 | import misc 7 | from periphery import WLAN, OLED, CANTRX 8 | 9 | 10 | MODULE_NAME = 'MAIN' 11 | 12 | 13 | # 76543210 14 | def get_bit(number, n): 15 | bit = (number & (1 << n)) >> n 16 | return bit 17 | 18 | 19 | def check_frame(frame): 20 | pass 21 | 22 | 23 | def parse_frame(frame): 24 | try: 25 | data = frame.decode('utf-8') 26 | # TODO: 27 | # check frame structure and checksum 28 | # check if cmd is bin or hex 29 | except Exception as e: 30 | misc.debug_print(MODULE_NAME, misc.PREFIX_ERR, e) 31 | finally: 32 | cmd_bytes = [int(data[i:i + 2], 16) for i in range(0, len(data), 2)] 33 | handshake = get_bit(cmd_bytes[0], 7) 34 | checksum = get_bit(cmd_bytes[0], 0) 35 | if not (handshake ^ checksum): 36 | respond = misc.TERMINATE 37 | elif handshake: 38 | respond = cmd_bytes[0] ^ misc.MASK 39 | else: 40 | respond = misc.ACK 41 | return respond, cmd_bytes 42 | 43 | 44 | def run_cmd(cmd_bytes): 45 | mode = get_bit(cmd_bytes[0], 6) 46 | if mode: 47 | cfg = (cmd_bytes[0] & 0x30) >> 4 48 | if cfg == 0b11: 49 | machine.reset() 50 | if cfg == 0b01: 51 | trx._reset() 52 | if cfg == 0b10: 53 | show_wlan_connectivity() 54 | if cfg == 0b00: 55 | oled.cls() 56 | else: 57 | is_rx = get_bit(cmd_bytes[0], 5) 58 | is_tx = get_bit(cmd_bytes[0], 4) 59 | if is_rx: 60 | trx.rx() 61 | else: 62 | trx.rx(False) 63 | if is_tx: 64 | trx.tx(msgs=['3D0 8 00 80 00 00 00 00 00 00', '280 8 49 0E 00 10 0E 00 1B 0E']) 65 | else: 66 | trx.tx(False) 67 | 68 | 69 | def start(ip_addr='0.0.0.0'): 70 | socket_server = socket.socket() 71 | addr = socket.getaddrinfo(ip_addr, 9871)[0][-1] 72 | socket_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 73 | socket_server.bind(addr) 74 | socket_server.listen(5) 75 | FLAG_SS_OPEN = True 76 | while FLAG_SS_OPEN: 77 | client = socket_server.accept() 78 | stream = client[0] 79 | while True: 80 | frame = stream.recv(128) 81 | oled.clear_lines(SOCK_LINES) 82 | respond, payload = parse_frame(frame) 83 | oled.print('0x{:X} 0x{:0>8b}'.format(respond, payload[0]), SOCK_LINES[0]) 84 | misc.debug_print(MODULE_NAME, misc.PREFIX_DEBUG, 'Respond: 0x{:X} cmd: {:0>8b}'.format(respond, payload[0])) 85 | stream.send(hex(respond)[2:].encode('utf-8')) 86 | if respond == misc.TERMINATE: 87 | if payload[0] == 0xff: 88 | FLAG_SS_OPEN = False 89 | break 90 | run_cmd(payload) 91 | stream.close() 92 | socket_server.close() 93 | 94 | 95 | def show_wlan_connectivity(): 96 | oled.clear_lines(WLAN_LINES) 97 | oled.print('AP: {}'.format(wifi.if_ap.ifconfig()[0]), 0) 98 | sta_ip = wifi.up()[0] 99 | if sta_ip != '0.0.0.0': 100 | oled.print('STA: {}'.format(sta_ip), 1) 101 | 102 | 103 | WLAN_LINES = [0, 1] 104 | SOCK_LINES = [3, 4] 105 | CAN_LINES = [6, 7] 106 | 107 | gc.collect() 108 | wifi = WLAN() 109 | oled = OLED(SoftI2C(scl=misc.PIN_OLED_SCL, sda=misc.PIN_OLED_SDA)) 110 | uart = UART(2, 115200) # rx to esp.io17, tx to esp.io16 111 | #################################################### 112 | # init min CAN setup to avoid crash in twai.c :459 113 | __can = machine.CAN(0, mode=machine.CAN.SILENT_LOOPBACK) 114 | #################################################### 115 | trx = CANTRX(misc.CAN_DEFAULT_SPEED, uart) 116 | 117 | oled.cls() 118 | # TODO: 119 | # use oled for statuses 120 | show_wlan_connectivity() 121 | start() 122 | -------------------------------------------------------------------------------- /misc.py: -------------------------------------------------------------------------------- 1 | from machine import Pin 2 | 3 | 4 | PIN_CANBUS_RX = 4 5 | PIN_CANBUS_TX = 5 6 | PIN_UART_RX = 16 7 | PIN_UART_TX = 17 8 | PIN_OLED_SCL = Pin(22) 9 | PIN_OLED_SDA = Pin(21) 10 | 11 | LED_WLAN = Pin(32, Pin.OUT) 12 | LED_CANBUS_RX = Pin(19, Pin.OUT) 13 | LED_CANBUS_TX = Pin(23, Pin.OUT) 14 | 15 | PREFIX_INFO = 'INFO' 16 | PREFIX_WARN = 'WARNING' 17 | PREFIX_ERR = 'ERROR' 18 | PREFIX_DEBUG = 'DEBUG' 19 | PREFIXES = [PREFIX_INFO, PREFIX_WARN, PREFIX_ERR, PREFIX_DEBUG] 20 | 21 | WIFI_FILE = 'wifi' 22 | MASK = 0xff 23 | ACK = 0x55 24 | TERMINATE = 0xaa 25 | CAN_DEFAULT_SPEED = 500 26 | FONT = 8 27 | 28 | 29 | # TODO: 30 | # add debug option, rename to dprint 31 | def debug_print(module, prefix, text): 32 | if module is not None and prefix in PREFIXES: 33 | print('[{}]\t***\t{}\t***\t{}'.format(module, prefix, text)) 34 | else: 35 | raise TypeError("Wrong usage of debug_print") 36 | -------------------------------------------------------------------------------- /periphery.py: -------------------------------------------------------------------------------- 1 | import network 2 | import framebuf 3 | from micropython import const 4 | from machine import CAN 5 | import _thread as thread 6 | 7 | import misc 8 | 9 | 10 | # TODO: 11 | # make the AP secure 12 | # add sort of wifi manager 13 | class WLAN: 14 | def __init__(self): 15 | self.MODULE_NAME = 'WLAN' 16 | self.if_sta = network.WLAN(network.STA_IF) 17 | self.if_ap = network.WLAN(network.AP_IF) 18 | self.if_ap.active(True) 19 | self.if_ap.config(essid='gateway') 20 | self._reset() 21 | 22 | def _reset(self): 23 | self.if_sta.active(False) 24 | self.if_ap.active(True) 25 | misc.LED_WLAN.off() 26 | misc.debug_print(self.MODULE_NAME, misc.PREFIX_DEBUG, 'Reseted') 27 | misc.debug_print(self.MODULE_NAME, misc.PREFIX_INFO, 'AP IP: {}'.format(self.if_ap.ifconfig()[0])) 28 | 29 | def _stop(self): 30 | self.if_sta.active(False) 31 | self.if_ap.active(False) 32 | misc.debug_print(self.MODULE_NAME, misc.PREFIX_INFO, 'Stopped') 33 | 34 | def up(self): 35 | if not self.if_sta.active() or not self.if_sta.isconnected(): 36 | try: 37 | f = open(misc.WIFI_FILE, 'r') 38 | ssid, password, *_ = f.read().split(' ') 39 | self.if_sta = network.WLAN(network.STA_IF) 40 | misc.debug_print(self.MODULE_NAME, misc.PREFIX_INFO, 'Connecting to: {}'.format(ssid)) 41 | self.if_sta.active(True) 42 | self.if_sta.connect(ssid, password) 43 | while not self.if_sta.isconnected(): 44 | pass 45 | misc.LED_WLAN.on() 46 | misc.debug_print(self.MODULE_NAME, misc.PREFIX_INFO, 'OK, IP: {}'.format(self.if_sta.ifconfig()[0])) 47 | except Exception as e: 48 | misc.debug_print(self.MODULE_NAME, misc.PREFIX_DEBUG, e) 49 | return self.if_sta.ifconfig() 50 | 51 | def down(self): 52 | if self.if_sta.active(): 53 | self.if_sta.active(False) 54 | misc.debug_print(self.MODULE_NAME, misc.PREFIX_INFO, 'WiFi interface deactivated') 55 | misc.LED_WLAN.off() 56 | 57 | 58 | # TODO: 59 | # add cursor 60 | class OLED: 61 | def __init__(self, i2c, width=128, height=64, addr=0x3c, external_vcc=False): 62 | self.MODULE_NAME = 'OLED' 63 | self.i2c = i2c 64 | self.addr = addr 65 | self.temp = bytearray(2) 66 | self.width = width 67 | self.height = height 68 | self.external_vcc = external_vcc 69 | self.pages = self.height // 8 70 | self.buffer = bytearray(self.pages * self.width) 71 | self.fb = framebuf.FrameBuffer(self.buffer, self.width, self.height, framebuf.MONO_VLSB) 72 | self._reset() 73 | 74 | def _reset(self): 75 | self.power_off() 76 | self.power_on() 77 | self.fb.fill(0) 78 | self.show() 79 | misc.debug_print(self.MODULE_NAME, misc.PREFIX_DEBUG, 'Reseted') 80 | 81 | def write_cmd(self, cmd): 82 | self.temp[0] = 0x80 # Co=1, D/C#=0 83 | self.temp[1] = cmd 84 | self.i2c.writeto(self.addr, self.temp) 85 | 86 | def write_data(self, buf): 87 | self.i2c.writeto(self.addr, b'\x40' + buf) 88 | 89 | def power_off(self): 90 | self.write_cmd(const(0xae) | 0x00) 91 | 92 | def power_on(self): 93 | self.write_cmd(const(0xae) | 0x01) 94 | 95 | def show(self): 96 | for page in range(self.height // 8): 97 | self.write_cmd(const(0xb0) | page) 98 | self.write_cmd(const(0x00) | 2) 99 | self.write_cmd(const(0x10) | 0) 100 | self.write_data(self.buffer[self.width * page:self.width * page + self.width]) 101 | 102 | def cls(self): 103 | self.fb.fill(0) 104 | self.show() 105 | 106 | def print(self, text='', line=0, pos=0): 107 | self.fb.text(text, pos*misc.FONT, line*misc.FONT) 108 | self.show() 109 | 110 | def clear(self, line=0, pos=0, count=1): 111 | self.fb.fill_rect(pos*misc.FONT, line*misc.FONT, count*misc.FONT, misc.FONT, 0) 112 | self.show() 113 | 114 | def clear_line(self, line): 115 | self.clear(line, 0, 16) 116 | 117 | def clear_lines(self, lines=()): 118 | for line in lines: 119 | self.clear_line(line) 120 | 121 | 122 | class CANTRX: 123 | def __init__(self, speed, output): 124 | self.MODULE_NAME = 'CAN' 125 | self.can = CAN(0, mode=CAN.NORMAL, baudrate=speed, tx_io=misc.PIN_CANBUS_TX, rx_io=misc.PIN_CANBUS_RX, auto_restart=False) 126 | self.output = output 127 | self._reset(True) 128 | 129 | def _reset(self, debug=True): 130 | self.FLAG_RX_EXIT = True 131 | self.FLAG_TX_EXIT = True 132 | self.can.clear_rx_queue() 133 | self.can.clear_tx_queue() 134 | misc.LED_CANBUS_RX.off() 135 | misc.LED_CANBUS_TX.off() 136 | if debug: 137 | misc.debug_print(self.MODULE_NAME, misc.PREFIX_DEBUG, 'Reseted') 138 | 139 | def _thread_canbus_rx(self): 140 | while self.can.any() and not self.FLAG_RX_EXIT: 141 | msg = self.rx_msg() 142 | self.output.write(bytearray(msg + '\n\r')) 143 | self.can.clear_rx_queue() 144 | misc.LED_CANBUS_RX.off() 145 | self.FLAG_RX_EXIT = True 146 | thread.exit() 147 | 148 | def _thread_canbus_tx(self, ms): 149 | while not self.FLAG_TX_EXIT: 150 | try: 151 | for m in ms: 152 | m_id, m_dlc, *m_data = m.split(' ') 153 | self.can.send([int(d, 16) for d in m_data], int(m_id, 16)) 154 | except Exception as e: 155 | misc.debug_print(self.MODULE_NAME, misc.PREFIX_ERR, e) 156 | break 157 | self.can.clear_tx_queue() 158 | misc.LED_CANBUS_TX.off() 159 | self.FLAG_TX_EXIT = True 160 | thread.exit() 161 | 162 | def rx_msg(self): 163 | try: 164 | msg_id, *_, msg_data = self.can.recv() 165 | msg_dlc = len(msg_data) 166 | msg = '{:03X} '.format(msg_id) + '{} '.format(msg_dlc) + ' '.join(['{:02X}'.format(b) for b in msg_data]) 167 | return msg 168 | except Exception as e: 169 | misc.debug_print(self.MODULE_NAME, misc.PREFIX_ERR, e) 170 | 171 | def rx(self, cmd=True): 172 | if not cmd or cmd == 0: 173 | self.FLAG_RX_EXIT = True 174 | return None 175 | if self.FLAG_RX_EXIT: 176 | self.FLAG_RX_EXIT = False 177 | misc.LED_CANBUS_RX.on() 178 | thread.start_new_thread(self._thread_canbus_rx, ()) 179 | return None 180 | 181 | def tx(self, cmd=True, msgs=None): 182 | if not cmd or cmd == 0: 183 | self.FLAG_TX_EXIT = True 184 | return None 185 | if self.FLAG_TX_EXIT: 186 | self.FLAG_TX_EXIT = False 187 | misc.LED_CANBUS_TX.on() 188 | thread.start_new_thread(self._thread_canbus_tx, (msgs,)) 189 | return None 190 | --------------------------------------------------------------------------------