Last active
October 22, 2019 09:45
-
-
Save ciniml/677451bf20ca78bf98000dd28fc10051 to your computer and use it in GitHub Desktop.
UIFlow M5StickC Wi-SUN HAT Custom Block
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Block __WiSUN_start | |
var __WiSUN_start_json = { | |
"previousStatement": null, | |
"nextStatement": null, | |
"message0": "%1", | |
"args0": [ | |
{ | |
"type": "field_label", | |
"text": "start" | |
} | |
], | |
"message1": "%1 %2", | |
"args1": [ | |
{ | |
"type": "field_label", | |
"text": "route_b_id" | |
}, | |
{ | |
"type": "input_value", | |
"name": "route_b_id" | |
} | |
], | |
"message2": "%1 %2", | |
"args2": [ | |
{ | |
"type": "field_label", | |
"text": "route_b_password" | |
}, | |
{ | |
"type": "input_value", | |
"name": "route_b_password" | |
} | |
], | |
"message3": "%1 %2", | |
"args3": [ | |
{ | |
"type": "field_label", | |
"text": "rx" | |
}, | |
{ | |
"type": "field_number", | |
"value": 0, | |
"name": "rx" | |
} | |
], | |
"message4": "%1 %2", | |
"args4": [ | |
{ | |
"type": "field_label", | |
"text": "tx" | |
}, | |
{ | |
"type": "field_number", | |
"value": 0, | |
"name": "tx" | |
} | |
], | |
"message5": "%1 %2", | |
"args5": [ | |
{ | |
"type": "field_label", | |
"text": "reset" | |
}, | |
{ | |
"type": "field_number", | |
"value": 0, | |
"name": "reset" | |
} | |
], | |
"colour": "#ff8000" | |
}; | |
window['Blockly'].Blocks['__WiSUN_start'] = { | |
init: function() { | |
this.jsonInit(__WiSUN_start_json); | |
} | |
}; | |
window['Blockly'].Python['__WiSUN_start'] = function(block) { | |
var route_b_id = Blockly.Python.valueToCode(block, 'route_b_id', Blockly.Python.ORDER_NONE); | |
var route_b_password = Blockly.Python.valueToCode(block, 'route_b_password', Blockly.Python.ORDER_NONE); | |
var rx = block.getFieldValue('rx'); | |
var tx = block.getFieldValue('tx'); | |
var reset = block.getFieldValue('reset'); | |
return `import sys | |
import machine | |
sys.path.append('/flash/res') | |
import wisun | |
wisun_uart = machine.UART(1, rx=${rx}, tx=${tx}, baudrate=115200) | |
wisun_reset = machine.Pin(${reset}, machine.Pin.OUT) | |
wisun = wisun.WiSUN(wisun_uart, None, wisun_reset) | |
wisun.start(${route_b_id}, ${route_b_password})` + "\n"; | |
}; | |
// Block __WiSUN_state | |
var __WiSUN_state_json = { | |
"output": null, | |
"message0": "%1", | |
"args0": [ | |
{ | |
"type": "field_label", | |
"text": "state" | |
} | |
], | |
"colour": "#ff8000" | |
}; | |
window['Blockly'].Blocks['__WiSUN_state'] = { | |
init: function() { | |
this.jsonInit(__WiSUN_state_json); | |
} | |
}; | |
window['Blockly'].Python['__WiSUN_state'] = function(block) { | |
return [`wisun.state()`, Blockly.Python.ORDER_CONDITIONAL] | |
}; | |
// Block __WiSUN_values | |
var __WiSUN_values_json = { | |
"output": null, | |
"message0": "%1", | |
"args0": [ | |
{ | |
"type": "field_label", | |
"text": "values" | |
} | |
], | |
"colour": "#ff8000" | |
}; | |
window['Blockly'].Blocks['__WiSUN_values'] = { | |
init: function() { | |
this.jsonInit(__WiSUN_values_json); | |
} | |
}; | |
window['Blockly'].Python['__WiSUN_values'] = function(block) { | |
return [`wisun.values()`, Blockly.Python.ORDER_CONDITIONAL] | |
}; | |
// Block __WiSUN_values_blocked | |
var __WiSUN_values_blocked_json = { | |
"output": null, | |
"message0": "%1", | |
"args0": [ | |
{ | |
"type": "field_label", | |
"text": "values(blocked)" | |
} | |
], | |
"colour": "#ff8000" | |
}; | |
window['Blockly'].Blocks['__WiSUN_values_blocked'] = { | |
init: function() { | |
this.jsonInit(__WiSUN_values_blocked_json); | |
} | |
}; | |
window['Blockly'].Python['__WiSUN_values_blocked'] = function(block) { | |
return [`wisun.wait_values()`, Blockly.Python.ORDER_CONDITIONAL] | |
}; | |
// Block __WiSUN_wait | |
var __WiSUN_wait_json = { | |
"previousStatement": null, | |
"nextStatement": null, | |
"message0": "%1", | |
"args0": [ | |
{ | |
"type": "field_label", | |
"text": "wait" | |
} | |
], | |
"colour": "#ff8000" | |
}; | |
window['Blockly'].Blocks['__WiSUN_wait'] = { | |
init: function() { | |
this.jsonInit(__WiSUN_wait_json); | |
} | |
}; | |
window['Blockly'].Python['__WiSUN_wait'] = function(block) { | |
return `wisun.wait_values()` + "\n"; | |
}; | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Module to control Rohm BP35A1 | |
License: Boost Software License 1.0 | |
""" | |
import logging | |
import time | |
import _thread | |
import struct | |
try: | |
from mpy_builtins import machine, const | |
from typing import Tuple, Callable, List, Dict, Optional, Union, Any | |
except: | |
import machine | |
class CancelledError(BaseException): | |
def __init__(self): | |
pass | |
cancelled_error = CancelledError() | |
stop_iteration = StopIteration() | |
class SleepAwaitable(object): | |
def __init__(self): | |
self.value = None # type:Optional[Union[float, BaseException]] | |
def __call__(self, value: Optional[Union[float, BaseException]]): | |
self.value = value | |
return self | |
def __iter__(self): | |
return self | |
def __next__(self): | |
if self.value is None: | |
raise stop_iteration | |
elif self.value is BaseException: | |
raise self.value | |
else: | |
time.sleep(self.value) | |
self.value = None | |
return self | |
class asyncio(object): | |
_sleep_awaitable = SleepAwaitable() | |
@staticmethod | |
def sleep_ms(duration_ms:int) -> SleepAwaitable: | |
return asyncio._sleep_awaitable(duration_ms*1.0e-3) | |
@staticmethod | |
def sleep(duration:float) -> SleepAwaitable: | |
return asyncio._sleep_awaitable(duration) | |
class BP35A1Error(RuntimeError): | |
def __init__(self, message:str) -> None: | |
super().__init__(message) | |
class WaitEvent(object): | |
def __init__(self): | |
self.__lock = _thread.allocate_lock() | |
self.__value_lock = _thread.allocate_lock() | |
self.__value = None | |
self.__lock.acquire() | |
def notify(self, value: Any): | |
self.__value_lock.acquire() | |
self.__value = value | |
self.__value_lock.release() | |
self.__lock.acquire(False) | |
self.__lock.release() | |
def wait(self, timeout: float = -1) -> Optional[Any]: | |
while True: | |
self.__value_lock.acquire() | |
if self.__value is not None: | |
value = self.__value | |
self.__value = None | |
self.__value_lock.release() | |
return value | |
else: | |
self.__value_lock.release() | |
if not self.__lock.acquire(True, timeout): | |
return None | |
def release(self): | |
self.__lock = None | |
class BP35A1(object): | |
"Controls Rohm BP35A1 Wi-SUN ECHONET module" | |
CR = const(0x0d) | |
LF = const(0x0a) | |
SPC = const(0x20) | |
IOEXPANDER_REG_OUTPUT = 0x02 | |
IOEXPANDER_OUTPUT_WKUP = 0x01 | |
IOEXPANDER_OUTPUT_RESET = 0x02 | |
IOEXPANDER_OUTPUT_RTS = 0x04 | |
def __init__(self, uart: machine.UART, wkup: Optional[machine.Pin], reset: machine.Pin) -> None: | |
"Construct BP35A1 instance" | |
self.__l = logging.Logger('BP35A1') | |
self.__uart = uart | |
self.__wkup = wkup | |
self.__reset = reset | |
def initialize(self) -> None: | |
"Initialize I/O ports and peripherals to communicate with the module." | |
if self.__wkup is not None: | |
self.__wkup.value(True) | |
self.__reset.value(False) # Assert RESET | |
self.__uart.init(baudrate=115200, timeout=5000) | |
async def reset(self) -> bool: | |
"Reset the module." | |
self.__reset.value(False) # Assert RESET at least 1 [ms] | |
await asyncio.sleep_ms(10) # / | |
self.__reset.value(True) # Deassert RESET | |
await asyncio.sleep_ms(3000) # We must wait 3000 milliseconds after RESET pin is deasserted. (HW spec p.14) | |
responded = False | |
for trial in range(15): | |
self.write_command(b'SKVER') | |
if await self.wait_response(b'SKVER', timeout=500) is not None: | |
if await self.wait_response(b'EVER', timeout=500) is not None: | |
if await self.wait_response(b'OK', timeout=500) is not None: | |
responded = True | |
break | |
if not responded: | |
self.__l.info("The module did not respond within timeout period.") | |
return False | |
# Disable command echo-back | |
if not await self.write_command_wait(b'SKSREG SFE 0', b'OK', timeout=1000): | |
self.__l.error("Failed to initialize the module.") | |
return True | |
async def set_password(self, password: str, timeout:int = None) -> bool: | |
"Generate PSK from the password and register it." | |
length = len(password) | |
if length == 0 or length > 32: | |
raise ValueError('The password length must be from 1 to 32 inclusive.') | |
command = bytes('SKSETPWD {0:02X} {1}'.format(length, password), 'utf-8') | |
return await self.write_command_wait(command, b'OK', timeout=timeout) | |
async def set_route_b_id(self, route_b_id: str, timeout:Optional[int] = None) -> bool: | |
"Sets Route-B ID" | |
length = len(route_b_id) | |
if length != 32: | |
raise ValueError('The Route-B ID length must be 32.') | |
command = bytes('SKSETRBID ' + route_b_id, 'utf-8') | |
return await self.write_command_wait(command, b'OK', timeout=timeout) | |
async def scan(self, channel_mask:int, scan_duration:int, timeout:Optional[int] = None, scan_timeout:Optional[int] = None) -> Tuple[bool, List[Dict[bytes, bytes]]]: | |
"Perform active scan and collect PAN list." | |
if scan_duration < 0 or scan_duration > 14: | |
raise ValueError('scan_duration must be from 0 to 14 inclusive.') | |
command = bytes('SKSCAN 2 {0:8X} {1}'.format(channel_mask, scan_duration), 'utf-8') | |
if not await self.write_command_wait(command, b'OK', timeout=timeout): | |
return False, [] | |
buffer = bytearray(1024) | |
mv = memoryview(buffer) | |
pans = [] # type: List[Dict[bytes, bytes]] | |
pan = None # type: Optional[Dict[bytes, bytes]] | |
while True: | |
response_length = await self.read_response_into(buffer, timeout=scan_timeout) | |
if response_length is None: | |
# Timed out. | |
return False, pans | |
response = mv[:response_length] # type: memoryview | |
self.__l.debug("response: %s", bytes(response)) | |
if response == b'EPANDESC': | |
if pan is not None: | |
pans.append(pan) | |
pan = {} | |
elif response_length >= 8 and response[:8] == b'EVENT 22': # end of scan | |
if pan is not None: # Add the last PAN | |
pans.append(pan) | |
break | |
elif response_length >= 6 and response[:6] == b'EVENT ': # Other event | |
pass # Just ignore this response. | |
else: | |
if pan is not None: | |
pair = bytes(response) # type: bytes | |
key, value = pair.strip().split(b':', 2) | |
if value is None: | |
pan[b'PairID'] = key | |
else: | |
pan[key] = value | |
return True, pans | |
async def set_channel(self, channel: int, timeout:Optional[int] = None) -> bool: | |
"Sets communication channel" | |
command = bytes('SKSREG S2 {0:02X}'.format(channel), 'utf-8') | |
return await self.write_command_wait(command, b'OK', timeout=timeout) | |
async def set_pan_id(self, pan_id: str, timeout:Optional[int] = None) -> bool: | |
"Sets PAN ID" | |
command = b'SKSREG S3 ' + bytes(pan_id, 'utf-8') | |
return await self.write_command_wait(command, b'OK', timeout=timeout) | |
async def get_link_local_address(self, mac_address:str, timeout:Optional[int] = None) -> Optional[str]: | |
"Gets the link local address from the corresponding MAC address." | |
command = b'SKLL64 ' + bytes(mac_address, 'utf-8') | |
self.write_command(command) | |
buffer = bytearray(1024) | |
response_length = await self.read_response_into(buffer, timeout=timeout) | |
if response_length is None: | |
return None | |
else: | |
return str(buffer[:response_length], 'utf-8') | |
async def connect_to(self, ll_address:str, timeout:Optional[int] = None) -> bool: | |
"Connect to a PAA as a PaC" | |
command = b'SKJOIN ' + bytes(ll_address, 'utf-8') | |
if not await self.write_command_wait(command, b'OK', timeout=timeout): | |
return False | |
# Wait until EVENT 0x25 (PANA connection has completed successfully) arrives. | |
buffer = bytearray(1024) | |
mv = memoryview(buffer) | |
while True: | |
response_length = await self.read_response_into(buffer, timeout=timeout) | |
if response_length is None: | |
# timed out | |
return False | |
else: | |
self.__l.debug("response: %s", buffer[:response_length]) | |
if response_length > 8: | |
if mv[:8] == b'EVENT 24': | |
# PANA connection failed. | |
return False | |
elif mv[:8] == b'EVENT 25': | |
# PANA connection complete | |
return True | |
async def send_to(self, do_encrypt:bool, ll_address:str, port:int, data:bytes, timeout:Optional[int] = None) -> bool: | |
encrypt_flag = '1' if do_encrypt else '0' | |
command = bytes('SKSENDTO 1 {0} {1:04X} {2} {3:04X} '.format(ll_address, port, encrypt_flag, len(data)), 'utf-8') | |
self.write(command) | |
self.write(data) | |
return await self.wait_response(b'OK', timeout=timeout) is not None | |
async def read_response_block(self, buffer:bytearray, offset:int=0, timeout:Optional[int] = None) -> Optional[int]: | |
buffer_length = len(buffer) | |
response_length = 0 | |
start_time_ms = time.ticks_ms() | |
while True: | |
c = self.__readchar() | |
if c < 0: | |
if timeout is not None and (time.ticks_ms()-start_time_ms) >= timeout: | |
return None | |
try: | |
await asyncio.sleep_ms(1) | |
except asyncio.CancelledError: | |
return None | |
continue | |
# self.__l.debug('%c', c) | |
if c == BP35A1.CR or c == BP35A1.LF: | |
pass | |
elif c == BP35A1.SPC: | |
return response_length | |
else: | |
buffer[offset+response_length] = c | |
response_length += 1 | |
if offset+response_length == buffer_length: | |
return response_length | |
async def receive(self, buffer:bytearray, timeout:Optional[int] = None) -> Optional[memoryview]: | |
head = b'ERXUDP' | |
head_len = len(head) | |
mv = memoryview(buffer) | |
start_time_ms = time.ticks_ms() | |
while timeout is None or time.ticks_ms() - start_time_ms < timeout: | |
response = await self.read_response_block(mv, timeout=timeout) | |
if response is None or response != head_len or mv[:head_len] != head: | |
pass | |
else: | |
# self.__l.debug('response:{0}'.format(bytes(mv[:response]))) | |
break | |
block_count = 1 | |
while True: | |
response = await self.read_response_block(mv, timeout=timeout) | |
if response is None: | |
return None | |
self.__l.debug('response block{0}:{1}'.format(block_count, bytes(mv[:response]))) | |
block_count += 1 | |
if block_count == 8: | |
data_len = int(str(mv[:response], 'utf-8'), 16) | |
break | |
self.__l.debug('ERXUDP DATALEN={0}'.format(data_len)) | |
bytes_read = self.__uart.readinto(mv[:data_len]) | |
return mv[:bytes_read+1] | |
def write(self, s:bytes) -> None: | |
self.__l.debug('<- %s', s) | |
self.__uart.write(s) | |
def read(self, length:int) -> bytes: | |
return self.__uart.read(length) | |
def write_command(self, command:bytes) -> None: | |
self.__l.debug('<- %s', command) | |
self.__uart.write(command) | |
self.__uart.write(b'\r\n') | |
async def write_command_wait(self, command:bytes, expected_response:bytes, timeout:Optional[int] = None) -> bool: | |
self.write_command(command) | |
return await self.wait_response(expected_response, timeout=timeout) is not None | |
def __readchar(self) -> int: | |
char_buffer = bytearray(1) | |
n = self.__uart.readinto(char_buffer) | |
return -1 if n is None or n == 0 else char_buffer[0] | |
async def read_response_into(self, buffer:bytearray, offset:int=0, timeout:Optional[int] = None) -> Optional[int]: | |
buffer_length = len(buffer) | |
response_length = 0 | |
state = 0 | |
start_time_ms = time.ticks_ms() | |
while True: | |
c = self.__readchar() | |
if c < 0: | |
if timeout is not None and (time.ticks_ms()-start_time_ms) >= timeout: | |
return None | |
try: | |
await asyncio.sleep_ms(1) | |
except asyncio.CancelledError: | |
return None | |
continue | |
#self.__l.debug('S:%d R:%c', state, c) | |
if state == 0 and c == BP35A1.CR: | |
state = 0 if response_length == 0 else 1 | |
elif state == 0 and c == BP35A1.LF: | |
state = 0 | |
elif state == 0: | |
buffer[offset+response_length] = c | |
response_length += 1 | |
if offset+response_length == buffer_length: | |
return response_length | |
elif state == 1 and c == BP35A1.LF: | |
return response_length | |
elif state == 1 and c == BP35A1.CR: | |
state = 1 | |
elif state == 1: | |
state = 0 | |
async def wait_response(self, expected_response:bytes, max_response_size:int=1024, timeout:Optional[int] = None) -> Optional[bytes]: | |
self.__l.debug('wait_response: target=%s', expected_response) | |
response = bytearray(max_response_size) | |
expected_length = len(expected_response) | |
while True: | |
length = await self.read_response_into(response, timeout=timeout) | |
if length is None: return None | |
self.__l.debug("wait_response: response=%s", response[:length]) | |
if length >= expected_length and response[:expected_length] == expected_response: | |
return response[:length] | |
async def wait_response_into(self, expected_response:bytes, response_buffer:bytearray, timeout:Optional[int] = None) -> Optional[memoryview]: | |
self.__l.debug('wait_response_into: target=%s', expected_response) | |
expected_length = len(expected_response) | |
mv = memoryview(response_buffer) | |
while True: | |
length = await self.read_response_into(response_buffer, timeout=timeout) | |
if length is None: return None | |
self.__l.debug("wait_response_into: response=%s", bytes(mv[:length])) | |
if length >= expected_length and mv[:expected_length] == expected_response: | |
return mv[:length] | |
async def wait_prompt(self, expected_prompt:bytes, timeout:Optional[int] = None) -> bool: | |
prompt_length = len(expected_prompt) | |
index = 0 | |
start_time_ms = time.ticks_ms() | |
while True: | |
c = self.__readchar() | |
if c < 0: | |
if time.ticks_ms() - start_time_ms > timeout: | |
return False | |
await asyncio.sleep_ms(1) | |
continue | |
if expected_prompt[index] == c: | |
index += 1 | |
if index == prompt_length: | |
return True | |
else: | |
index = 0 | |
async def execute_command(self, command:bytes, response_buffer:bytearray, index:int=0, expected_response_predicate:Callable[[memoryview],bool]=None, expected_response_list:List[bytes]=[b'OK'], timeout:int=None) -> Tuple[bool, List[memoryview]]: | |
assert expected_response_predicate is not None or expected_response_list is not None | |
if expected_response_predicate is None: | |
expected_response_predicate = lambda mv: mv in expected_response_list | |
self.write_command(command) | |
buffer_length = len(response_buffer) | |
responses = [] # type: List[memoryview] | |
mv = memoryview(response_buffer) | |
while True: | |
length = await self.read_response_into(response_buffer, index, timeout=timeout) | |
if length is None: | |
return (False, responses) | |
response = mv[index:index+length] | |
responses.append(response) | |
if expected_response_predicate(response): | |
return (True, responses) | |
index += length | |
async def execute_command_single_response(self, command:bytes, starts_with:bytes=None, timeout:Optional[int] = None) -> Optional[bytes]: | |
buffer = bytearray(1024) | |
result, responses = await self.execute_command(command, buffer, timeout=timeout) | |
if not result: return None | |
starts_with_length = len(starts_with) if starts_with is not None else 0 | |
for response in responses: # type: Union[memoryview, bytes] | |
if starts_with_length == 0 and len(response) > 0: | |
response = bytes(response) | |
self.__l.debug('-> %s', response) | |
return response | |
if starts_with_length > 0 and len(response) >= starts_with_length and response[:starts_with_length] == starts_with: | |
response = bytes(response) | |
self.__l.debug('-> %s', response) | |
return response | |
return None | |
class EchonetLiteFrame(object): | |
ESV_SETI_SNA = 0x50 | |
ESV_SETC_SNA = 0x51 | |
ESV_GET_SNA = 0x52 | |
ESV_INF_SNA = 0x53 | |
ESV_SETGET_SNA = 0x5e | |
ESV_SETI = 0x60 | |
ESV_SETC = 0x61 | |
ESV_GET = 0x62 | |
ESV_INF_REQ = 0x63 | |
ESV_SETGET = 0x6e | |
ESV_SET_RES = 0x71 | |
ESV_GET_RES = 0x72 | |
ESV_INF = 0x73 | |
ESV_INFC = 0x74 | |
ESV_INFC_RES = 0x7a | |
ESV_SETGET_RES = 0x7e | |
def __init__(self, buffer): | |
if not isinstance(buffer, memoryview): | |
self._m = memoryview(buffer) | |
else: | |
self._m = buffer # type: memoryview | |
self._next_offset = 12 | |
def is_valid(self) -> bool: | |
if self._m is None or len(self._m) < 4: | |
return False | |
# Check EHD | |
if not (self._m[0] == 0x10 and self._m[1] == 0x81): | |
return False | |
return True | |
def init(self) -> None: | |
self._m[0] = 0x10 | |
self._m[1] = 0x81 | |
def tid(self, value:Optional[int]=None)->int: | |
if value is not None: | |
self._m[2] = value >> 8 | |
self._m[3] = value & 0xff | |
return (self._m[2] << 8) | self._m[3] | |
def seoj(self, value:Optional[bytes]=None)->memoryview: | |
if value is not None: | |
self._m[4:4+3] = value | |
return self._m[4:4+3] | |
def deoj(self, value:Optional[bytes]=None)->memoryview: | |
if value is not None: | |
self._m[7:7+3] = value | |
return self._m[7:7+3] | |
def esv(self, value:Optional[int]=None)->int: | |
if value is not None: | |
self._m[10] = value | |
return self._m[10] | |
def opc(self, value:Optional[int]=None)->int: | |
if value is not None: | |
self._m[11] = value | |
return self._m[11] | |
def target_properies(self)->Generator[memoryview, None, None]: | |
opc = self.opc() | |
offset = 12 | |
for i in range(opc): | |
if offset + 1 >= len(self._m): | |
break | |
pdc = self._m[offset + 1] | |
if offset + 2 + pdc >= len(self._m): | |
break | |
yield self._m[offset:offset + 2 + pdc] | |
offset += 2 + pdc | |
def clear_properties(self)->None: | |
self.opc(0) | |
self._next_offset = 12 | |
def add_property(self, epc:int, data:Optional[bytes])->None: | |
self._m[self._next_offset + 0] = epc | |
data_len = len(data) if data is not None else 0 | |
self._m[self._next_offset + 1] = data_len | |
if data_len > 0: | |
self._m[self._next_offset + 2:self._next_offset + 2 + data_len] = data | |
self._next_offset += 2 + data_len | |
self.opc(self.opc() + 1) | |
def get_length(self) -> int: | |
return self._next_offset | |
def bytes(self)->memoryview: | |
return self._m[:self._next_offset+1] | |
PROPERTY_COEFFICIENT = const(0xd3) # 係数 | |
PROPERTY_CUMULATIVE_VALUE = const(0xe0) # 積算電力量計測値 | |
PROPERTY_CUMULATIVE_UNIT = const(0xe1) # 積算電力量単位 | |
PROPERTY_INSTANT_POWER = const(0xe7) # 瞬時電力計測値 | |
PROPERTY_INSTANT_CURRENT = const(0xe8) # 瞬時電流計測値 | |
# Construct ECHONETlite request frame | |
getPropertyFrame = EchonetLiteFrame(bytearray(64)) | |
getPropertyFrame.init() | |
getPropertyFrame.tid(0x0000) # TID = 0x0000 | |
getPropertyFrame.seoj(b'\x05\xff\x01') # SEOJ 送信元EOJ クラスグループ=管理・操作関連機器, クラス=コントローラ | |
getPropertyFrame.deoj(b'\x02\x88\x01') # DEOJ 送信先EOJ クラスグループ=住宅・設備関連機器, クラス=低圧スマート電力量メータ | |
getPropertyFrame.esv(EchonetLiteFrame.ESV_GET) # プロパティ読み出し要求 | |
getPropertyFrame.add_property(PROPERTY_COEFFICIENT , None) | |
getPropertyFrame.add_property(PROPERTY_CUMULATIVE_VALUE, None) | |
getPropertyFrame.add_property(PROPERTY_CUMULATIVE_UNIT , None) | |
getPropertyFrame.add_property(PROPERTY_INSTANT_POWER , None) | |
getPropertyFrame.add_property(PROPERTY_INSTANT_CURRENT , None) | |
class WiSUN(object): | |
STATE_ERROR = const(-1) | |
STATE_INITIALIZING = const(0) | |
STATE_SCANNING = const(1) | |
STATE_CONNECTING = const(2) | |
STATE_CONNECTED = const(3) | |
@staticmethod | |
def __thread_proc(obj: WiSUN) -> None: | |
runner = obj.__run() # type: SleepAwaitable | |
try: | |
while True: | |
next(runner) | |
runner.send(None) | |
except StopIteration: | |
pass | |
print('WiSUN thread stopped') | |
def __init__(self, uart: machine.UART, wake_up: Optional[machine.Pin], reset: machine.Pin): | |
self.__lock = _thread.allocate_lock() | |
self.__thread = None | |
self.__bp35 = BP35A1(uart, wake_up, reset) | |
self.__state = WiSUN.STATE_INITIALIZING | |
self.__l = logging.Logger('WiSUN') | |
self.__route_b_id = None # type: Optional[str] | |
self.__route_b_password = None # type: Optional[str] | |
self.__instant_power = None # type: Optional[float] | |
self.__cumulative_power = None # type: Optional[float] | |
self.__timestamp = None # type: Optional[float] | |
self.__wait = WaitEvent() | |
def start(self, route_b_id: str, route_b_password: str) -> None: | |
self.__route_b_id = route_b_id | |
self.__route_b_password = route_b_password | |
self.__thread = _thread.start_new_thread(WiSUN.__thread_proc, (self,)) | |
def __make_values(self) -> Dict[str, Any]: | |
return { | |
'instant_power': self.__instant_power, | |
'cumulative_power': self.__cumulative_power, | |
'timestamp': self.__timestamp, | |
'state': self.__state, | |
} | |
def __notify_values(self) -> None: | |
values = self.__make_values() | |
self.__wait.notify(values) | |
def state(self) -> int: | |
self.__lock.acquire() | |
value = self.__state | |
self.__lock.release() | |
return value | |
def __set_state(self, state: int) -> None: | |
self.__lock.acquire() | |
self.__state = state | |
self.__notify_values() | |
self.__lock.release() | |
def values(self) -> Dict[str, Any]: | |
self.__lock.acquire() | |
values = self.__make_values() | |
self.__lock.release() | |
return values | |
def wait_values(self, timeout: float = -1) -> Optional[Dict[str, Any]]: | |
return self.__wait.wait() | |
async def __run(self): | |
response_buffer = bytearray(1024) | |
while True: | |
self.__set_state(WiSUN.STATE_INITIALIZING) | |
if not await self.__bp35.reset(): | |
self.__l.error('Failed to reset the Wi-SUN module.') | |
self.__set_state(WiSUN.STATE_ERROR) | |
await asyncio.sleep(1) | |
continue | |
if not await self.__bp35.set_password(self.__route_b_password, timeout=5000): | |
self.__l.error('Failed to set password.') | |
continue | |
if not await self.__bp35.set_route_b_id(self.__route_b_id, timeout=5000): | |
self.__l.error('Failed to set route-b ID.') | |
continue | |
while True: | |
self.__set_state(WiSUN.STATE_SCANNING) | |
self.__l.info("Scanning PANs...") | |
success, pans = await self.__bp35.scan(0xffffffff, 6, timeout=1000, scan_timeout=30000) | |
if success: | |
self.__l.info("PANs detected: {0}".format(pans)) | |
break | |
else: | |
self.__l.error("Failed to scan PANs") | |
if len(pans) == 0: | |
continue | |
## Connecting | |
self.__set_state(WiSUN.STATE_CONNECTING) | |
pan = pans[0] # type: Dict[bytes, bytes] | |
if not (b'Channel' in pan and b'Pan ID' in pan and b'Addr' in pan): | |
self.__l.error("Invalid PAN information") | |
continue | |
channel = int(str(pan[b'Channel'], 'utf-8'), 16) | |
pan_id = str(pan[b'Pan ID'], 'utf-8') | |
mac_address = str(pan[b'Addr'], 'utf-8') | |
if not await self.__bp35.set_channel(channel, timeout=1000): | |
self.__l.error("Failed to set channel.") | |
continue | |
if not await self.__bp35.set_pan_id(pan_id, timeout=1000): | |
self.__l.error("Failed to set PAN ID") | |
continue | |
ll_address = await self.__bp35.get_link_local_address(mac_address, timeout=1000) | |
if ll_address is None: | |
self.__l.error("Failed to get link local address") | |
continue | |
if not await self.__bp35.connect_to(ll_address, timeout=10000): | |
self.__l.error("Failed to connect to the coordinator.") | |
continue | |
self.__set_state(WiSUN.STATE_CONNECTED) | |
no_response_count = 0 | |
no_response_reset_count = 0 | |
while True: | |
if await self.__bp35.send_to(True, ll_address, 0xe1a, getPropertyFrame.bytes(), timeout=10000): | |
response = await self.__bp35.receive(response_buffer, timeout=10000) | |
if response is None: | |
no_response_count += 1 | |
if no_response_count >= 6: # Reset if there are no responses more than 6 times. | |
self.__l.info("No response. Perform reset.") | |
no_response_reset_count += 1 | |
break | |
else: | |
no_response_count = 0 | |
frame = EchonetLiteFrame(response) | |
if frame.is_valid(): | |
seoj = frame.seoj() | |
esv = frame.esv() | |
instant_power = None # type: Optional[float] | |
instant_current = None # type: Optional[Tuple[float,float]] | |
coefficient = 1 | |
cumulative_unit = 1.0 | |
cumulative_value = None | |
self.__l.debug("seoj={0}, esv={1}".format(seoj, esv)) | |
if seoj == b'\x02\x88\x01' and esv == EchonetLiteFrame.ESV_GET_RES: # Is the response for the request to read property? | |
for mv in frame.target_properies(): | |
if mv[0] == PROPERTY_INSTANT_POWER and mv[1] == 4 and len(mv) == 6: # instant power | |
power = struct.unpack('>i', mv[2:])[0] | |
self.__l.info('Power={0}[W]'.format(power)) | |
instant_power = power | |
elif mv[0] == PROPERTY_INSTANT_CURRENT and mv[1] == 4 and len(mv) == 6: # instant current | |
current_r, current_t = struct.unpack('>hh', mv[2:]) | |
self.__l.info('Current R={0},T={1}[dA]'.format(current_r, current_t)) | |
instant_current = (current_r, current_t) | |
elif mv[0] == PROPERTY_COEFFICIENT and mv[1] == 4 and len(mv) == 6: # coefficient | |
coefficient = struct.unpack('>I', mv[2:])[0] | |
self.__l.debug('Coefficient={0}'.format(coefficient)) | |
elif mv[0] == PROPERTY_CUMULATIVE_UNIT and mv[1] == 1 and len(mv) == 3: # unit | |
unit = mv[2] | |
if unit == 0x00: | |
cumulative_unit = 1.0e0 | |
elif unit == 0x01: | |
cumulative_unit = 1.0e-1 | |
elif unit == 0x02: | |
cumulative_unit = 1.0e-2 | |
elif unit == 0x03: | |
cumulative_unit = 1.0e-3 | |
elif unit == 0x04: | |
cumulative_unit = 1.0e-4 | |
elif unit == 0x0a: | |
cumulative_unit = 1.0e+1 | |
elif unit == 0x0b: | |
cumulative_unit = 1.0e+2 | |
elif unit == 0x0c: | |
cumulative_unit = 1.0e+3 | |
elif unit == 0x0d: | |
cumulative_unit = 1.0e+4 | |
self.__l.debug('CumulativeUnit={0}'.format(cumulative_unit)) | |
elif mv[0] == PROPERTY_CUMULATIVE_VALUE and mv[1] == 4 and len(mv) == 6: # cumulative power | |
cumulative_value = struct.unpack('>I', mv[2:])[0] | |
self.__l.debug('CumulativeValue={0}'.format(cumulative_value)) | |
updated = False | |
self.__lock.acquire() | |
if instant_power is not None: | |
self.__instant_power = instant_power | |
updated = True | |
if instant_current is not None: | |
self.__instant_current = instant_current | |
updated = True | |
if cumulative_value is not None: | |
self.__cumulative_power = cumulative_value*cumulative_unit | |
updated = True | |
self.__lock.release() | |
if updated: | |
self.__timestamp = time.time() | |
self.__notify_values() | |
await asyncio.sleep(10) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment