Compare commits

..

10 Commits
0.4.0 ... 0.4.4

13 changed files with 266 additions and 186 deletions

View File

@ -19,8 +19,8 @@ class _EntityViewPageState extends State<EntityViewPage> {
void initState() { void initState() {
super.initState(); super.initState();
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) { _stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
Logger.d("State change event handled by entity page: ${event.entityId}");
if (event.entityId == widget.entityId) { if (event.entityId == widget.entityId) {
Logger.d("State change event handled by entity page: ${event.entityId}");
setState(() {}); setState(() {});
} }
}); });

View File

@ -6,14 +6,12 @@ class CameraEntity extends Entity {
CameraEntity(Map rawData) : super(rawData); CameraEntity(Map rawData) : super(rawData);
bool get supportOnOff => ((attributes["supported_features"] & bool get supportOnOff => ((supportedFeatures &
CameraEntity.SUPPORT_ON_OFF) == CameraEntity.SUPPORT_ON_OFF) ==
CameraEntity.SUPPORT_ON_OFF); CameraEntity.SUPPORT_ON_OFF);
@override @override
Widget _buildAdditionalControlsForPage(BuildContext context) { Widget _buildAdditionalControlsForPage(BuildContext context) {
return CameraControlsWidget( return CameraStreamView();
url: '$homeAssistantWebHost/api/camera_proxy_stream/camera.demo_camera?token=${this.attributes['access_token']}',
);
} }
} }

View File

@ -23,44 +23,44 @@ class ClimateEntity extends Entity {
static const SUPPORT_AUX_HEAT = 2048; static const SUPPORT_AUX_HEAT = 2048;
static const SUPPORT_ON_OFF = 4096; static const SUPPORT_ON_OFF = 4096;
bool get supportTargetTemperature => ((attributes["supported_features"] & bool get supportTargetTemperature => ((supportedFeatures &
ClimateEntity.SUPPORT_TARGET_TEMPERATURE) == ClimateEntity.SUPPORT_TARGET_TEMPERATURE) ==
ClimateEntity.SUPPORT_TARGET_TEMPERATURE); ClimateEntity.SUPPORT_TARGET_TEMPERATURE);
bool get supportTargetTemperatureHigh => ((attributes["supported_features"] & bool get supportTargetTemperatureHigh => ((supportedFeatures &
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH) == ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH) ==
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH); ClimateEntity.SUPPORT_TARGET_TEMPERATURE_HIGH);
bool get supportTargetTemperatureLow => ((attributes["supported_features"] & bool get supportTargetTemperatureLow => ((supportedFeatures &
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW) == ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW) ==
ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW); ClimateEntity.SUPPORT_TARGET_TEMPERATURE_LOW);
bool get supportTargetHumidity => ((attributes["supported_features"] & bool get supportTargetHumidity => ((supportedFeatures &
ClimateEntity.SUPPORT_TARGET_HUMIDITY) == ClimateEntity.SUPPORT_TARGET_HUMIDITY) ==
ClimateEntity.SUPPORT_TARGET_HUMIDITY); ClimateEntity.SUPPORT_TARGET_HUMIDITY);
bool get supportTargetHumidityHigh => ((attributes["supported_features"] & bool get supportTargetHumidityHigh => ((supportedFeatures &
ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH) == ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH) ==
ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH); ClimateEntity.SUPPORT_TARGET_HUMIDITY_HIGH);
bool get supportTargetHumidityLow => ((attributes["supported_features"] & bool get supportTargetHumidityLow => ((supportedFeatures &
ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW) == ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW) ==
ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW); ClimateEntity.SUPPORT_TARGET_HUMIDITY_LOW);
bool get supportFanMode => bool get supportFanMode =>
((attributes["supported_features"] & ClimateEntity.SUPPORT_FAN_MODE) == ((supportedFeatures & ClimateEntity.SUPPORT_FAN_MODE) ==
ClimateEntity.SUPPORT_FAN_MODE); ClimateEntity.SUPPORT_FAN_MODE);
bool get supportOperationMode => ((attributes["supported_features"] & bool get supportOperationMode => ((supportedFeatures &
ClimateEntity.SUPPORT_OPERATION_MODE) == ClimateEntity.SUPPORT_OPERATION_MODE) ==
ClimateEntity.SUPPORT_OPERATION_MODE); ClimateEntity.SUPPORT_OPERATION_MODE);
bool get supportHoldMode => bool get supportHoldMode =>
((attributes["supported_features"] & ClimateEntity.SUPPORT_HOLD_MODE) == ((supportedFeatures & ClimateEntity.SUPPORT_HOLD_MODE) ==
ClimateEntity.SUPPORT_HOLD_MODE); ClimateEntity.SUPPORT_HOLD_MODE);
bool get supportSwingMode => bool get supportSwingMode =>
((attributes["supported_features"] & ClimateEntity.SUPPORT_SWING_MODE) == ((supportedFeatures & ClimateEntity.SUPPORT_SWING_MODE) ==
ClimateEntity.SUPPORT_SWING_MODE); ClimateEntity.SUPPORT_SWING_MODE);
bool get supportAwayMode => bool get supportAwayMode =>
((attributes["supported_features"] & ClimateEntity.SUPPORT_AWAY_MODE) == ((supportedFeatures & ClimateEntity.SUPPORT_AWAY_MODE) ==
ClimateEntity.SUPPORT_AWAY_MODE); ClimateEntity.SUPPORT_AWAY_MODE);
bool get supportAuxHeat => bool get supportAuxHeat =>
((attributes["supported_features"] & ClimateEntity.SUPPORT_AUX_HEAT) == ((supportedFeatures & ClimateEntity.SUPPORT_AUX_HEAT) ==
ClimateEntity.SUPPORT_AUX_HEAT); ClimateEntity.SUPPORT_AUX_HEAT);
bool get supportOnOff => bool get supportOnOff =>
((attributes["supported_features"] & ClimateEntity.SUPPORT_ON_OFF) == ((supportedFeatures & ClimateEntity.SUPPORT_ON_OFF) ==
ClimateEntity.SUPPORT_ON_OFF); ClimateEntity.SUPPORT_ON_OFF);
List<String> get operationList => attributes["operation_list"] != null List<String> get operationList => attributes["operation_list"] != null

View File

@ -11,29 +11,29 @@ class CoverEntity extends Entity {
static const SUPPORT_STOP_TILT = 64; static const SUPPORT_STOP_TILT = 64;
static const SUPPORT_SET_TILT_POSITION = 128; static const SUPPORT_SET_TILT_POSITION = 128;
bool get supportOpen => ((attributes["supported_features"] & bool get supportOpen => ((supportedFeatures &
CoverEntity.SUPPORT_OPEN) == CoverEntity.SUPPORT_OPEN) ==
CoverEntity.SUPPORT_OPEN); CoverEntity.SUPPORT_OPEN);
bool get supportClose => ((attributes["supported_features"] & bool get supportClose => ((supportedFeatures &
CoverEntity.SUPPORT_CLOSE) == CoverEntity.SUPPORT_CLOSE) ==
CoverEntity.SUPPORT_CLOSE); CoverEntity.SUPPORT_CLOSE);
bool get supportSetPosition => ((attributes["supported_features"] & bool get supportSetPosition => ((supportedFeatures &
CoverEntity.SUPPORT_SET_POSITION) == CoverEntity.SUPPORT_SET_POSITION) ==
CoverEntity.SUPPORT_SET_POSITION); CoverEntity.SUPPORT_SET_POSITION);
bool get supportStop => ((attributes["supported_features"] & bool get supportStop => ((supportedFeatures &
CoverEntity.SUPPORT_STOP) == CoverEntity.SUPPORT_STOP) ==
CoverEntity.SUPPORT_STOP); CoverEntity.SUPPORT_STOP);
bool get supportOpenTilt => ((attributes["supported_features"] & bool get supportOpenTilt => ((supportedFeatures &
CoverEntity.SUPPORT_OPEN_TILT) == CoverEntity.SUPPORT_OPEN_TILT) ==
CoverEntity.SUPPORT_OPEN_TILT); CoverEntity.SUPPORT_OPEN_TILT);
bool get supportCloseTilt => ((attributes["supported_features"] & bool get supportCloseTilt => ((supportedFeatures &
CoverEntity.SUPPORT_CLOSE_TILT) == CoverEntity.SUPPORT_CLOSE_TILT) ==
CoverEntity.SUPPORT_CLOSE_TILT); CoverEntity.SUPPORT_CLOSE_TILT);
bool get supportStopTilt => ((attributes["supported_features"] & bool get supportStopTilt => ((supportedFeatures &
CoverEntity.SUPPORT_STOP_TILT) == CoverEntity.SUPPORT_STOP_TILT) ==
CoverEntity.SUPPORT_STOP_TILT); CoverEntity.SUPPORT_STOP_TILT);
bool get supportSetTiltPosition => ((attributes["supported_features"] & bool get supportSetTiltPosition => ((supportedFeatures &
CoverEntity.SUPPORT_SET_TILT_POSITION) == CoverEntity.SUPPORT_SET_TILT_POSITION) ==
CoverEntity.SUPPORT_SET_TILT_POSITION); CoverEntity.SUPPORT_SET_TILT_POSITION);

View File

@ -91,6 +91,7 @@ class Entity {
String get lastUpdated => _getLastUpdatedFormatted(); String get lastUpdated => _getLastUpdatedFormatted();
bool get isHidden => attributes["hidden"] ?? false; bool get isHidden => attributes["hidden"] ?? false;
double get doubleState => double.tryParse(state) ?? 0.0; double get doubleState => double.tryParse(state) ?? 0.0;
int get supportedFeatures => attributes["supported_features"] ?? 0;
Entity(Map rawData) { Entity(Map rawData) {
update(rawData); update(rawData);

View File

@ -8,13 +8,13 @@ class FanEntity extends Entity {
FanEntity(Map rawData) : super(rawData); FanEntity(Map rawData) : super(rawData);
bool get supportSetSpeed => ((attributes["supported_features"] & bool get supportSetSpeed => ((supportedFeatures &
FanEntity.SUPPORT_SET_SPEED) == FanEntity.SUPPORT_SET_SPEED) ==
FanEntity.SUPPORT_SET_SPEED); FanEntity.SUPPORT_SET_SPEED);
bool get supportOscillate => ((attributes["supported_features"] & bool get supportOscillate => ((supportedFeatures &
FanEntity.SUPPORT_OSCILLATE) == FanEntity.SUPPORT_OSCILLATE) ==
FanEntity.SUPPORT_OSCILLATE); FanEntity.SUPPORT_OSCILLATE);
bool get supportDirection => ((attributes["supported_features"] & bool get supportDirection => ((supportedFeatures &
FanEntity.SUPPORT_DIRECTION) == FanEntity.SUPPORT_DIRECTION) ==
FanEntity.SUPPORT_DIRECTION); FanEntity.SUPPORT_DIRECTION);

View File

@ -10,25 +10,25 @@ class LightEntity extends Entity {
static const SUPPORT_TRANSITION = 32; static const SUPPORT_TRANSITION = 32;
static const SUPPORT_WHITE_VALUE = 128; static const SUPPORT_WHITE_VALUE = 128;
bool get supportBrightness => ((attributes["supported_features"] & bool get supportBrightness => ((supportedFeatures &
LightEntity.SUPPORT_BRIGHTNESS) == LightEntity.SUPPORT_BRIGHTNESS) ==
LightEntity.SUPPORT_BRIGHTNESS); LightEntity.SUPPORT_BRIGHTNESS);
bool get supportColorTemp => ((attributes["supported_features"] & bool get supportColorTemp => ((supportedFeatures &
LightEntity.SUPPORT_COLOR_TEMP) == LightEntity.SUPPORT_COLOR_TEMP) ==
LightEntity.SUPPORT_COLOR_TEMP); LightEntity.SUPPORT_COLOR_TEMP);
bool get supportEffect => ((attributes["supported_features"] & bool get supportEffect => ((supportedFeatures &
LightEntity.SUPPORT_EFFECT) == LightEntity.SUPPORT_EFFECT) ==
LightEntity.SUPPORT_EFFECT); LightEntity.SUPPORT_EFFECT);
bool get supportFlash => ((attributes["supported_features"] & bool get supportFlash => ((supportedFeatures &
LightEntity.SUPPORT_FLASH) == LightEntity.SUPPORT_FLASH) ==
LightEntity.SUPPORT_FLASH); LightEntity.SUPPORT_FLASH);
bool get supportColor => ((attributes["supported_features"] & bool get supportColor => ((supportedFeatures &
LightEntity.SUPPORT_COLOR) == LightEntity.SUPPORT_COLOR) ==
LightEntity.SUPPORT_COLOR); LightEntity.SUPPORT_COLOR);
bool get supportTransition => ((attributes["supported_features"] & bool get supportTransition => ((supportedFeatures &
LightEntity.SUPPORT_TRANSITION) == LightEntity.SUPPORT_TRANSITION) ==
LightEntity.SUPPORT_TRANSITION); LightEntity.SUPPORT_TRANSITION);
bool get supportWhiteValue => ((attributes["supported_features"] & bool get supportWhiteValue => ((supportedFeatures &
LightEntity.SUPPORT_WHITE_VALUE) == LightEntity.SUPPORT_WHITE_VALUE) ==
LightEntity.SUPPORT_WHITE_VALUE); LightEntity.SUPPORT_WHITE_VALUE);
@ -38,7 +38,7 @@ class LightEntity extends Entity {
double get maxMireds => _getDoubleAttributeValue("max_mireds"); double get maxMireds => _getDoubleAttributeValue("max_mireds");
double get minMireds => _getDoubleAttributeValue("min_mireds"); double get minMireds => _getDoubleAttributeValue("min_mireds");
HSVColor get color => _getColor(); HSVColor get color => _getColor();
bool get isAdditionalControls => ((attributes["supported_features"] != null) && (attributes["supported_features"] != 0)); bool get isAdditionalControls => ((supportedFeatures != null) && (supportedFeatures != 0));
List<String> get effectList => getStringListAttributeValue("effect_list"); List<String> get effectList => getStringListAttributeValue("effect_list");
LightEntity(Map rawData) : super(rawData); LightEntity(Map rawData) : super(rawData);

View File

@ -22,53 +22,53 @@ class MediaPlayerEntity extends Entity {
MediaPlayerEntity(Map rawData) : super(rawData); MediaPlayerEntity(Map rawData) : super(rawData);
bool get supportPause => ((attributes["supported_features"] & bool get supportPause => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_PAUSE) == MediaPlayerEntity.SUPPORT_PAUSE) ==
MediaPlayerEntity.SUPPORT_PAUSE); MediaPlayerEntity.SUPPORT_PAUSE);
bool get supportSeek => ((attributes["supported_features"] & bool get supportSeek => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_SEEK) == MediaPlayerEntity.SUPPORT_SEEK) ==
MediaPlayerEntity.SUPPORT_SEEK); MediaPlayerEntity.SUPPORT_SEEK);
bool get supportVolumeSet => ((attributes["supported_features"] & bool get supportVolumeSet => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_VOLUME_SET) == MediaPlayerEntity.SUPPORT_VOLUME_SET) ==
MediaPlayerEntity.SUPPORT_VOLUME_SET); MediaPlayerEntity.SUPPORT_VOLUME_SET);
bool get supportVolumeMute => ((attributes["supported_features"] & bool get supportVolumeMute => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_VOLUME_MUTE) == MediaPlayerEntity.SUPPORT_VOLUME_MUTE) ==
MediaPlayerEntity.SUPPORT_VOLUME_MUTE); MediaPlayerEntity.SUPPORT_VOLUME_MUTE);
bool get supportPreviousTrack => ((attributes["supported_features"] & bool get supportPreviousTrack => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_PREVIOUS_TRACK) == MediaPlayerEntity.SUPPORT_PREVIOUS_TRACK) ==
MediaPlayerEntity.SUPPORT_PREVIOUS_TRACK); MediaPlayerEntity.SUPPORT_PREVIOUS_TRACK);
bool get supportNextTrack => ((attributes["supported_features"] & bool get supportNextTrack => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_NEXT_TRACK) == MediaPlayerEntity.SUPPORT_NEXT_TRACK) ==
MediaPlayerEntity.SUPPORT_NEXT_TRACK); MediaPlayerEntity.SUPPORT_NEXT_TRACK);
bool get supportTurnOn => ((attributes["supported_features"] & bool get supportTurnOn => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_TURN_ON) == MediaPlayerEntity.SUPPORT_TURN_ON) ==
MediaPlayerEntity.SUPPORT_TURN_ON); MediaPlayerEntity.SUPPORT_TURN_ON);
bool get supportTurnOff => ((attributes["supported_features"] & bool get supportTurnOff => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_TURN_OFF) == MediaPlayerEntity.SUPPORT_TURN_OFF) ==
MediaPlayerEntity.SUPPORT_TURN_OFF); MediaPlayerEntity.SUPPORT_TURN_OFF);
bool get supportPlayMedia => ((attributes["supported_features"] & bool get supportPlayMedia => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_PLAY_MEDIA) == MediaPlayerEntity.SUPPORT_PLAY_MEDIA) ==
MediaPlayerEntity.SUPPORT_PLAY_MEDIA); MediaPlayerEntity.SUPPORT_PLAY_MEDIA);
bool get supportVolumeStep => ((attributes["supported_features"] & bool get supportVolumeStep => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_VOLUME_STEP) == MediaPlayerEntity.SUPPORT_VOLUME_STEP) ==
MediaPlayerEntity.SUPPORT_VOLUME_STEP); MediaPlayerEntity.SUPPORT_VOLUME_STEP);
bool get supportSelectSource => ((attributes["supported_features"] & bool get supportSelectSource => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_SELECT_SOURCE) == MediaPlayerEntity.SUPPORT_SELECT_SOURCE) ==
MediaPlayerEntity.SUPPORT_SELECT_SOURCE); MediaPlayerEntity.SUPPORT_SELECT_SOURCE);
bool get supportStop => ((attributes["supported_features"] & bool get supportStop => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_STOP) == MediaPlayerEntity.SUPPORT_STOP) ==
MediaPlayerEntity.SUPPORT_STOP); MediaPlayerEntity.SUPPORT_STOP);
bool get supportClearPlaylist => ((attributes["supported_features"] & bool get supportClearPlaylist => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_CLEAR_PLAYLIST) == MediaPlayerEntity.SUPPORT_CLEAR_PLAYLIST) ==
MediaPlayerEntity.SUPPORT_CLEAR_PLAYLIST); MediaPlayerEntity.SUPPORT_CLEAR_PLAYLIST);
bool get supportPlay => ((attributes["supported_features"] & bool get supportPlay => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_PLAY) == MediaPlayerEntity.SUPPORT_PLAY) ==
MediaPlayerEntity.SUPPORT_PLAY); MediaPlayerEntity.SUPPORT_PLAY);
bool get supportShuffleSet => ((attributes["supported_features"] & bool get supportShuffleSet => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_SHUFFLE_SET) == MediaPlayerEntity.SUPPORT_SHUFFLE_SET) ==
MediaPlayerEntity.SUPPORT_SHUFFLE_SET); MediaPlayerEntity.SUPPORT_SHUFFLE_SET);
bool get supportSelectSoundMode => ((attributes["supported_features"] & bool get supportSelectSoundMode => ((supportedFeatures &
MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE) == MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE) ==
MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE); MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE);

View File

@ -0,0 +1,175 @@
part of '../../main.dart';
class CameraStreamView extends StatefulWidget {
CameraStreamView({Key key}) : super(key: key);
@override
_CameraStreamViewState createState() => _CameraStreamViewState();
}
class _CameraStreamViewState extends State<CameraStreamView> {
@override
void initState() {
super.initState();
}
CameraEntity _entity;
http.Client client;
http.StreamedResponse response;
List<int> binaryImage = [];
bool timeToStop = false;
Completer streamCompleter;
bool started = false;
bool useSVG = false;
void _connect() async {
started = true;
timeToStop = false;
String streamUrl = '$homeAssistantWebHost/api/camera_proxy_stream/${_entity.entityId}?token=${_entity.attributes['access_token']}';
client = new http.Client(); // create a client to make api calls
http.Request request = new http.Request("GET", Uri.parse(streamUrl)); // create get request
Logger.d("[Sending] ==> $streamUrl");
response = await client.send(request);
Logger.d("[Received] <== ${response.headers}");
String frameBoundary = response.headers['content-type'].split('boundary=')[1];
final int frameBoundarySize = frameBoundary.length;
List<int> primaryBuffer=[];
int imageSizeStart = 59;
int imageSizeEnd = 0;
int imageStart = 0;
int imageSize = 0;
String strBuffer = "";
String contentType = "";
streamCompleter = Completer();
response.stream.transform(
StreamTransformer.fromHandlers(
handleData: (data, sink) {
primaryBuffer.addAll(data);
imageStart = 0;
imageSizeEnd = 0;
if (primaryBuffer.length >= imageSizeStart + 10) {
contentType = utf8.decode(
primaryBuffer.sublist(frameBoundarySize+16, imageSizeStart + 10), allowMalformed: true).split("\r\n")[0];
useSVG = contentType == "image/svg+xml";
imageSizeStart = frameBoundarySize + 16 + contentType.length + 18;
for (int i = imageSizeStart; i < primaryBuffer.length - 4; i++) {
strBuffer = utf8.decode(
primaryBuffer.sublist(i, i + 4), allowMalformed: true);
if (strBuffer == "\r\n\r\n") {
imageSizeEnd = i;
imageStart = i + 4;
break;
}
}
if (imageSizeEnd > 0) {
imageSize = int.tryParse(utf8.decode(
primaryBuffer.sublist(imageSizeStart, imageSizeEnd),
allowMalformed: true));
//Logger.d("content-length: $imageSize");
if (imageSize != null &&
primaryBuffer.length >= imageStart + imageSize + 2) {
sink.add(
primaryBuffer.sublist(
imageStart, imageStart + imageSize));
primaryBuffer.removeRange(0, imageStart + imageSize + 2);
}
}
}
if (timeToStop) {
sink?.close();
streamCompleter.complete();
}
},
handleError: (error, stack, sink) {
Logger.e("Error parsing MJPEG stream: $error");
},
handleDone: (sink) {
Logger.d("Camera stream finished. Reconnecting...");
sink?.close();
streamCompleter?.complete();
_reconnect();
},
)
).listen((d) {
if (!timeToStop) {
setState(() {
binaryImage = d;
});
}
});
}
void _reconnect() {
disconnect().then((_){
_connect();
});
}
Future disconnect() {
Completer disconF = Completer();
timeToStop = true;
if (streamCompleter != null && !streamCompleter.isCompleted) {
streamCompleter.future.then((_) {
client?.close();
disconF.complete();
});
} else {
client?.close();
disconF.complete();
}
return disconF.future;
}
@override
Widget build(BuildContext context) {
if (!started) {
_entity = EntityModel
.of(context)
.entityWrapper
.entity;
_connect();
}
if (binaryImage.isEmpty) {
return Column(
children: <Widget>[
Container(
padding: const EdgeInsets.all(20.0),
child: const CircularProgressIndicator()
)
],
);
} else {
if (useSVG) {
return Column(
children: <Widget>[
SvgPicture.memory(
Uint8List.fromList(binaryImage),
placeholderBuilder: (BuildContext context) =>
new Container(
padding: const EdgeInsets.all(20.0),
child: const CircularProgressIndicator()
),
)
],
);
} else {
return Column(
children: <Widget>[
Image.memory(
Uint8List.fromList(binaryImage), gaplessPlayback: true),
],
);
}
}
}
@override
void dispose() {
disconnect();
super.dispose();
}
}

View File

@ -1,115 +0,0 @@
part of '../../main.dart';
class CameraControlsWidget extends StatefulWidget {
final String url;
CameraControlsWidget({Key key, @required this.url}) : super(key: key);
@override
_CameraControlsWidgetState createState() => _CameraControlsWidgetState();
}
class _CameraControlsWidgetState extends State<CameraControlsWidget> {
@override
void initState() {
super.initState();
_getData();
}
http.Client client;
http.StreamedResponse response;
List<int> binaryImage = [];
String cameraState = "Connecting...";
bool timeToStop = false;
Completer streamCompleter;
void _getData() async {
client = new http.Client(); // create a client to make api calls
http.Request request = new http.Request("GET", Uri.parse(widget.url)); // create get request
Logger.d("[Sending] ==> ${widget.url}");
response = await client.send(request);
setState(() {
cameraState = "Starting...";
});
Logger.d("[Received] <== ${response.headers}");
List<int> primaryBuffer=[];
final int imageSizeStart = 59;
int imageSizeEnd = 0;
int imageStart = 0;
int imageSize = 0;
String strBuffer = "";
streamCompleter = Completer();
response.stream.transform(
StreamTransformer.fromHandlers(
handleData: (data, sink) {
primaryBuffer.addAll(data);
if (primaryBuffer.length >= 66) {
for (int i = imageSizeStart; i < primaryBuffer.length - 4; i++) {
strBuffer = utf8.decode(
primaryBuffer.sublist(i, i + 4), allowMalformed: true);
if (strBuffer == "\r\n\r\n") {
imageSizeEnd = i;
imageStart = i + 4;
break;
}
}
imageSize = int.tryParse(utf8.decode(
primaryBuffer.sublist(imageSizeStart, imageSizeEnd),
allowMalformed: true));
if (imageSize != null && primaryBuffer.length >= imageStart + imageSize + 2) {
sink.add(
primaryBuffer.sublist(imageStart, imageStart + imageSize));
primaryBuffer.removeRange(0, imageStart + imageSize + 2);
}
}
if (timeToStop) {
sink?.close();
streamCompleter.complete();
}
},
handleError: (error, stack, sink) {
Logger.e("Error parsing MJPEG stream: $error");
},
handleDone: (sink) {
sink?.close();
},
)
).listen((d) {
setState(() {
binaryImage = d;
});
});
}
@override
Widget build(BuildContext context) {
if (binaryImage.isEmpty) {
return Column(
children: <Widget>[
Text("$cameraState")
],
);
} else {
return Column(
children: <Widget>[
Image.memory(Uint8List.fromList(binaryImage), gaplessPlayback: true),
],
);
}
}
@override
void dispose() {
super.dispose();
timeToStop = true;
if (streamCompleter != null && !streamCompleter.isCompleted) {
streamCompleter.future.then((_) {
client?.close();
});
} else {
client?.close();
}
}
}

View File

@ -15,6 +15,7 @@ import 'package:http/http.dart' as http;
import 'package:charts_flutter/flutter.dart' as charts; import 'package:charts_flutter/flutter.dart' as charts;
import 'package:progress_indicators/progress_indicators.dart'; import 'package:progress_indicators/progress_indicators.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_svg/flutter_svg.dart';
part 'entity_class/const.dart'; part 'entity_class/const.dart';
part 'entity_class/entity.class.dart'; part 'entity_class/entity.class.dart';
@ -50,6 +51,7 @@ part 'entity_widgets/common/mode_selector.dart';
part 'entity_widgets/common/universal_slider.dart'; part 'entity_widgets/common/universal_slider.dart';
part 'entity_widgets/common/flat_service_button.dart'; part 'entity_widgets/common/flat_service_button.dart';
part 'entity_widgets/common/light_color_picker.dart'; part 'entity_widgets/common/light_color_picker.dart';
part 'entity_widgets/common/camera_stream_view.dart';
part 'entity_widgets/entity_colors.class.dart'; part 'entity_widgets/entity_colors.class.dart';
part 'entity_widgets/entity_page_container.dart'; part 'entity_widgets/entity_page_container.dart';
part 'entity_widgets/history_chart/entity_history.dart'; part 'entity_widgets/history_chart/entity_history.dart';
@ -73,7 +75,6 @@ part 'entity_widgets/controls/light_controls.dart';
part 'entity_widgets/controls/media_player_widgets.dart'; part 'entity_widgets/controls/media_player_widgets.dart';
part 'entity_widgets/controls/fan_controls.dart'; part 'entity_widgets/controls/fan_controls.dart';
part 'entity_widgets/controls/alarm_control_panel_controls.dart'; part 'entity_widgets/controls/alarm_control_panel_controls.dart';
part 'entity_widgets/controls/camera_controls.dart';
part 'settings.page.dart'; part 'settings.page.dart';
part 'configuration.page.dart'; part 'configuration.page.dart';
part 'home_assistant.class.dart'; part 'home_assistant.class.dart';
@ -93,8 +94,7 @@ part 'ui_widgets/card_header_widget.dart';
EventBus eventBus = new EventBus(); EventBus eventBus = new EventBus();
const String appName = "HA Client"; const String appName = "HA Client";
const appVersion = "0.4.0"; const appVersion = "0.4.4";
const appBuild = "91";
String homeAssistantWebHost; String homeAssistantWebHost;
@ -383,8 +383,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
) )
], ],
applicationName: appName, applicationName: appName,
applicationVersion: appVersion, applicationVersion: appVersion
applicationLegalese: "build $appBuild",
) )
]); ]);
} }

View File

@ -35,7 +35,7 @@ packages:
name: cached_network_image name: cached_network_image
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.6.0-alpha.2" version: "0.6.0+1"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
@ -49,14 +49,14 @@ packages:
name: charts_common name: charts_common
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.5.0" version: "0.6.0"
charts_flutter: charts_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
name: charts_flutter name: charts_flutter
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.5.0" version: "0.6.0"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@ -100,7 +100,7 @@ packages:
name: event_bus name: event_bus
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "1.0.3"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -112,7 +112,7 @@ packages:
name: flutter_cache_manager name: flutter_cache_manager
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.3.0-beta.2+1" version: "0.3.0"
flutter_launcher_icons: flutter_launcher_icons:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -127,6 +127,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.0" version: "0.2.0"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.3"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -195,13 +202,27 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.6.2" version: "1.6.2"
path_drawing:
dependency: transitive
description:
name: path_drawing
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.0"
path_parsing:
dependency: transitive
description:
name: path_parsing
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
path_provider: path_provider:
dependency: transitive dependency: transitive
description: description:
name: path_provider name: path_provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.1" version: "0.5.0+1"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@ -248,7 +269,7 @@ packages:
name: sqflite name: sqflite
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.2+1" version: "1.1.0+1"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -276,7 +297,7 @@ packages:
name: synchronized name: synchronized
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.5.3+2" version: "2.0.2+1"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -311,7 +332,7 @@ packages:
name: uuid name: uuid
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3" version: "2.0.0"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -332,7 +353,7 @@ packages:
name: xml name: xml
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.3.1" version: "3.2.5"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
@ -341,5 +362,5 @@ packages:
source: hosted source: hosted
version: "2.1.15" version: "2.1.15"
sdks: sdks:
dart: ">=2.0.0 <3.0.0" dart: ">=2.1.0 <3.0.0"
flutter: ">=0.5.6 <2.0.0" flutter: ">=0.7.3 <2.0.0"

View File

@ -1,7 +1,7 @@
name: hass_client name: hass_client
description: Home Assistant Android Client description: Home Assistant Android Client
version: 0.4.0+91 version: 0.4.4+95
environment: environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0" sdk: ">=2.0.0-dev.68.0 <3.0.0"
@ -18,6 +18,7 @@ dependencies:
date_format: any date_format: any
charts_flutter: any charts_flutter: any
flutter_markdown: any flutter_markdown: any
flutter_svg: ^0.10.3
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: